Sanitize candidates in ICE-level stats when necessary.

The address and the related address of local candidates are sanitized
accordingly when the mDNS concealment of local IPs is enabled. Also,
remote hostname candidates created from signaling are sanitized in stats
as well. A couple of unit tests are revised to reflect the desired
behavior of AsyncResolverInterface so that when a hostname candidate is
resolved, the hostname is kept in the candidate address.

Bug: webrtc:9605, chromium:914452
Change-Id: Iad9ad04ce4e50304e44cf04b15b97a7ae2dec960
Reviewed-on: https://webrtc-review.googlesource.com/c/113643
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Jeroen de Borst <jeroendb@webrtc.org>
Commit-Queue: Qingsi Wang <qingsi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25996}
diff --git a/api/candidate.cc b/api/candidate.cc
index 275b173..5637566 100644
--- a/api/candidate.cc
+++ b/api/candidate.cc
@@ -125,4 +125,19 @@
   return !(*this == o);
 }
 
+Candidate Candidate::ToSanitizedCopy(bool use_hostname_address,
+                                     bool filter_related_address) const {
+  Candidate copy(*this);
+  if (use_hostname_address) {
+    rtc::SocketAddress hostname_only_addr(address().hostname(),
+                                          address().port());
+    copy.set_address(hostname_only_addr);
+  }
+  if (filter_related_address) {
+    copy.set_related_address(
+        rtc::EmptySocketAddressWithFamily(copy.address().family()));
+  }
+  return copy;
+}
+
 }  // namespace cricket
diff --git a/api/candidate.h b/api/candidate.h
index 4c650d9..02b4bca 100644
--- a/api/candidate.h
+++ b/api/candidate.h
@@ -166,6 +166,16 @@
   bool operator==(const Candidate& o) const;
   bool operator!=(const Candidate& o) const;
 
+  // Returns a sanitized copy configured by the given booleans. If
+  // |use_host_address| is true, the returned copy has its IP removed from
+  // |address()|, which leads |address()| to be a hostname address. If
+  // |filter_related_address|, the returned copy has its related address reset
+  // to the wildcard address (i.e. 0.0.0.0 for IPv4 and :: for IPv6). Note that
+  // setting both booleans to false returns an identical copy to the original
+  // candidate.
+  Candidate ToSanitizedCopy(bool use_hostname_address,
+                            bool filter_related_address) const;
+
  private:
   std::string ToStringInternal(bool sensitive) const;
 
diff --git a/p2p/base/p2ptransportchannel_unittest.cc b/p2p/base/p2ptransportchannel_unittest.cc
index 8549794b..bd767cd 100644
--- a/p2p/base/p2ptransportchannel_unittest.cc
+++ b/p2p/base/p2ptransportchannel_unittest.cc
@@ -4611,7 +4611,7 @@
   ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
   // ICE parameter will be set up when creating the channels.
   set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
-  GetEndpoint(0)->network_manager_.CreateMdnsResponder();
+  GetEndpoint(0)->network_manager_.CreateMdnsResponder(rtc::Thread::Current());
   GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
   CreateChannels();
   // Pause sending candidates from both endpoints until we find out what port
@@ -4682,7 +4682,7 @@
   ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
   // ICE parameter will be set up when creating the channels.
   set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
-  GetEndpoint(0)->network_manager_.CreateMdnsResponder();
+  GetEndpoint(0)->network_manager_.CreateMdnsResponder(rtc::Thread::Current());
   GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
   CreateChannels();
   // Pause sending candidates from both endpoints until we find out what port
@@ -4749,7 +4749,7 @@
   ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
   // ICE parameter will be set up when creating the channels.
   set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
-  GetEndpoint(0)->network_manager_.CreateMdnsResponder();
+  GetEndpoint(0)->network_manager_.CreateMdnsResponder(rtc::Thread::Current());
   GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
   CreateChannels();
   // Pause sending candidates from both endpoints until we find out what port
@@ -4758,17 +4758,17 @@
   PauseCandidates(1);
   ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
   ASSERT_EQ(1u, GetEndpoint(0)->saved_candidates_[0]->candidates.size());
