Add MDnsResponderInterface and obfuscate local IP addresses in gathering.

MDnsResponderInterface can be accessed by rtc::NetworkManager to
generate mDNS hostnames for local IP addresses, so that the addresses of
ICE host candidates are obfuscated in gathering whenever an mDNS
responder is present. The mDNS responder will handle incoming mDNS
queries about the generated mDNS hostnames, e.g. queries received from
the AsyncResolverInterface of the remote ICE endpoint.

Bug: webrtc:9605
Change-Id: Ib9e77427327b3d1fabdb1f3854d5e8457db40375
Reviewed-on: https://webrtc-review.googlesource.com/97881
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Qingsi Wang <qingsi@google.com>
Cr-Commit-Position: refs/heads/master@{#24714}
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index 74a124c..0f08990 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -91,6 +91,7 @@
     "../rtc_base:rtc_base",
     "../rtc_base:safe_minmax",
     "../rtc_base:stringutils",
+    "../rtc_base:weak_ptr",
     "../rtc_base/third_party/base64",
     "../rtc_base/third_party/sigslot",
     "../system_wrappers:field_trial_api",
diff --git a/p2p/base/mockasyncresolver.h b/p2p/base/mockasyncresolver.h
index 6201376..0ca6e8b 100644
--- a/p2p/base/mockasyncresolver.h
+++ b/p2p/base/mockasyncresolver.h
@@ -17,13 +17,19 @@
 
 namespace rtc {
 
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+
 class MockAsyncResolver : public AsyncResolverInterface {
  public:
-  MockAsyncResolver() = default;
+  MockAsyncResolver() {
+    ON_CALL(*this, Start(_)).WillByDefault(InvokeWithoutArgs([this] {
+      SignalDone(this);
+    }));
+  }
   ~MockAsyncResolver() = default;
 
-  void Start(const rtc::SocketAddress& addr) { SignalDone(this); }
-
+  MOCK_METHOD1(Start, void(const rtc::SocketAddress&));
   MOCK_CONST_METHOD2(GetResolvedAddress, bool(int family, SocketAddress* addr));
   MOCK_CONST_METHOD0(GetError, int());
 
diff --git a/p2p/base/p2ptransportchannel.cc b/p2p/base/p2ptransportchannel.cc
index 9a4bb89..e860b29 100644
--- a/p2p/base/p2ptransportchannel.cc
+++ b/p2p/base/p2ptransportchannel.cc
@@ -807,7 +807,24 @@
   // Port has received a valid stun packet from an address that no Connection
   // is currently available for. See if we already have a candidate with the
   // address. If it isn't we need to create new candidate for it.
-
+  //
+  // TODO(qingsi): There is a caveat of the logic below if we have remote
+  // candidates with hostnames. We could create a prflx candidate that is
+  // identical to a host candidate that are currently in the process of name
+  // resolution. We would not have a duplicate candidate since when adding the
+  // resolved host candidate, FinishingAddingRemoteCandidate does
+  // MaybeUpdatePeerReflexiveCandidate, and the prflx candidate would be updated
+  // to a host candidate. As a result, for a brief moment we would have a prflx
+  // candidate showing a private IP address, though we do not signal prflx
+  // candidates to applications and we could obfuscate the IP addresses of prflx
+  // candidates in P2PTransportChannel::GetStats. The difficulty of preventing
+  // creating the prflx from the beginning is that we do not have a reliable way
+  // to claim two candidates are identical without the address information. If
+  // we always pause the addition of a prflx candidate when there is ongoing
+  // name resolution and dedup after we have a resolved address, we run into the
+  // risk of losing/delaying the addition of a non-identical candidate that
+  // could be the only way to have a connection, if the resolution never
+  // completes or is significantly delayed.
   const Candidate* candidate = nullptr;
   for (const Candidate& c : remote_candidates_) {
     if (c.username() == remote_username && c.address() == address &&
diff --git a/p2p/base/p2ptransportchannel_unittest.cc b/p2p/base/p2ptransportchannel_unittest.cc
index 2da0fc3..fadcb0a 100644
--- a/p2p/base/p2ptransportchannel_unittest.cc
+++ b/p2p/base/p2ptransportchannel_unittest.cc
@@ -44,6 +44,9 @@
 using rtc::SocketAddress;
 using ::testing::_;
 using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::InvokeWithoutArgs;
+using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::SetArgPointee;
 
@@ -760,20 +763,6 @@
     }
   }
 
-  SocketAddress ReplaceSavedCandidateIpWithHostname(
-      int endpoint,
-      const SocketAddress& hostname_address) {
-    Endpoint* ed = GetEndpoint(endpoint);
-
-    RTC_CHECK(1 == ed->saved_candidates_.size());
-    auto& candidates = ed->saved_candidates_[0];
-    RTC_CHECK(1 == candidates->candidates.size());
-    auto& candidate = candidates->candidates[0];
-    SocketAddress ip_address = candidate.address();
-    candidate.set_address(hostname_address);
-    return ip_address;
-  }
-
   void ResumeCandidates(int endpoint) {
     Endpoint* ed = GetEndpoint(endpoint);
     for (auto& candidate : ed->saved_candidates_) {
@@ -4596,53 +4585,185 @@
   EXPECT_FALSE(candidate.address().IsUnresolvedIP());
 }
 
+// Test that if we signal a hostname candidate after the remote endpoint
+// discovers a prflx remote candidate with the same underlying IP address, the
+// prflx candidate is updated to a host candidate after the name resolution is
+// done.
 TEST_F(P2PTransportChannelTest,
-       PeerReflexiveCandidateBeforeSignalingWithHostname) {
+       PeerReflexiveCandidateBeforeSignalingWithMDnsName) {
   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 only gather host candidates with addresses
+  // kPublicAddrs[0] and kPublicAddrs[1], respectively.
   ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
-  // Emulate no remote parameters coming in.
-  set_remote_ice_parameter_source(FROM_CANDIDATE);
-  GetEndpoint(0)->async_resolver_factory_ = &mock_async_resolver_factory;
+  // ICE parameter will be set up when creating the channels.
+  set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+  GetEndpoint(0)->network_manager_.CreateMDnsResponder();
+  GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
   CreateChannels();
-  // Only have remote parameters come in for ep2, not ep1.
-  ep2_ch1()->SetRemoteIceParameters(kIceParams[0]);
-
-  // Pause sending ep2's candidates to ep1 until ep1 receives the peer reflexive
-  // candidate.
+  // Pause sending candidates from both endpoints until we find out what port
+  // number is assgined to ep1's host candidate.
+  PauseCandidates(0);
   PauseCandidates(1);
-
-  // Wait until the callee becomes writable to make sure that a ping request is
-  // received by the caller before his remote ICE credentials are set.
-  ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
-  ep1_ch1()->SetRemoteIceParameters(kIceParams[1]);
-  // The caller should have the selected connection connected to the peer
-  // reflexive candidate.
+  ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+  ASSERT_EQ(1u, GetEndpoint(0)->saved_candidates_[0]->candidates.size());
+  const auto& local_candidate =
+      GetEndpoint(0)->saved_candidates_[0]->candidates[0];
+  // The IP address of ep1's host candidate should be obfuscated.
+  EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
+  // This is the underlying private IP address of the same candidate at ep1.
+  const auto local_address = rtc::SocketAddress(
+      kPublicAddrs[0].ipaddr(), local_candidate.address().port());
+  // Let ep2 signal its candidate to ep1. ep1 should form a candidate
+  // pair and start to ping. After receiving the ping, ep2 discovers a prflx
+  // remote candidate and form a candidate pair as well.
+  ResumeCandidates(1);
+  ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout);
+  // ep2 should have the selected connection connected to the prflx remote
+  // candidate.
   const Connection* selected_connection = nullptr;
   ASSERT_TRUE_WAIT(
-      (selected_connection = ep1_ch1()->selected_connection()) != nullptr,
+      (selected_connection = ep2_ch1()->selected_connection()) != nullptr,
       kMediumTimeout);
   EXPECT_EQ(PRFLX_PORT_TYPE, selected_connection->remote_candidate().type());
-  EXPECT_EQ(kIceUfrag[1], selected_connection->remote_candidate().username());
-  EXPECT_EQ(kIcePwd[1], selected_connection->remote_candidate().password());
-
-  SocketAddress hostname_address("fake.hostname", 12345);
-  SocketAddress ip_address =
-      ReplaceSavedCandidateIpWithHostname(1, hostname_address);
-  EXPECT_CALL(mock_async_resolver, GetError()).WillOnce(Return(0));
-  EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(ip_address), Return(true)));
+  EXPECT_EQ(kIceUfrag[0], selected_connection->remote_candidate().username());
+  EXPECT_EQ(kIcePwd[0], selected_connection->remote_candidate().password());
+  // Set expectation before ep1 signals a hostname candidate.
+  {
+    InSequence sequencer;
+    EXPECT_CALL(mock_async_resolver, Start(_));
+    EXPECT_CALL(mock_async_resolver, GetError()).WillOnce(Return(0));
+    // Let the mock resolver of ep2 receives the correct resolution.
+    EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
+        .WillOnce(DoAll(SetArgPointee<1>(local_address), Return(true)));
+  }
   EXPECT_CALL(mock_async_resolver, Destroy(_));
-
-  ResumeCandidates(1);
-  // Verify ep1's selected connection is updated to use the 'local' candidate.
+  ResumeCandidates(0);
+  // Verify ep2's selected connection is updated to use the 'local' candidate.
   EXPECT_EQ_WAIT(LOCAL_PORT_TYPE,
-                 ep1_ch1()->selected_connection()->remote_candidate().type(),
+                 ep2_ch1()->selected_connection()->remote_candidate().type(),
                  kMediumTimeout);
-  EXPECT_EQ(selected_connection, ep1_ch1()->selected_connection());
+  EXPECT_EQ(selected_connection, ep2_ch1()->selected_connection());
+
+  DestroyChannels();
+}
+
+// Test that if we discover a prflx candidate during the process of name
+// resolution for a remote hostname candidate, we update the prflx candidate to
+// a host candidate if the hostname candidate turns out to have the same IP
+// address after the resolution completes.
+TEST_F(P2PTransportChannelTest,
+       PeerReflexiveCandidateDuringResolvingHostCandidateWithMDnsName) {
+  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 only gather host candidates with addresses
+  // kPublicAddrs[0] and kPublicAddrs[1], respectively.
+  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(1)->async_resolver_factory_ = &mock_async_resolver_factory;
+  CreateChannels();
+  // Pause sending candidates from both endpoints until we find out what port
+  // number is assgined to ep1's host candidate.
+  PauseCandidates(0);
+  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 =
+      GetEndpoint(0)->saved_candidates_[0]->candidates[0];
+  // The IP address of ep1's host candidate should be obfuscated.
+  EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
+  // This is the underlying private IP address of the same candidate at ep1.
+  const auto local_address = rtc::SocketAddress(
+      kPublicAddrs[0].ipaddr(), local_candidate.address().port());
+  bool mock_async_resolver_started = false;
+  // Not signaling done yet, and only make sure we are in the process of
+  // resolution.
+  EXPECT_CALL(mock_async_resolver, Start(_))
+      .WillOnce(InvokeWithoutArgs([&mock_async_resolver_started]() {
+        mock_async_resolver_started = true;
+      }));
+  // Let ep1 signal its hostname candidate to ep2.
+  ResumeCandidates(0);
+  EXPECT_TRUE_WAIT(mock_async_resolver_started, kMediumTimeout);
+  // Now that ep2 is in the process of resolving the hostname candidate signaled
+  // by ep1. Let ep2 signal its host candidate with an IP address to ep1, so
+  // that ep1 can form a candidate pair, select it and start to ping ep2.
+  ResumeCandidates(1);
+  ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout);
+  // Let the mock resolver of ep2 receives the correct resolution.
+  EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(local_address), Return(true)));
+  // Upon receiving a ping from ep1, ep2 adds a prflx candidate from the
+  // unknown address and establishes a connection.
+  //
+  // There is a caveat in our implementation associated with this expectation.
+  // See the big comment in P2PTransportChannel::OnUnknownAddress.
+  ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
+  EXPECT_EQ(PRFLX_PORT_TYPE,
+            ep2_ch1()->selected_connection()->remote_candidate().type());
+  // ep2 should also be able resolve the hostname candidate. The resolved remote
+  // host candidate should be merged with the prflx remote candidate.
+  mock_async_resolver.SignalDone(&mock_async_resolver);
+  EXPECT_EQ_WAIT(LOCAL_PORT_TYPE,
+                 ep2_ch1()->selected_connection()->remote_candidate().type(),
+                 kMediumTimeout);
+  EXPECT_EQ(1u, ep2_ch1()->remote_candidates().size());
+
+  DestroyChannels();
+}
+
+// Test that if we only gather and signal a host candidate, the IP address of
+// which is obfuscated by an mDNS name, and if the peer can complete the name
+// resolution with the correct IP address, we can have a p2p connection.
+TEST_F(P2PTransportChannelTest, CanConnectWithHostCandidateWithMDnsName) {
+  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 only gather host candidates with addresses
+  // kPublicAddrs[0] and kPublicAddrs[1], respectively.
+  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(1)->async_resolver_factory_ = &mock_async_resolver_factory;
+  CreateChannels();
+  // Pause sending candidates from both endpoints until we find out what port
+  // number is assgined to ep1's host candidate.
+  PauseCandidates(0);
+  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 =
+      GetEndpoint(0)->saved_candidates_[0]->candidates[0];
+  // The IP address of ep1's host candidate should be obfuscated.
+  EXPECT_TRUE(local_candidate.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());
+
+  EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(local_address), Return(true)));
+  // Let ep1 signal its hostname candidate to ep2.
+  ResumeCandidates(0);
+
+  // We should be able to receive a ping from ep2 and establish a connection
+  // with a peer reflexive candidate from ep2.
+  ASSERT_TRUE_WAIT((ep1_ch1()->selected_connection()) != nullptr,
+                   kMediumTimeout);
+  EXPECT_EQ(PRFLX_PORT_TYPE,
+            ep1_ch1()->selected_connection()->remote_candidate().type());
+
   DestroyChannels();
 }
 
