diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn index 74a124c62c..0f089902a5 100644 --- a/p2p/BUILD.gn +++ b/p2p/BUILD.gn @@ -91,6 +91,7 @@ rtc_static_library("rtc_p2p") { "../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 620137668f..0ca6e8b27a 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 9a4bb89e23..e860b299ff 100644 --- a/p2p/base/p2ptransportchannel.cc +++ b/p2p/base/p2ptransportchannel.cc @@ -807,7 +807,24 @@ void P2PTransportChannel::OnUnknownAddress( // 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 2da0fc3561..fadcb0a338 100644 --- a/p2p/base/p2ptransportchannel_unittest.cc +++ b/p2p/base/p2ptransportchannel_unittest.cc @@ -44,6 +44,9 @@ namespace { 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 @@ class P2PTransportChannelTestBase : public testing::Test, } } - 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 @@ TEST(P2PTransportChannelResolverTest, HostnameCandidateIsResolved) { 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 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 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 8ab33a3118..0ba9bb3e01 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 @@ Port::Port(rtc::Thread* thread, enable_port_packets_(false), ice_role_(ICEROLE_UNKNOWN), tiebreaker_(0), - shared_socket_(true) { + shared_socket_(true), + weak_factory_(this) { Construct(); } @@ -297,7 +299,8 @@ Port::Port(rtc::Thread* thread, 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 @@ void Port::AddAddress(const rtc::SocketAddress& address, 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 @@ void Port::AddAddress(const rtc::SocketAddress& address, 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 d881fb8eaf..151a4a3155 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 @@ class Port : public PortInterface, 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 @@ class Port : public PortInterface, State state_ = State::INIT; int64_t last_time_all_connections_removed_ = 0; + rtc::WeakPtrFactory weak_factory_; + friend class Connection; }; diff --git a/p2p/client/basicportallocator_unittest.cc b/p2p/client/basicportallocator_unittest.cc index b49be8a8f0..2b6dd3a9fc 100644 --- a/p2p/client/basicportallocator_unittest.cc +++ b/p2p/client/basicportallocator_unittest.cc @@ -2246,4 +2246,53 @@ TEST_F(BasicPortAllocatorTest, IceRegatheringMetricsLoggedWhenNetworkChanges) { static_cast(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 a642d9e105..009671c782 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -790,6 +790,7 @@ rtc_static_library("rtc_base_generic") { "ipaddress.cc", "ipaddress.h", "keep_ref_until_done.h", + "mdns_responder_interface.h", "messagedigest.cc", "messagedigest.h", "messagehandler.cc", @@ -1000,6 +1001,7 @@ rtc_source_set("rtc_base_tests_utils") { # 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 0000000000..32d69ba683 --- /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 +#include +#include + +#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 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 8ed11646cd..d5426a3e2c 100644 --- a/rtc_base/fakenetwork.h +++ b/rtc_base/fakenetwork.h @@ -16,6 +16,9 @@ #include #include +#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 @@ class FakeNetworkManager : public NetworkManagerBase, public MessageHandler { // MessageHandler interface. virtual void OnMessage(Message* msg) { DoUpdateNetworks(); } + void CreateMDnsResponder() { + if (mdns_responder_ == nullptr) { + mdns_responder_ = absl::make_unique(); + } + } + 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 @@ class FakeNetworkManager : public NetworkManagerBase, public MessageHandler { IPAddress default_local_ipv4_address_; IPAddress default_local_ipv6_address_; + + std::unique_ptr 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 0000000000..9dbaf56ad8 --- /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 +#include +#include +#include +#include + +#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; + using NameRemovedCallback = std::function; + + 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 01b84a6022..f0d402efe6 100644 --- a/rtc_base/network.cc +++ b/rtc_base/network.cc @@ -260,6 +260,10 @@ bool NetworkManager::GetDefaultLocalAddress(int family, IPAddress* addr) const { return false; } +webrtc::MDnsResponderInterface* NetworkManager::GetMDnsResponder() const { + return nullptr; +} + NetworkManagerBase::NetworkManagerBase() : enumeration_permission_(NetworkManager::ENUMERATION_ALLOWED), ipv6_enabled_(true) {} @@ -282,6 +286,7 @@ void NetworkManagerBase::GetAnyAddressNetworks(NetworkList* networks) { 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 @@ void NetworkManagerBase::GetAnyAddressNetworks(NetworkList* networks) { "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 @@ void NetworkManagerBase::MergeNetworkList(const NetworkList& new_networks, 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 ad932d05ac..601e39f027 100644 --- a/rtc_base/network.h +++ b/rtc_base/network.h @@ -20,6 +20,7 @@ #include #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 @@ class NetworkManager : public DefaultLocalAddressProvider { // 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 @@ class NetworkManager : public DefaultLocalAddressProvider { 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 @@ class Network { const std::vector& 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 @@ class Network { int prefix_length_; std::string key_; std::vector ips_; + webrtc::MDnsResponderInterface* mdns_responder_ = nullptr; int scope_id_; bool ignored_; AdapterType type_;