-  const auto& local_candidate =
+  const auto& local_candidate_ep1 =
       GetEndpoint(0)->saved_candidates_[0]->candidates[0];
   // The IP address of ep1's host candidate should be obfuscated.
-  EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
+  EXPECT_TRUE(local_candidate_ep1.address().IsUnresolvedIP());
   // This is the underlying private IP address of the same candidate at ep1,
-  // and let the mock resolver of ep2 receives the correct resolution.
-  const auto local_address = rtc::SocketAddress(
-      kPublicAddrs[0].ipaddr(), local_candidate.address().port());
+  // and let the mock resolver of ep2 receive the correct resolution.
+  rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
+  resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr());
 
   EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(local_address), Return(true)));
+      .WillOnce(DoAll(SetArgPointee<1>(resolved_address_ep1), Return(true)));
   // Let ep1 signal its hostname candidate to ep2.
   ResumeCandidates(0);
 
@@ -4784,4 +4784,103 @@
   DestroyChannels();
 }
 
+// Test that when the IP of a host candidate is concealed by an mDNS name, the
+// stats from the gathering ICE endpoint do not reveal the address of this local
+// host candidate or the related address of a local srflx candidate from the
+// same endpoint. Also, the remote ICE endpoint that successfully resolves a
+// signaled host candidate with an mDNS name should not reveal the address of
+// this remote host candidate in stats.
+TEST_F(P2PTransportChannelTest,
+       CandidatesSanitizedInStatsWhenMdnsObfuscationEnabled) {
+  NiceMock<rtc::MockAsyncResolver> mock_async_resolver;
+  webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
+  EXPECT_CALL(mock_async_resolver_factory, Create())
+      .WillOnce(Return(&mock_async_resolver));
+
+  // ep1 and ep2 will gather host candidates with addresses
+  // kPublicAddrs[0] and kPublicAddrs[1], respectively. ep1 also gathers a srflx
+  // and a relay candidates.
+  ConfigureEndpoints(OPEN, OPEN,
+                     kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_TCP,
+                     kOnlyLocalPorts);
+  // ICE parameter will be set up when creating the channels.
+  set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+  GetEndpoint(0)->network_manager_.CreateMdnsResponder(rtc::Thread::Current());
+  GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
+  CreateChannels();
+  // Pause sending candidates from both endpoints until we find out what port
+  // number is assigned to ep1's host candidate.
+  PauseCandidates(0);
+  PauseCandidates(1);
+  // Ep1 has a UDP host, a srflx and a relay candidates.
+  ASSERT_EQ_WAIT(3u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+  ASSERT_EQ_WAIT(1u, GetEndpoint(1)->saved_candidates_.size(), kMediumTimeout);
+
+  for (const auto& candidates_data : GetEndpoint(0)->saved_candidates_) {
+    ASSERT_EQ(1u, candidates_data->candidates.size());
+    const auto& local_candidate_ep1 = candidates_data->candidates[0];
+    if (local_candidate_ep1.type() == LOCAL_PORT_TYPE) {
+      // This is the underlying private IP address of the same candidate at ep1,
+      // and let the mock resolver of ep2 receive the correct resolution.
+      rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
+      resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr());
+      EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
+          .WillOnce(
+              DoAll(SetArgPointee<1>(resolved_address_ep1), Return(true)));
+      break;
+    }
+  }
+  ResumeCandidates(0);
+  ResumeCandidates(1);
+
+  ASSERT_EQ_WAIT(kIceGatheringComplete, ep1_ch1()->gathering_state(),
+                 kMediumTimeout);
+  // We should have the following candidate pairs on both endpoints:
+  // ep1_host <-> ep2_host, ep1_srflx <-> ep2_host, ep1_relay <-> ep2_host
+  ASSERT_EQ_WAIT(3u, ep1_ch1()->connections().size(), kMediumTimeout);
+  ASSERT_EQ_WAIT(3u, ep2_ch1()->connections().size(), kMediumTimeout);
+
+  ConnectionInfos connection_infos_ep1;
+  CandidateStatsList candidate_stats_list_ep1;
+  ConnectionInfos connection_infos_ep2;
+  CandidateStatsList candidate_stats_list_ep2;
+  ep1_ch1()->GetStats(&connection_infos_ep1, &candidate_stats_list_ep1);
+  ep2_ch1()->GetStats(&connection_infos_ep2, &candidate_stats_list_ep2);
+  EXPECT_EQ(3u, connection_infos_ep1.size());
+  EXPECT_EQ(3u, candidate_stats_list_ep1.size());
+  EXPECT_EQ(3u, connection_infos_ep2.size());
+  // Check the stats of ep1 seen by ep1.
+  for (const auto& connection_info : connection_infos_ep1) {
+    const auto& local_candidate = connection_info.local_candidate;
+    if (local_candidate.type() == LOCAL_PORT_TYPE) {
+      EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
+    } else if (local_candidate.type() == STUN_PORT_TYPE) {
+      EXPECT_TRUE(local_candidate.related_address().IsAnyIP());
+    } else if (local_candidate.type() == RELAY_PORT_TYPE) {
+      // The related address of the relay candidate should be equal to the
+      // srflx address. Note that NAT is not configured, hence the following
+      // expectation.
+      EXPECT_EQ(kPublicAddrs[0].ipaddr(),
+                local_candidate.related_address().ipaddr());
+    } else {
+      FAIL();
+    }
+  }
+  // Check the stats of ep1 seen by ep2.
+  for (const auto& connection_info : connection_infos_ep2) {
+    const auto& remote_candidate = connection_info.remote_candidate;
+    if (remote_candidate.type() == LOCAL_PORT_TYPE) {
+      EXPECT_TRUE(remote_candidate.address().IsUnresolvedIP());
+    } else if (remote_candidate.type() == STUN_PORT_TYPE) {
+      EXPECT_TRUE(remote_candidate.related_address().IsAnyIP());
+    } else if (remote_candidate.type() == RELAY_PORT_TYPE) {
+      EXPECT_EQ(kPublicAddrs[0].ipaddr(),
+                remote_candidate.related_address().ipaddr());
+    } else {
+      FAIL();
+    }
+  }
+  DestroyChannels();
+}
+
 }  // namespace cricket