diff --git a/p2p/base/port.cc b/p2p/base/port.cc
index 8ab33a3..0ba9bb3 100644
--- a/p2p/base/port.cc
+++ b/p2p/base/port.cc
@@ -22,6 +22,7 @@
 #include "rtc_base/crc32.h"
 #include "rtc_base/helpers.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/mdns_responder_interface.h"
 #include "rtc_base/messagedigest.h"
 #include "rtc_base/network.h"
 #include "rtc_base/numerics/safe_minmax.h"
@@ -270,7 +271,8 @@
       enable_port_packets_(false),
       ice_role_(ICEROLE_UNKNOWN),
       tiebreaker_(0),
-      shared_socket_(true) {
+      shared_socket_(true),
+      weak_factory_(this) {
   Construct();
 }
 
@@ -297,7 +299,8 @@
       enable_port_packets_(false),
       ice_role_(ICEROLE_UNKNOWN),
       tiebreaker_(0),
-      shared_socket_(false) {
+      shared_socket_(false),
+      weak_factory_(this) {
   RTC_DCHECK(factory_ != NULL);
   Construct();
 }
@@ -411,7 +414,7 @@
                       uint32_t type_preference,
                       uint32_t relay_preference,
                       const std::string& url,
-                      bool final) {
+                      bool is_final) {
   if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
     RTC_DCHECK(!tcptype.empty());
   }
@@ -426,12 +429,41 @@
   c.set_tcptype(tcptype);
   c.set_network_name(network_->name());
   c.set_network_type(network_->type());
-  c.set_related_address(related_address);
   c.set_url(url);
+  // TODO(bugs.webrtc.org/9723): Use a config to control the feature of IP
+  // handling with mDNS.
+  if (network_->GetMDnsResponder() != nullptr) {
+    // Obfuscate the IP address of a host candidates by an mDNS hostname.
+    if (type == LOCAL_PORT_TYPE) {
+      auto weak_ptr = weak_factory_.GetWeakPtr();
+      auto callback = [weak_ptr, c, is_final](const rtc::IPAddress& addr,
+                                              const std::string& name) mutable {
+        RTC_DCHECK(c.address().ipaddr() == addr);
+        rtc::SocketAddress hostname_address(name, c.address().port());
+        c.set_address(hostname_address);
+        RTC_DCHECK(c.related_address() == rtc::SocketAddress());
+        if (weak_ptr != nullptr) {
+          weak_ptr->FinishAddingAddress(c, is_final);
+        }
+      };
+      network_->GetMDnsResponder()->CreateNameForAddress(c.address().ipaddr(),
+                                                         callback);
+      return;
+    }
+    // For other types of candidates, the related address should be set to
+    // 0.0.0.0 or ::0.
+    c.set_related_address(rtc::SocketAddress());
+  } else {
+    c.set_related_address(related_address);
+  }
+  FinishAddingAddress(c, is_final);
+}
+
+void Port::FinishAddingAddress(const Candidate& c, bool is_final) {
   candidates_.push_back(c);
   SignalCandidateReady(this, c);
 
-  if (final) {
+  if (is_final) {
     SignalPortComplete(this);
   }
 }
diff --git a/p2p/base/port.h b/p2p/base/port.h
index d881fb8..151a4a3 100644
--- a/p2p/base/port.h
+++ b/p2p/base/port.h
@@ -39,6 +39,7 @@
 #include "rtc_base/socketaddress.h"
 #include "rtc_base/third_party/sigslot/sigslot.h"
 #include "rtc_base/thread.h"
+#include "rtc_base/weak_ptr.h"
 
 namespace cricket {
 
@@ -403,7 +404,9 @@
                   uint32_t type_preference,
                   uint32_t relay_preference,
                   const std::string& url,
-                  bool final);
+                  bool is_final);
+
+  void FinishAddingAddress(const Candidate& c, bool is_final);
 
   // Adds the given connection to the map keyed by the remote candidate address.
   // If an existing connection has the same address, the existing one will be
@@ -488,6 +491,8 @@
   State state_ = State::INIT;
   int64_t last_time_all_connections_removed_ = 0;
 
+  rtc::WeakPtrFactory<Port> weak_factory_;
+
   friend class Connection;
 };
 
diff --git a/p2p/client/basicportallocator_unittest.cc b/p2p/client/basicportallocator_unittest.cc
index b49be8a..2b6dd3a 100644
--- a/p2p/client/basicportallocator_unittest.cc
+++ b/p2p/client/basicportallocator_unittest.cc
@@ -2246,4 +2246,53 @@
                    static_cast<int>(IceRegatheringReason::NETWORK_CHANGE)));
 }
 
+// Test that when an mDNS responder is present, the local address of a host
+// candidate is masked by an mDNS hostname and the related address of any other
+// type of candidates is set to 0.0.0.0 or ::0.
+TEST_F(BasicPortAllocatorTest, HostCandidateAddressIsReplacedByHostname) {
+  // Default config uses GTURN and no NAT, so replace that with the
+  // desired setup (NAT, STUN server, TURN server, UDP/TCP).
+  ResetWithStunServerAndNat(kStunAddr);
+  turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+  AddTurnServers(kTurnUdpIntAddr, kTurnTcpIntAddr);
+  AddTurnServers(kTurnUdpIntIPv6Addr, kTurnTcpIntIPv6Addr);
+
+  ASSERT_EQ(&network_manager_, allocator().network_manager());
+  network_manager_.CreateMDnsResponder();
+  AddInterface(kClientAddr);
+  ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
+                             kDefaultAllocationTimeout, fake_clock);
+  EXPECT_EQ(5u, candidates_.size());
+  int num_host_udp_candidates = 0;
+  int num_host_tcp_candidates = 0;
+  int num_srflx_candidates = 0;
+  int num_relay_candidates = 0;
+  for (const auto& candidate : candidates_) {
+    if (candidate.type() == LOCAL_PORT_TYPE) {
+      EXPECT_TRUE(candidate.address().IsUnresolvedIP());
+      if (candidate.protocol() == UDP_PROTOCOL_NAME) {
+        ++num_host_udp_candidates;
+      } else {
+        ++num_host_tcp_candidates;
+      }
+    } else {
+      EXPECT_NE(PRFLX_PORT_TYPE, candidate.type());
+      // The related address should be set to 0.0.0.0 or ::0 for srflx and
+      // relay candidates.
+      EXPECT_EQ(rtc::SocketAddress(), candidate.related_address());
+      if (candidate.type() == STUN_PORT_TYPE) {
+        ++num_srflx_candidates;
+      } else {
+        ++num_relay_candidates;
+      }
+    }
+  }
+  EXPECT_EQ(1, num_host_udp_candidates);
+  EXPECT_EQ(1, num_host_tcp_candidates);
+  EXPECT_EQ(1, num_srflx_candidates);
+  EXPECT_EQ(2, num_relay_candidates);
+}
+
 }  // namespace cricket
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
index a642d9e..009671c 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -790,6 +790,7 @@
     "ipaddress.cc",
     "ipaddress.h",
     "keep_ref_until_done.h",