diff --git a/p2p/base/port.cc b/p2p/base/port.cc
index 835e12c..24aed8e 100644
--- a/p2p/base/port.cc
+++ b/p2p/base/port.cc
@@ -1826,14 +1826,13 @@
   stats_.timeout = write_state_ == STATE_WRITE_TIMEOUT;
   stats_.new_connection = !reported_;
   stats_.rtt = rtt_;
-  stats_.local_candidate = local_candidate();
-  stats_.remote_candidate = remote_candidate();
   stats_.key = this;
   stats_.state = state_;
   stats_.priority = priority();
   stats_.nominated = nominated();
   stats_.total_round_trip_time_ms = total_round_trip_time_ms_;
   stats_.current_round_trip_time_ms = current_round_trip_time_ms_;
+  CopyCandidatesToStatsAndSanitizeIfNecessary();
   return stats_;
 }
 
@@ -1910,6 +1909,36 @@
   SignalStateChange(this);
 }
 
+void Connection::CopyCandidatesToStatsAndSanitizeIfNecessary() {
+  auto get_sanitized_copy = [](const Candidate& c) {
+    bool use_hostname_address = c.type() == LOCAL_PORT_TYPE;
+    bool filter_related_address = c.type() == STUN_PORT_TYPE;
+    return c.ToSanitizedCopy(use_hostname_address, filter_related_address);
+  };
+
+  if (port_->Network()->GetMdnsResponder() != nullptr) {
+    // When the mDNS obfuscation of local IPs is enabled, we sanitize local
+    // candidates.
+    stats_.local_candidate = get_sanitized_copy(local_candidate());
+  } else {
+    stats_.local_candidate = local_candidate();
+  }
+
+  if (!remote_candidate().address().hostname().empty()) {
+    // If the remote endpoint signaled us a hostname candidate, we assume it is
+    // supposed to be sanitized in the stats.
+    //
+    // A prflx remote candidate should not have a hostname set.
+    RTC_DCHECK(remote_candidate().type() != PRFLX_PORT_TYPE);
+    // A remote hostname candidate should have a resolved IP before we can form
+    // a candidate pair.
+    RTC_DCHECK(!remote_candidate().address().IsUnresolvedIP());
+    stats_.remote_candidate = get_sanitized_copy(remote_candidate());
+  } else {
+    stats_.remote_candidate = remote_candidate();
+  }
+}
+
 bool Connection::rtt_converged() const {
   return rtt_samples_ > (RTT_RATIO + 1);
 }