+    "mdns_responder_interface.h",
     "messagedigest.cc",
     "messagedigest.h",
     "messagehandler.cc",
@@ -1000,6 +1001,7 @@
     # included by multiple targets below.
     "cpu_time.cc",
     "cpu_time.h",
+    "fake_mdns_responder.h",
     "fakeclock.cc",
     "fakeclock.h",
     "fakenetwork.h",
diff --git a/rtc_base/fake_mdns_responder.h b/rtc_base/fake_mdns_responder.h
new file mode 100644
index 0000000..32d69ba
--- /dev/null
+++ b/rtc_base/fake_mdns_responder.h
@@ -0,0 +1,56 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef RTC_BASE_FAKE_MDNS_RESPONDER_H_
+#define RTC_BASE_FAKE_MDNS_RESPONDER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "rtc_base/mdns_responder_interface.h"
+
+#include "rtc_base/helpers.h"
+
+namespace webrtc {
+
+class FakeMDnsResponder : public MDnsResponderInterface {
+ public:
+  FakeMDnsResponder() = default;
+  ~FakeMDnsResponder() = default;
+
+  void CreateNameForAddress(const rtc::IPAddress& addr,
+                            NameCreatedCallback callback) override {
+    std::string name;
+    if (addr_name_map_.find(addr) != addr_name_map_.end()) {
+      name = addr_name_map_[addr];
+    } else {
+      name = std::to_string(next_available_id_++) + ".local";
+      addr_name_map_[addr] = name;
+    }
+    callback(addr, name);
+  }
+  void RemoveNameForAddress(const rtc::IPAddress& addr,
+                            NameRemovedCallback callback) override {
+    auto it = addr_name_map_.find(addr);
+    if (it != addr_name_map_.end()) {
+      addr_name_map_.erase(it);
+    }
+    callback(it != addr_name_map_.end());
+  }
+
+ private:
+  uint32_t next_available_id_ = 0;
+  std::map<rtc::IPAddress, std::string> addr_name_map_;
+};
+
+}  // namespace webrtc
+
+#endif  // RTC_BASE_FAKE_MDNS_RESPONDER_H_
diff --git a/rtc_base/fakenetwork.h b/rtc_base/fakenetwork.h
index 8ed1164..d5426a3 100644
--- a/rtc_base/fakenetwork.h
+++ b/rtc_base/fakenetwork.h
@@ -16,6 +16,9 @@
 #include <utility>
 #include <vector>
 