diff --git a/p2p/base/port.h b/p2p/base/port.h
index 320ed62..5037377 100644
--- a/p2p/base/port.h
+++ b/p2p/base/port.h
@@ -792,6 +792,8 @@
   void MaybeUpdateLocalCandidate(ConnectionRequest* request,
                                  StunMessage* response);
 
+  void CopyCandidatesToStatsAndSanitizeIfNecessary();
+
   void LogCandidatePairConfig(webrtc::IceCandidatePairConfigType type);
   void LogCandidatePairEvent(webrtc::IceCandidatePairEventType type,
                              uint32_t transaction_id);
diff --git a/p2p/base/portallocator.cc b/p2p/base/portallocator.cc
index 023e90b..3861946 100644
--- a/p2p/base/portallocator.cc
+++ b/p2p/base/portallocator.cc
@@ -91,6 +91,14 @@
     for (const auto& candidate : candidates) {
       CandidateStats candidate_stats(candidate);
       port->GetStunStats(&candidate_stats.stun_stats);
+      bool mdns_obfuscation_enabled =
+          port->Network()->GetMdnsResponder() != nullptr;
+      if (mdns_obfuscation_enabled) {
+        bool use_hostname_address = candidate.type() == LOCAL_PORT_TYPE;
+        bool filter_related_address = candidate.type() == STUN_PORT_TYPE;
+        candidate_stats.candidate = candidate_stats.candidate.ToSanitizedCopy(
+            use_hostname_address, filter_related_address);
+      }
       candidate_stats_list->push_back(std::move(candidate_stats));
     }
   }
diff --git a/p2p/client/basicportallocator.cc b/p2p/client/basicportallocator.cc
index 0c2fef3..5c3f63a 100644
--- a/p2p/client/basicportallocator.cc
+++ b/p2p/client/basicportallocator.cc
@@ -525,14 +525,10 @@
 Candidate BasicPortAllocatorSession::SanitizeCandidate(
     const Candidate& c) const {
   RTC_DCHECK_RUN_ON(network_thread_);
-  Candidate copy = c;
   // If the candidate has a generated hostname, we need to obfuscate its IP
   // address when signaling this candidate.
-  if (!c.address().hostname().empty() && !c.address().IsUnresolvedIP()) {
-    rtc::SocketAddress hostname_only_addr(c.address().hostname(),
-                                          c.address().port());
-    copy.set_address(hostname_only_addr);
-  }
+  bool use_hostname_address =
+      !c.address().hostname().empty() && !c.address().IsUnresolvedIP();
   // If adapter enumeration is disabled or host candidates are disabled,
   // clear the raddr of STUN candidates to avoid local address leakage.
   bool filter_stun_related_address =
@@ -542,12 +538,10 @@
   // If the candidate filter doesn't allow reflexive addresses, empty TURN raddr
   // to avoid reflexive address leakage.
   bool filter_turn_related_address = !(candidate_filter_ & CF_REFLEXIVE);
-  if ((c.type() == STUN_PORT_TYPE && filter_stun_related_address) ||
-      (c.type() == RELAY_PORT_TYPE && filter_turn_related_address)) {
-    copy.set_related_address(
-        rtc::EmptySocketAddressWithFamily(copy.address().family()));
-  }
-  return copy;
+  bool filter_related_address =
+      ((c.type() == STUN_PORT_TYPE && filter_stun_related_address) ||
+       (c.type() == RELAY_PORT_TYPE && filter_turn_related_address));
+  return c.ToSanitizedCopy(use_hostname_address, filter_related_address);
 }
 
 bool BasicPortAllocatorSession::CandidatesAllocationDone() const {
diff --git a/p2p/client/basicportallocator_unittest.cc b/p2p/client/basicportallocator_unittest.cc
index b038e18..2b205e6 100644
--- a/p2p/client/basicportallocator_unittest.cc
+++ b/p2p/client/basicportallocator_unittest.cc
@@ -2264,7 +2264,7 @@
   AddTurnServers(kTurnUdpIntIPv6Addr, kTurnTcpIntIPv6Addr);
 
   ASSERT_EQ(&network_manager_, allocator().network_manager());
-  network_manager_.CreateMdnsResponder();
+  network_manager_.CreateMdnsResponder(rtc::Thread::Current());
   AddInterface(kClientAddr);
   ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
   session_->StartGettingPorts();
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 45e8c71..f24073d 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -6449,7 +6449,8 @@
                                   kBestConnections_IPv6,
                                   kPeerConnectionAddressFamilyCounter_Max);
       } else {
-        RTC_CHECK(0);
+        RTC_CHECK(!local.address().hostname().empty() &&
+                  local.address().IsUnresolvedIP());
       }
 
       return;