+#include "absl/memory/memory.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/fake_mdns_responder.h"
 #include "rtc_base/messagehandler.h"
 #include "rtc_base/network.h"
 #include "rtc_base/socketaddress.h"
@@ -79,9 +82,19 @@
   // MessageHandler interface.
   virtual void OnMessage(Message* msg) { DoUpdateNetworks(); }
 
+  void CreateMDnsResponder() {
+    if (mdns_responder_ == nullptr) {
+      mdns_responder_ = absl::make_unique<webrtc::FakeMDnsResponder>();
+    }
+  }
+
   using NetworkManagerBase::set_enumeration_permission;
   using NetworkManagerBase::set_default_local_addresses;
 
+  webrtc::MDnsResponderInterface* GetMDnsResponder() const override {
+    return mdns_responder_.get();
+  }
+
  private:
   void DoUpdateNetworks() {
     if (start_count_ == 0)
@@ -117,6 +130,8 @@
 
   IPAddress default_local_ipv4_address_;
   IPAddress default_local_ipv6_address_;
+
+  std::unique_ptr<webrtc::FakeMDnsResponder> mdns_responder_;
 };
 
 }  // namespace rtc
diff --git a/rtc_base/mdns_responder_interface.h b/rtc_base/mdns_responder_interface.h
new file mode 100644
index 0000000..9dbaf56
--- /dev/null
+++ b/rtc_base/mdns_responder_interface.h
@@ -0,0 +1,51 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef RTC_BASE_MDNS_RESPONDER_INTERFACE_H_
+#define RTC_BASE_MDNS_RESPONDER_INTERFACE_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "rtc_base/ipaddress.h"
+#include "rtc_base/socketaddress.h"
+
+namespace webrtc {
+
+// Defines an mDNS responder that can be used in ICE candidate gathering, where
+// the local IP addresses of host candidates are obfuscated by mDNS hostnames.
+class MDnsResponderInterface {
+ public:
+  using NameCreatedCallback =
+      std::function<void(const rtc::IPAddress&, const std::string&)>;
+  using NameRemovedCallback = std::function<void(bool)>;
+
+  MDnsResponderInterface() = default;
+  virtual ~MDnsResponderInterface() = default;
+
+  // Asynchronously creates a type-4 UUID hostname for an IP address. The
+  // created name should be given to |callback| with the address that it
+  // represents.
+  virtual void CreateNameForAddress(const rtc::IPAddress& addr,
+                                    NameCreatedCallback callback) = 0;
+  // Removes the name mapped to the given address if there is such an
+  // name-address mapping previously created via CreateNameForAddress. The
+  // result of whether an associated name-address mapping is removed should be
+  // given to |callback|.
+  virtual void RemoveNameForAddress(const rtc::IPAddress& addr,
+                                    NameRemovedCallback callback) = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // RTC_BASE_MDNS_RESPONDER_INTERFACE_H_
diff --git a/rtc_base/network.cc b/rtc_base/network.cc
index 01b84a6..f0d402e 100644
--- a/rtc_base/network.cc
+++ b/rtc_base/network.cc
@@ -260,6 +260,10 @@
   return false;
 }
 
+webrtc::MDnsResponderInterface* NetworkManager::GetMDnsResponder() const {
+  return nullptr;
+}
+
 NetworkManagerBase::NetworkManagerBase()
     : enumeration_permission_(NetworkManager::ENUMERATION_ALLOWED),
       ipv6_enabled_(true) {}
@@ -282,6 +286,7 @@
         new rtc::Network("any", "any", ipv4_any_address, 0, ADAPTER_TYPE_ANY));
     ipv4_any_address_network_->set_default_local_address_provider(this);
     ipv4_any_address_network_->AddIP(ipv4_any_address);