diff --git a/pc/peerconnection_integrationtest.cc b/pc/peerconnection_integrationtest.cc
index 8f4ac85..0b817c7 100644
--- a/pc/peerconnection_integrationtest.cc
+++ b/pc/peerconnection_integrationtest.cc
@@ -22,6 +22,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/memory/memory.h"
 #include "api/audio_codecs/builtin_audio_decoder_factory.h"
 #include "api/audio_codecs/builtin_audio_encoder_factory.h"
 #include "api/mediastreaminterface.h"
@@ -78,6 +79,7 @@
 using ::testing::Combine;
 using ::testing::ElementsAre;
 using ::testing::Return;
+using ::testing::NiceMock;
 using ::testing::SetArgPointee;
 using ::testing::UnorderedElementsAreArray;
 using ::testing::Values;
@@ -182,17 +184,6 @@
   cricket::MediaType expected_media_type_;
 };
 
-// Used by PeerConnectionWrapper::OnIceCandidate to allow a test to modify an
-// ICE candidate before it is signaled.
-class IceCandidateReplacerInterface {
- public:
-  virtual ~IceCandidateReplacerInterface() = default;
-  // Return nullptr to drop the candidate (it won't be signaled to the other
-  // side).
-  virtual std::unique_ptr<webrtc::IceCandidateInterface> ReplaceCandidate(
-      const webrtc::IceCandidateInterface*) = 0;
-};
-
 // Helper class that wraps a peer connection, observes it, and can accept
 // signaling messages from another wrapper.
 //
@@ -274,9 +265,8 @@
     remote_offer_handler_ = std::move(handler);
   }
 
-  void SetLocalIceCandidateReplacer(
-      std::unique_ptr<IceCandidateReplacerInterface> replacer) {
-    local_ice_candidate_replacer_ = std::move(replacer);
+  void SetRemoteAsyncResolver(rtc::MockAsyncResolver* resolver) {
+    remote_async_resolver_ = resolver;
   }
 
   // Every ICE connection state in order that has been seen by the observer.
@@ -906,46 +896,32 @@
     EXPECT_EQ(pc()->ice_gathering_state(), new_state);
     ice_gathering_state_history_.push_back(new_state);
   }