+    ipv4_any_address_network_->SetMDnsResponder(GetMDnsResponder());
   }
   networks->push_back(ipv4_any_address_network_.get());
 
@@ -292,6 +297,7 @@
           "any", "any", ipv6_any_address, 0, ADAPTER_TYPE_ANY));
       ipv6_any_address_network_->set_default_local_address_provider(this);
       ipv6_any_address_network_->AddIP(ipv6_any_address);
+      ipv6_any_address_network_->SetMDnsResponder(GetMDnsResponder());
     }
     networks->push_back(ipv6_any_address_network_.get());
   }
@@ -381,6 +387,7 @@
         delete net;
       }
     }
+    networks_map_[key]->SetMDnsResponder(GetMDnsResponder());
   }
   // It may still happen that the merged list is a subset of |networks_|.
   // To detect this change, we compare their sizes.
diff --git a/rtc_base/network.h b/rtc_base/network.h
index ad932d0..601e39f 100644
--- a/rtc_base/network.h
+++ b/rtc_base/network.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "rtc_base/ipaddress.h"
+#include "rtc_base/mdns_responder_interface.h"
 #include "rtc_base/messagehandler.h"
 #include "rtc_base/networkmonitor.h"
 #include "rtc_base/third_party/sigslot/sigslot.h"