-  std::unique_ptr<webrtc::IceCandidateInterface> ReplaceIceCandidate(
-      const webrtc::IceCandidateInterface* candidate) {
-    std::string candidate_string;
-    candidate->ToString(&candidate_string);
-
-    auto owned_candidate =
-        local_ice_candidate_replacer_->ReplaceCandidate(candidate);
-    if (!owned_candidate) {
-      RTC_LOG(LS_INFO) << "LocalIceCandidateReplacer dropped \""
-                       << candidate_string << "\"";
-      return nullptr;
-    }
-    std::string owned_candidate_string;
-    owned_candidate->ToString(&owned_candidate_string);
-    RTC_LOG(LS_INFO) << "LocalIceCandidateReplacer changed \""
-                     << candidate_string << "\" to \"" << owned_candidate_string
-                     << "\"";
-    return owned_candidate;
-  }
   void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override {
     RTC_LOG(LS_INFO) << debug_name_ << ": OnIceCandidate";
 
-    const webrtc::IceCandidateInterface* new_candidate = candidate;
-    std::unique_ptr<webrtc::IceCandidateInterface> owned_candidate;
-    if (local_ice_candidate_replacer_) {
-      owned_candidate = ReplaceIceCandidate(candidate);
-      if (!owned_candidate) {
-        return;  // The candidate was dropped.
+    if (remote_async_resolver_) {
+      const auto& local_candidate = candidate->candidate();
+      const auto& mdns_responder = network()->GetMdnsResponderForTesting();
+      if (local_candidate.address().IsUnresolvedIP()) {
+        RTC_DCHECK(local_candidate.type() == cricket::LOCAL_PORT_TYPE);
+        rtc::SocketAddress resolved_addr(local_candidate.address());
+        const auto resolved_ip = mdns_responder->GetMappedAddressForName(
+            local_candidate.address().hostname());
+        RTC_DCHECK(!resolved_ip.IsNil());
+        resolved_addr.SetResolvedIP(resolved_ip);
+        EXPECT_CALL(*remote_async_resolver_, GetResolvedAddress(_, _))
+            .WillOnce(DoAll(SetArgPointee<1>(resolved_addr), Return(true)));
+        EXPECT_CALL(*remote_async_resolver_, Destroy(_));
       }
-      new_candidate = owned_candidate.get();
     }
 
     std::string ice_sdp;
-    EXPECT_TRUE(new_candidate->ToString(&ice_sdp));
+    EXPECT_TRUE(candidate->ToString(&ice_sdp));
     if (signaling_message_receiver_ == nullptr || !signal_ice_candidates_) {
       // Remote party may be deleted.
       return;
     }
-    SendIceMessage(new_candidate->sdp_mid(), new_candidate->sdp_mline_index(),
-                   ice_sdp);
+    SendIceMessage(candidate->sdp_mid(), candidate->sdp_mline_index(), ice_sdp);
   }
   void OnDataChannel(
       rtc::scoped_refptr<DataChannelInterface> data_channel) override {
@@ -989,7 +965,7 @@
   std::function<void(cricket::SessionDescription*)> received_sdp_munger_;
   std::function<void(cricket::SessionDescription*)> generated_sdp_munger_;
   std::function<void()> remote_offer_handler_;
-  std::unique_ptr<IceCandidateReplacerInterface> local_ice_candidate_replacer_;
+  rtc::MockAsyncResolver* remote_async_resolver_ = nullptr;
   rtc::scoped_refptr<DataChannelInterface> data_channel_;
   std::unique_ptr<MockDataChannelObserver> data_observer_;
 
@@ -1448,6 +1424,15 @@
     return old;
   }
 
+  void SetPortAllocatorFlags(uint32_t caller_flags, uint32_t callee_flags) {
+    network_thread()->Invoke<void>(
+        RTC_FROM_HERE, rtc::Bind(&cricket::PortAllocator::set_flags,
+                                 caller()->port_allocator(), caller_flags));
+    network_thread()->Invoke<void>(
+        RTC_FROM_HERE, rtc::Bind(&cricket::PortAllocator::set_flags,
+                                 callee()->port_allocator(), callee_flags));
+  }
+
   rtc::FirewallSocketServer* firewall() const { return fss_.get(); }
 
   // Expects the provided number of new frames to be received within
@@ -3637,81 +3622,48 @@
                  callee()->ice_connection_state(), kDefaultTimeout);
 }
 
-// Replaces the first candidate with a static address and configures a
-// MockAsyncResolver to return the replaced address the first time the static
-// address is resolved. Candidates past the first will not be signaled.
-class ReplaceFirstCandidateAddressDropOthers final
-    : public IceCandidateReplacerInterface {
- public:
-  ReplaceFirstCandidateAddressDropOthers(
-      const SocketAddress& new_address,
-      rtc::MockAsyncResolver* mock_async_resolver)
-      : mock_async_resolver_(mock_async_resolver), new_address_(new_address) {
-    RTC_DCHECK(mock_async_resolver);
-  }
+constexpr int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
+                                cricket::PORTALLOCATOR_DISABLE_RELAY |
+                                cricket::PORTALLOCATOR_DISABLE_TCP;
 
-  std::unique_ptr<webrtc::IceCandidateInterface> ReplaceCandidate(
-      const webrtc::IceCandidateInterface* candidate) override {
-    if (replaced_candidate_) {
-      return nullptr;
-    }
-
-    replaced_candidate_ = true;
-    cricket::Candidate new_candidate(candidate->candidate());
-    new_candidate.set_address(new_address_);
-    EXPECT_CALL(*mock_async_resolver_, GetResolvedAddress(_, _))
-        .WillOnce(DoAll(SetArgPointee<1>(candidate->candidate().address()),
-                        Return(true)));
-    EXPECT_CALL(*mock_async_resolver_, Destroy(_));
-    return webrtc::CreateIceCandidate(
-        candidate->sdp_mid(), candidate->sdp_mline_index(), new_candidate);
-  }
-
- private:
-  rtc::MockAsyncResolver* mock_async_resolver_;
-  SocketAddress new_address_;
-  bool replaced_candidate_ = false;
-};
-
-// Drops all candidates before they are signaled.
-class DropAllCandidates final : public IceCandidateReplacerInterface {
- public:
-  std::unique_ptr<webrtc::IceCandidateInterface> ReplaceCandidate(
-      const webrtc::IceCandidateInterface*) override {
-    return nullptr;
-  }
-};
-
-// Replace the first caller ICE candidate IP with a fake hostname and drop the
-// other candidates. Drop all candidates on the callee side (to avoid a prflx
-// connection). Use a mock resolver to resolve the hostname back to the original
-// IP on the callee side and check that the ice connection connects.
+// Use a mock resolver to resolve the hostname back to the original IP on both
+// sides and check that the ICE connection connects.
 TEST_P(PeerConnectionIntegrationTest,
        IceStatesReachCompletionWithRemoteHostname) {
-  webrtc::MockAsyncResolverFactory* callee_mock_async_resolver_factory;
-  {
-    auto resolver_factory =
-        absl::make_unique<webrtc::MockAsyncResolverFactory>();
-    callee_mock_async_resolver_factory = resolver_factory.get();
-    webrtc::PeerConnectionDependencies callee_deps(nullptr);
-    callee_deps.async_resolver_factory = std::move(resolver_factory);
-
-    ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndDeps(
-        RTCConfiguration(), webrtc::PeerConnectionDependencies(nullptr),
-        RTCConfiguration(), std::move(callee_deps)));
-  }
-
-  rtc::MockAsyncResolver mock_async_resolver;
+  auto caller_resolver_factory =
+      absl::make_unique<NiceMock<webrtc::MockAsyncResolverFactory>>();
+  auto callee_resolver_factory =
+      absl::make_unique<NiceMock<webrtc::MockAsyncResolverFactory>>();
+  NiceMock<rtc::MockAsyncResolver> callee_async_resolver;
+  NiceMock<rtc::MockAsyncResolver> caller_async_resolver;
 
   // This also verifies that the injected AsyncResolverFactory is used by
   // P2PTransportChannel.