@@ -111,7 +112,7 @@
   // include ignored networks.
   virtual void GetNetworks(NetworkList* networks) const = 0;
 
-  // return the current permission state of GetNetworks()
+  // Returns the current permission state of GetNetworks().
   virtual EnumerationPermission enumeration_permission() const;
 
   // "AnyAddressNetwork" is a network which only contains single "any address"
@@ -137,6 +138,10 @@
       ipv6_network_count = 0;
     }
   };
+
+  // Returns the mDNS responder that can be used to obfuscate the local IP
+  // addresses of ICE host candidates by mDNS hostnames.
+  virtual webrtc::MDnsResponderInterface* GetMDnsResponder() const;
 };
 
 // Base class for NetworkManager implementations.
@@ -356,6 +361,19 @@
   const std::vector<InterfaceAddress>& GetIPs() const { return ips_; }
   // Clear the network's list of addresses.
   void ClearIPs() { ips_.clear(); }
+  // Sets the mDNS responder that can be used to obfuscate the local IP
+  // addresses of host candidates by mDNS names in ICE gathering. After a
+  // name-address mapping is created by the mDNS responder, queries for the
+  // created name will be resolved by the responder.
+  //
+  // The mDNS responder, if not null, should outlive this rtc::Network.
+  void SetMDnsResponder(webrtc::MDnsResponderInterface* mdns_responder) {
+    mdns_responder_ = mdns_responder;
+  }
+  // Returns the mDNS responder, which is null by default.
+  webrtc::MDnsResponderInterface* GetMDnsResponder() const {
+    return mdns_responder_;
+  }
 
   // Returns the scope-id of the network's address.
   // Should only be relevant for link-local IPv6 addresses.
@@ -428,6 +446,7 @@
   int prefix_length_;
   std::string key_;
   std::vector<InterfaceAddress> ips_;
+  webrtc::MDnsResponderInterface* mdns_responder_ = nullptr;
   int scope_id_;
   bool ignored_;
   AdapterType type_;