-  EXPECT_CALL(*callee_mock_async_resolver_factory, Create())
-      .WillOnce(Return(&mock_async_resolver));
-  caller()->SetLocalIceCandidateReplacer(
-      absl::make_unique<ReplaceFirstCandidateAddressDropOthers>(
-          SocketAddress("a.b", 10000), &mock_async_resolver));
-  callee()->SetLocalIceCandidateReplacer(
-      absl::make_unique<DropAllCandidates>());
+  EXPECT_CALL(*caller_resolver_factory, Create())
+      .WillOnce(Return(&caller_async_resolver));
+  webrtc::PeerConnectionDependencies caller_deps(nullptr);
+  caller_deps.async_resolver_factory = std::move(caller_resolver_factory);
+
+  EXPECT_CALL(*callee_resolver_factory, Create())
+      .WillOnce(Return(&callee_async_resolver));
+  webrtc::PeerConnectionDependencies callee_deps(nullptr);
+  callee_deps.async_resolver_factory = std::move(callee_resolver_factory);
+
+  PeerConnectionInterface::RTCConfiguration config;
+  config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle;
+  config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
+
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndDeps(
+      config, std::move(caller_deps), config, std::move(callee_deps)));
+
+  caller()->SetRemoteAsyncResolver(&callee_async_resolver);
+  callee()->SetRemoteAsyncResolver(&caller_async_resolver);
+
+  // Enable hostname candidates with mDNS names.
+  caller()->network()->CreateMdnsResponder(network_thread());
+  callee()->network()->CreateMdnsResponder(network_thread());
+
+  SetPortAllocatorFlags(kOnlyLocalPorts, kOnlyLocalPorts);
 
   ConnectFakeSignaling();
   caller()->AddAudioVideoTracks();
@@ -3746,14 +3698,8 @@
   }
 
   void SetPortAllocatorFlags() {
-    network_thread()->Invoke<void>(
-        RTC_FROM_HERE,
-        rtc::Bind(&cricket::PortAllocator::set_flags,
-                  caller()->port_allocator(), port_allocator_flags_));
-    network_thread()->Invoke<void>(
-        RTC_FROM_HERE,
-        rtc::Bind(&cricket::PortAllocator::set_flags,
-                  callee()->port_allocator(), port_allocator_flags_));
+    PeerConnectionIntegrationBaseTest::SetPortAllocatorFlags(
+        port_allocator_flags_, port_allocator_flags_);
   }
 
   std::vector<SocketAddress> CallerAddresses() {
diff --git a/rtc_base/fake_mdns_responder.h b/rtc_base/fake_mdns_responder.h
index 1e60a5d..17a7678 100644
--- a/rtc_base/fake_mdns_responder.h
+++ b/rtc_base/fake_mdns_responder.h
@@ -50,6 +50,15 @@
                                [callback, result]() { callback(result); });
   }
 
+  rtc::IPAddress GetMappedAddressForName(const std::string& name) const {
+    for (const auto& addr_name_pair : addr_name_map_) {
+      if (addr_name_pair.second == name) {
+        return addr_name_pair.first;
+      }
+    }
+    return rtc::IPAddress();
+  }
+
  private:
   uint32_t next_available_id_ = 0;
   std::map<rtc::IPAddress, std::string> addr_name_map_;
diff --git a/rtc_base/fakenetwork.h b/rtc_base/fakenetwork.h
index fc690d9..98c6217 100644
--- a/rtc_base/fakenetwork.h
+++ b/rtc_base/fakenetwork.h
@@ -82,20 +82,25 @@
   // MessageHandler interface.
   virtual void OnMessage(Message* msg) { DoUpdateNetworks(); }
 
-  void CreateMdnsResponder() {
+  void CreateMdnsResponder(rtc::Thread* network_thread) {
     if (mdns_responder_ == nullptr) {
       mdns_responder_ =
-          absl::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current());
+          absl::make_unique<webrtc::FakeMdnsResponder>(network_thread);
     }
   }
 
   using NetworkManagerBase::set_enumeration_permission;
   using NetworkManagerBase::set_default_local_addresses;
 
+  // rtc::NetworkManager override.
   webrtc::MdnsResponderInterface* GetMdnsResponder() const override {
     return mdns_responder_.get();
   }
 
+  webrtc::FakeMdnsResponder* GetMdnsResponderForTesting() const {
+    return mdns_responder_.get();
+  }
+
  private:
   void DoUpdateNetworks() {
     if (start_count_ == 0)