From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../p2p/base/p2p_transport_channel_unittest.cc | 6630 ++++++++++++++++++++ 1 file changed, 6630 insertions(+) create mode 100644 third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc (limited to 'third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc') diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc new file mode 100644 index 0000000000..4d73f013fb --- /dev/null +++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc @@ -0,0 +1,6630 @@ +/* + * Copyright 2009 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. + */ + +#include "p2p/base/p2p_transport_channel.h" + +#include +#include +#include +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/strings/string_view.h" +#include "api/test/mock_async_dns_resolver.h" +#include "p2p/base/active_ice_controller_factory_interface.h" +#include "p2p/base/active_ice_controller_interface.h" +#include "p2p/base/basic_ice_controller.h" +#include "p2p/base/connection.h" +#include "p2p/base/fake_port_allocator.h" +#include "p2p/base/ice_transport_internal.h" +#include "p2p/base/mock_active_ice_controller.h" +#include "p2p/base/mock_async_resolver.h" +#include "p2p/base/mock_ice_controller.h" +#include "p2p/base/packet_transport_internal.h" +#include "p2p/base/test_stun_server.h" +#include "p2p/base/test_turn_server.h" +#include "p2p/client/basic_port_allocator.h" +#include "rtc_base/checks.h" +#include "rtc_base/dscp.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/fake_mdns_responder.h" +#include "rtc_base/fake_network.h" +#include "rtc_base/firewall_socket_server.h" +#include "rtc_base/gunit.h" +#include "rtc_base/helpers.h" +#include "rtc_base/internal/default_socket_server.h" +#include "rtc_base/logging.h" +#include "rtc_base/mdns_responder_interface.h" +#include "rtc_base/nat_server.h" +#include "rtc_base/nat_socket_factory.h" +#include "rtc_base/proxy_server.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/thread.h" +#include "rtc_base/virtual_socket_server.h" +#include "system_wrappers/include/metrics.h" +#include "test/scoped_key_value_config.h" + +namespace { + +using rtc::SocketAddress; +using ::testing::_; +using ::testing::Assign; +using ::testing::Combine; +using ::testing::Contains; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::InvokeArgument; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::SizeIs; +using ::testing::TestWithParam; +using ::testing::Values; +using ::testing::WithParamInterface; +using ::webrtc::PendingTaskSafetyFlag; +using ::webrtc::SafeTask; + +// Default timeout for tests in this file. +// Should be large enough for slow buildbots to run the tests reliably. +static const int kDefaultTimeout = 10000; +static const int kMediumTimeout = 3000; +static const int kShortTimeout = 1000; + +static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP; +static const int LOW_RTT = 20; +// Addresses on the public internet. +static const SocketAddress kPublicAddrs[2] = {SocketAddress("11.11.11.11", 0), + SocketAddress("22.22.22.22", 0)}; +// IPv6 Addresses on the public internet. +static const SocketAddress kIPv6PublicAddrs[2] = { + SocketAddress("2400:4030:1:2c00:be30:abcd:efab:cdef", 0), + SocketAddress("2600:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)}; +// For configuring multihomed clients. +static const SocketAddress kAlternateAddrs[2] = { + SocketAddress("101.101.101.101", 0), SocketAddress("202.202.202.202", 0)}; +static const SocketAddress kIPv6AlternateAddrs[2] = { + SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0), + SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)}; +// Addresses for HTTP proxy servers. +static const SocketAddress kHttpsProxyAddrs[2] = { + SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443)}; +// Addresses for SOCKS proxy servers. +static const SocketAddress kSocksProxyAddrs[2] = { + SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080)}; +// Internal addresses for NAT boxes. +static const SocketAddress kNatAddrs[2] = {SocketAddress("192.168.1.1", 0), + SocketAddress("192.168.2.1", 0)}; +// Private addresses inside the NAT private networks. +static const SocketAddress kPrivateAddrs[2] = { + SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0)}; +// For cascaded NATs, the internal addresses of the inner NAT boxes. +static const SocketAddress kCascadedNatAddrs[2] = { + SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0)}; +// For cascaded NATs, private addresses inside the inner private networks. +static const SocketAddress kCascadedPrivateAddrs[2] = { + SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0)}; +// The address of the public STUN server. +static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT); +// The addresses for the public turn server. +static const SocketAddress kTurnUdpIntAddr("99.99.99.3", + cricket::STUN_SERVER_PORT); +static const SocketAddress kTurnTcpIntAddr("99.99.99.4", + cricket::STUN_SERVER_PORT + 1); +static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); +static const cricket::RelayCredentials kRelayCredentials("test", "test"); + +// Based on ICE_UFRAG_LENGTH +const char* kIceUfrag[4] = {"UF00", "UF01", "UF02", "UF03"}; +// Based on ICE_PWD_LENGTH +const char* kIcePwd[4] = { + "TESTICEPWD00000000000000", "TESTICEPWD00000000000001", + "TESTICEPWD00000000000002", "TESTICEPWD00000000000003"}; +const cricket::IceParameters kIceParams[4] = { + {kIceUfrag[0], kIcePwd[0], false}, + {kIceUfrag[1], kIcePwd[1], false}, + {kIceUfrag[2], kIcePwd[2], false}, + {kIceUfrag[3], kIcePwd[3], false}}; + +const uint64_t kLowTiebreaker = 11111; +const uint64_t kHighTiebreaker = 22222; +const uint64_t kTiebreakerDefault = 44444; + +cricket::IceConfig CreateIceConfig( + int receiving_timeout, + cricket::ContinualGatheringPolicy continual_gathering_policy, + absl::optional backup_ping_interval = absl::nullopt) { + cricket::IceConfig config; + config.receiving_timeout = receiving_timeout; + config.continual_gathering_policy = continual_gathering_policy; + config.backup_connection_ping_interval = backup_ping_interval; + return config; +} + +cricket::Candidate CreateUdpCandidate(absl::string_view type, + absl::string_view ip, + int port, + int priority, + absl::string_view ufrag = "") { + cricket::Candidate c; + c.set_address(rtc::SocketAddress(ip, port)); + c.set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); + c.set_protocol(cricket::UDP_PROTOCOL_NAME); + c.set_priority(priority); + c.set_username(ufrag); + c.set_type(type); + return c; +} + +cricket::BasicPortAllocator* CreateBasicPortAllocator( + rtc::NetworkManager* network_manager, + rtc::SocketServer* socket_server, + const cricket::ServerAddresses& stun_servers, + const rtc::SocketAddress& turn_server_udp, + const rtc::SocketAddress& turn_server_tcp) { + cricket::RelayServerConfig turn_server; + turn_server.credentials = kRelayCredentials; + if (!turn_server_udp.IsNil()) { + turn_server.ports.push_back( + cricket::ProtocolAddress(turn_server_udp, cricket::PROTO_UDP)); + } + if (!turn_server_tcp.IsNil()) { + turn_server.ports.push_back( + cricket::ProtocolAddress(turn_server_tcp, cricket::PROTO_TCP)); + } + std::vector turn_servers(1, turn_server); + + std::unique_ptr allocator = + std::make_unique( + network_manager, + std::make_unique(socket_server)); + allocator->Initialize(); + allocator->SetConfiguration(stun_servers, turn_servers, 0, webrtc::NO_PRUNE); + return allocator.release(); +} + +// An one-shot resolver factory with default return arguments. +// Resolution is immediate, always succeeds, and returns nonsense. +class ResolverFactoryFixture : public webrtc::MockAsyncDnsResolverFactory { + public: + ResolverFactoryFixture() { + mock_async_dns_resolver_ = std::make_unique(); + EXPECT_CALL(*mock_async_dns_resolver_, Start(_, _)) + .WillRepeatedly(InvokeArgument<1>()); + EXPECT_CALL(*mock_async_dns_resolver_, result()) + .WillOnce(ReturnRef(mock_async_dns_resolver_result_)); + + // A default action for GetResolvedAddress. Will be overruled + // by SetAddressToReturn. + EXPECT_CALL(mock_async_dns_resolver_result_, GetResolvedAddress(_, _)) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(mock_async_dns_resolver_result_, GetError()) + .WillOnce(Return(0)); + EXPECT_CALL(*this, Create()).WillOnce([this]() { + return std::move(mock_async_dns_resolver_); + }); + } + + void SetAddressToReturn(rtc::SocketAddress address_to_return) { + EXPECT_CALL(mock_async_dns_resolver_result_, GetResolvedAddress(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(address_to_return), Return(true))); + } + void DelayResolution() { + // This function must be called before Create(). + ASSERT_TRUE(!!mock_async_dns_resolver_); + EXPECT_CALL(*mock_async_dns_resolver_, Start(_, _)) + .WillOnce(SaveArg<1>(&saved_callback_)); + } + void FireDelayedResolution() { + // This function must be called after Create(). + ASSERT_TRUE(saved_callback_); + saved_callback_(); + } + + private: + std::unique_ptr mock_async_dns_resolver_; + webrtc::MockAsyncDnsResolverResult mock_async_dns_resolver_result_; + std::function saved_callback_; +}; + +bool HasLocalAddress(const cricket::CandidatePairInterface* pair, + const SocketAddress& address) { + return pair->local_candidate().address().EqualIPs(address); +} + +bool HasRemoteAddress(const cricket::CandidatePairInterface* pair, + const SocketAddress& address) { + return pair->remote_candidate().address().EqualIPs(address); +} + +} // namespace + +namespace cricket { + +// This test simulates 2 P2P endpoints that want to establish connectivity +// with each other over various network topologies and conditions, which can be +// specified in each individial test. +// A virtual network (via VirtualSocketServer) along with virtual firewalls and +// NATs (via Firewall/NATSocketServer) are used to simulate the various network +// conditions. We can configure the IP addresses of the endpoints, +// block various types of connectivity, or add arbitrary levels of NAT. +// We also run a STUN server and a relay server on the virtual network to allow +// our typical P2P mechanisms to do their thing. +// For each case, we expect the P2P stack to eventually settle on a specific +// form of connectivity to the other side. The test checks that the P2P +// negotiation successfully establishes connectivity within a certain time, +// and that the result is what we expect. +// Note that this class is a base class for use by other tests, who will provide +// specialized test behavior. +class P2PTransportChannelTestBase : public ::testing::Test, + public sigslot::has_slots<> { + public: + explicit P2PTransportChannelTestBase(absl::string_view field_trials) + : field_trials_(field_trials), + vss_(new rtc::VirtualSocketServer()), + nss_(new rtc::NATSocketServer(vss_.get())), + ss_(new rtc::FirewallSocketServer(nss_.get())), + main_(ss_.get()), + stun_server_(TestStunServer::Create(ss_.get(), kStunAddr)), + turn_server_(&main_, ss_.get(), kTurnUdpIntAddr, kTurnUdpExtAddr), + socks_server1_(ss_.get(), + kSocksProxyAddrs[0], + ss_.get(), + kSocksProxyAddrs[0]), + socks_server2_(ss_.get(), + kSocksProxyAddrs[1], + ss_.get(), + kSocksProxyAddrs[1]), + force_relay_(false) { + ep1_.role_ = ICEROLE_CONTROLLING; + ep2_.role_ = ICEROLE_CONTROLLED; + + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr); + ep1_.allocator_.reset(CreateBasicPortAllocator( + &ep1_.network_manager_, ss_.get(), stun_servers, kTurnUdpIntAddr, + rtc::SocketAddress())); + ep2_.allocator_.reset(CreateBasicPortAllocator( + &ep2_.network_manager_, ss_.get(), stun_servers, kTurnUdpIntAddr, + rtc::SocketAddress())); + + ep1_.SetIceTiebreaker(kTiebreakerDefault); + ep1_.allocator_->SetIceTiebreaker(kTiebreakerDefault); + ep2_.SetIceTiebreaker(kTiebreakerDefault); + ep2_.allocator_->SetIceTiebreaker(kTiebreakerDefault); + webrtc::metrics::Reset(); + } + + P2PTransportChannelTestBase() + : P2PTransportChannelTestBase(absl::string_view()) {} + + protected: + enum Config { + OPEN, // Open to the Internet + NAT_FULL_CONE, // NAT, no filtering + NAT_ADDR_RESTRICTED, // NAT, must send to an addr to recv + NAT_PORT_RESTRICTED, // NAT, must send to an addr+port to recv + NAT_SYMMETRIC, // NAT, endpoint-dependent bindings + NAT_DOUBLE_CONE, // Double NAT, both cone + NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner + BLOCK_UDP, // Firewall, UDP in/out blocked + BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked + BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443 + PROXY_HTTPS, // All traffic through HTTPS proxy + PROXY_SOCKS, // All traffic through SOCKS proxy + NUM_CONFIGS + }; + + struct Result { + Result(absl::string_view controlling_type, + absl::string_view controlling_protocol, + absl::string_view controlled_type, + absl::string_view controlled_protocol, + int wait) + : controlling_type(controlling_type), + controlling_protocol(controlling_protocol), + controlled_type(controlled_type), + controlled_protocol(controlled_protocol), + connect_wait(wait) {} + + // The expected candidate type and protocol of the controlling ICE agent. + std::string controlling_type; + std::string controlling_protocol; + // The expected candidate type and protocol of the controlled ICE agent. + std::string controlled_type; + std::string controlled_protocol; + // How long to wait before the correct candidate pair is selected. + int connect_wait; + }; + + struct ChannelData { + bool CheckData(const char* data, int len) { + bool ret = false; + if (!ch_packets_.empty()) { + std::string packet = ch_packets_.front(); + ret = (packet == std::string(data, len)); + ch_packets_.pop_front(); + } + return ret; + } + + std::string name_; // TODO(?) - Currently not used. + std::list ch_packets_; + std::unique_ptr ch_; + }; + + struct CandidateData { + IceTransportInternal* channel; + Candidate candidate; + }; + + struct Endpoint : public sigslot::has_slots<> { + Endpoint() + : role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + role_conflict_(false), + save_candidates_(false) {} + bool HasTransport(const rtc::PacketTransportInternal* transport) { + return (transport == cd1_.ch_.get() || transport == cd2_.ch_.get()); + } + ChannelData* GetChannelData(rtc::PacketTransportInternal* transport) { + if (!HasTransport(transport)) + return NULL; + if (cd1_.ch_.get() == transport) + return &cd1_; + else + return &cd2_; + } + + void SetIceRole(IceRole role) { role_ = role; } + IceRole ice_role() { return role_; } + void SetIceTiebreaker(uint64_t tiebreaker) { tiebreaker_ = tiebreaker; } + uint64_t GetIceTiebreaker() { return tiebreaker_; } + void OnRoleConflict(bool role_conflict) { role_conflict_ = role_conflict; } + bool role_conflict() { return role_conflict_; } + void SetAllocationStepDelay(uint32_t delay) { + allocator_->set_step_delay(delay); + } + void SetAllowTcpListen(bool allow_tcp_listen) { + allocator_->set_allow_tcp_listen(allow_tcp_listen); + } + + void OnIceRegathering(PortAllocatorSession*, IceRegatheringReason reason) { + ++ice_regathering_counter_[reason]; + } + + int GetIceRegatheringCountForReason(IceRegatheringReason reason) { + return ice_regathering_counter_[reason]; + } + + rtc::FakeNetworkManager network_manager_; + std::unique_ptr allocator_; + webrtc::AsyncDnsResolverFactoryInterface* async_dns_resolver_factory_; + ChannelData cd1_; + ChannelData cd2_; + IceRole role_; + uint64_t tiebreaker_; + bool role_conflict_; + bool save_candidates_; + std::vector saved_candidates_; + bool ready_to_send_ = false; + std::map ice_regathering_counter_; + }; + + ChannelData* GetChannelData(rtc::PacketTransportInternal* transport) { + if (ep1_.HasTransport(transport)) + return ep1_.GetChannelData(transport); + else + return ep2_.GetChannelData(transport); + } + + IceParameters IceParamsWithRenomination(const IceParameters& ice, + bool renomination) { + IceParameters new_ice = ice; + new_ice.renomination = renomination; + return new_ice; + } + + void CreateChannels(const IceConfig& ep1_config, + const IceConfig& ep2_config, + bool renomination = false) { + IceParameters ice_ep1_cd1_ch = + IceParamsWithRenomination(kIceParams[0], renomination); + IceParameters ice_ep2_cd1_ch = + IceParamsWithRenomination(kIceParams[1], renomination); + ep1_.cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + ice_ep1_cd1_ch, ice_ep2_cd1_ch); + ep2_.cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT, + ice_ep2_cd1_ch, ice_ep1_cd1_ch); + ep1_.cd1_.ch_->SetIceConfig(ep1_config); + ep2_.cd1_.ch_->SetIceConfig(ep2_config); + ep1_.cd1_.ch_->MaybeStartGathering(); + ep2_.cd1_.ch_->MaybeStartGathering(); + ep1_.cd1_.ch_->allocator_session()->SignalIceRegathering.connect( + &ep1_, &Endpoint::OnIceRegathering); + ep2_.cd1_.ch_->allocator_session()->SignalIceRegathering.connect( + &ep2_, &Endpoint::OnIceRegathering); + } + + void CreateChannels() { + IceConfig default_config; + CreateChannels(default_config, default_config, false); + } + + std::unique_ptr CreateChannel( + int endpoint, + int component, + const IceParameters& local_ice, + const IceParameters& remote_ice) { + webrtc::IceTransportInit init; + init.set_port_allocator(GetAllocator(endpoint)); + init.set_async_dns_resolver_factory( + GetEndpoint(endpoint)->async_dns_resolver_factory_); + init.set_field_trials(&field_trials_); + auto channel = P2PTransportChannel::Create("test content name", component, + std::move(init)); + channel->SignalReadyToSend.connect( + this, &P2PTransportChannelTestBase::OnReadyToSend); + channel->SignalCandidateGathered.connect( + this, &P2PTransportChannelTestBase::OnCandidateGathered); + channel->SignalCandidatesRemoved.connect( + this, &P2PTransportChannelTestBase::OnCandidatesRemoved); + channel->SignalReadPacket.connect( + this, &P2PTransportChannelTestBase::OnReadPacket); + channel->SignalRoleConflict.connect( + this, &P2PTransportChannelTestBase::OnRoleConflict); + channel->SignalNetworkRouteChanged.connect( + this, &P2PTransportChannelTestBase::OnNetworkRouteChanged); + channel->SignalSentPacket.connect( + this, &P2PTransportChannelTestBase::OnSentPacket); + channel->SetIceParameters(local_ice); + if (remote_ice_parameter_source_ == FROM_SETICEPARAMETERS) { + channel->SetRemoteIceParameters(remote_ice); + } + channel->SetIceRole(GetEndpoint(endpoint)->ice_role()); + channel->SetIceTiebreaker(GetEndpoint(endpoint)->GetIceTiebreaker()); + return channel; + } + + void DestroyChannels() { + safety_->SetNotAlive(); + ep1_.cd1_.ch_.reset(); + ep2_.cd1_.ch_.reset(); + ep1_.cd2_.ch_.reset(); + ep2_.cd2_.ch_.reset(); + // Process pending tasks that need to run for cleanup purposes such as + // pending deletion of Connection objects (see Connection::Destroy). + rtc::Thread::Current()->ProcessMessages(0); + } + P2PTransportChannel* ep1_ch1() { return ep1_.cd1_.ch_.get(); } + P2PTransportChannel* ep1_ch2() { return ep1_.cd2_.ch_.get(); } + P2PTransportChannel* ep2_ch1() { return ep2_.cd1_.ch_.get(); } + P2PTransportChannel* ep2_ch2() { return ep2_.cd2_.ch_.get(); } + + TestTurnServer* test_turn_server() { return &turn_server_; } + rtc::VirtualSocketServer* virtual_socket_server() { return vss_.get(); } + + // Common results. + static const Result kLocalUdpToLocalUdp; + static const Result kLocalUdpToStunUdp; + static const Result kLocalUdpToPrflxUdp; + static const Result kPrflxUdpToLocalUdp; + static const Result kStunUdpToLocalUdp; + static const Result kStunUdpToStunUdp; + static const Result kStunUdpToPrflxUdp; + static const Result kPrflxUdpToStunUdp; + static const Result kLocalUdpToRelayUdp; + static const Result kPrflxUdpToRelayUdp; + static const Result kRelayUdpToPrflxUdp; + static const Result kLocalTcpToLocalTcp; + static const Result kLocalTcpToPrflxTcp; + static const Result kPrflxTcpToLocalTcp; + + rtc::NATSocketServer* nat() { return nss_.get(); } + rtc::FirewallSocketServer* fw() { return ss_.get(); } + + Endpoint* GetEndpoint(int endpoint) { + if (endpoint == 0) { + return &ep1_; + } else if (endpoint == 1) { + return &ep2_; + } else { + return NULL; + } + } + BasicPortAllocator* GetAllocator(int endpoint) { + return GetEndpoint(endpoint)->allocator_.get(); + } + void AddAddress(int endpoint, const SocketAddress& addr) { + GetEndpoint(endpoint)->network_manager_.AddInterface(addr); + } + void AddAddress(int endpoint, + const SocketAddress& addr, + absl::string_view ifname, + rtc::AdapterType adapter_type, + absl::optional underlying_vpn_adapter_type = + absl::nullopt) { + GetEndpoint(endpoint)->network_manager_.AddInterface( + addr, ifname, adapter_type, underlying_vpn_adapter_type); + } + void RemoveAddress(int endpoint, const SocketAddress& addr) { + GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr); + } + void SetProxy(int endpoint, rtc::ProxyType type) { + rtc::ProxyInfo info; + info.type = type; + info.address = (type == rtc::PROXY_HTTPS) ? kHttpsProxyAddrs[endpoint] + : kSocksProxyAddrs[endpoint]; + GetAllocator(endpoint)->set_proxy("unittest/1.0", info); + } + void SetAllocatorFlags(int endpoint, int flags) { + GetAllocator(endpoint)->set_flags(flags); + } + void SetIceRole(int endpoint, IceRole role) { + GetEndpoint(endpoint)->SetIceRole(role); + } + void SetIceTiebreaker(int endpoint, uint64_t tiebreaker) { + GetEndpoint(endpoint)->SetIceTiebreaker(tiebreaker); + } + bool GetRoleConflict(int endpoint) { + return GetEndpoint(endpoint)->role_conflict(); + } + void SetAllocationStepDelay(int endpoint, uint32_t delay) { + return GetEndpoint(endpoint)->SetAllocationStepDelay(delay); + } + void SetAllowTcpListen(int endpoint, bool allow_tcp_listen) { + return GetEndpoint(endpoint)->SetAllowTcpListen(allow_tcp_listen); + } + + // Return true if the approprite parts of the expected Result, based + // on the local and remote candidate of ep1_ch1, match. This can be + // used in an EXPECT_TRUE_WAIT. + bool CheckCandidate1(const Result& expected) { + const std::string& local_type = LocalCandidate(ep1_ch1())->type(); + const std::string& local_protocol = LocalCandidate(ep1_ch1())->protocol(); + const std::string& remote_type = RemoteCandidate(ep1_ch1())->type(); + const std::string& remote_protocol = RemoteCandidate(ep1_ch1())->protocol(); + return (local_protocol == expected.controlling_protocol && + remote_protocol == expected.controlled_protocol && + local_type == expected.controlling_type && + remote_type == expected.controlled_type); + } + + // EXPECT_EQ on the approprite parts of the expected Result, based + // on the local and remote candidate of ep1_ch1. This is like + // CheckCandidate1, except that it will provide more detail about + // what didn't match. + void ExpectCandidate1(const Result& expected) { + if (CheckCandidate1(expected)) { + return; + } + + const std::string& local_type = LocalCandidate(ep1_ch1())->type(); + const std::string& local_protocol = LocalCandidate(ep1_ch1())->protocol(); + const std::string& remote_type = RemoteCandidate(ep1_ch1())->type(); + const std::string& remote_protocol = RemoteCandidate(ep1_ch1())->protocol(); + EXPECT_EQ(expected.controlling_type, local_type); + EXPECT_EQ(expected.controlled_type, remote_type); + EXPECT_EQ(expected.controlling_protocol, local_protocol); + EXPECT_EQ(expected.controlled_protocol, remote_protocol); + } + + // Return true if the approprite parts of the expected Result, based + // on the local and remote candidate of ep2_ch1, match. This can be + // used in an EXPECT_TRUE_WAIT. + bool CheckCandidate2(const Result& expected) { + const std::string& local_type = LocalCandidate(ep2_ch1())->type(); + const std::string& local_protocol = LocalCandidate(ep2_ch1())->protocol(); + const std::string& remote_type = RemoteCandidate(ep2_ch1())->type(); + const std::string& remote_protocol = RemoteCandidate(ep2_ch1())->protocol(); + return (local_protocol == expected.controlled_protocol && + remote_protocol == expected.controlling_protocol && + local_type == expected.controlled_type && + remote_type == expected.controlling_type); + } + + // EXPECT_EQ on the approprite parts of the expected Result, based + // on the local and remote candidate of ep2_ch1. This is like + // CheckCandidate2, except that it will provide more detail about + // what didn't match. + void ExpectCandidate2(const Result& expected) { + if (CheckCandidate2(expected)) { + return; + } + + const std::string& local_type = LocalCandidate(ep2_ch1())->type(); + const std::string& local_protocol = LocalCandidate(ep2_ch1())->protocol(); + const std::string& remote_type = RemoteCandidate(ep2_ch1())->type(); + const std::string& remote_protocol = RemoteCandidate(ep2_ch1())->protocol(); + EXPECT_EQ(expected.controlled_type, local_type); + EXPECT_EQ(expected.controlling_type, remote_type); + EXPECT_EQ(expected.controlled_protocol, local_protocol); + EXPECT_EQ(expected.controlling_protocol, remote_protocol); + } + + static bool CheckCandidate(P2PTransportChannel* channel, + SocketAddress from, + SocketAddress to) { + auto local_candidate = LocalCandidate(channel); + auto remote_candidate = RemoteCandidate(channel); + return local_candidate != nullptr && + local_candidate->address().EqualIPs(from) && + remote_candidate != nullptr && + remote_candidate->address().EqualIPs(to); + } + + static bool CheckCandidatePair(P2PTransportChannel* ch1, + P2PTransportChannel* ch2, + SocketAddress from, + SocketAddress to) { + return CheckCandidate(ch1, from, to) && CheckCandidate(ch2, to, from); + } + + static bool CheckConnected(P2PTransportChannel* ch1, + P2PTransportChannel* ch2) { + return ch1 != nullptr && ch1->receiving() && ch1->writable() && + ch2 != nullptr && ch2->receiving() && ch2->writable(); + } + + static bool CheckCandidatePairAndConnected(P2PTransportChannel* ch1, + P2PTransportChannel* ch2, + SocketAddress from, + SocketAddress to) { + return CheckConnected(ch1, ch2) && CheckCandidatePair(ch1, ch2, from, to); + } + + virtual void Test(const Result& expected) { + rtc::ScopedFakeClock clock; + int64_t connect_start = rtc::TimeMillis(); + int64_t connect_time; + + // Create the channels and wait for them to connect. + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + expected.connect_wait + kShortTimeout, clock); + connect_time = rtc::TimeMillis() - connect_start; + if (connect_time < expected.connect_wait) { + RTC_LOG(LS_INFO) << "Connect time: " << connect_time << " ms"; + } else { + RTC_LOG(LS_INFO) << "Connect time: TIMEOUT (" << expected.connect_wait + << " ms)"; + } + + // Allow a few turns of the crank for the selected connections to emerge. + // This may take up to 2 seconds. + if (ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection()) { + int64_t converge_start = rtc::TimeMillis(); + int64_t converge_time; + // Verifying local and remote channel selected connection information. + // This is done only for the RFC 5245 as controlled agent will use + // USE-CANDIDATE from controlling (ep1) agent. We can easily predict from + // EP1 result matrix. + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidate1(expected) && CheckCandidate2(expected), + kDefaultTimeout, clock); + // Also do EXPECT_EQ on each part so that failures are more verbose. + ExpectCandidate1(expected); + ExpectCandidate2(expected); + + converge_time = rtc::TimeMillis() - converge_start; + int64_t converge_wait = 2000; + if (converge_time < converge_wait) { + RTC_LOG(LS_INFO) << "Converge time: " << converge_time << " ms"; + } else { + RTC_LOG(LS_INFO) << "Converge time: TIMEOUT (" << converge_time + << " ms)"; + } + } + // Try sending some data to other end. + TestSendRecv(&clock); + + // Destroy the channels, and wait for them to be fully cleaned up. + DestroyChannels(); + } + + void TestSendRecv(rtc::ThreadProcessingFakeClock* clock) { + for (int i = 0; i < 10; ++i) { + const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + int len = static_cast(strlen(data)); + // local_channel1 <==> remote_channel1 + EXPECT_EQ_SIMULATED_WAIT(len, SendData(ep1_ch1(), data, len), + kMediumTimeout, *clock); + EXPECT_TRUE_SIMULATED_WAIT(CheckDataOnChannel(ep2_ch1(), data, len), + kMediumTimeout, *clock); + EXPECT_EQ_SIMULATED_WAIT(len, SendData(ep2_ch1(), data, len), + kMediumTimeout, *clock); + EXPECT_TRUE_SIMULATED_WAIT(CheckDataOnChannel(ep1_ch1(), data, len), + kMediumTimeout, *clock); + } + } + + // This test waits for the transport to become receiving and writable on both + // end points. Once they are, the end points set new local ice parameters and + // restart the ice gathering. Finally it waits for the transport to select a + // new connection using the newly generated ice candidates. + // Before calling this function the end points must be configured. + void TestHandleIceUfragPasswordChanged() { + rtc::ScopedFakeClock clock; + ep1_ch1()->SetRemoteIceParameters(kIceParams[1]); + ep2_ch1()->SetRemoteIceParameters(kIceParams[0]); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + + const Candidate* old_local_candidate1 = LocalCandidate(ep1_ch1()); + const Candidate* old_local_candidate2 = LocalCandidate(ep2_ch1()); + const Candidate* old_remote_candidate1 = RemoteCandidate(ep1_ch1()); + const Candidate* old_remote_candidate2 = RemoteCandidate(ep2_ch1()); + + ep1_ch1()->SetIceParameters(kIceParams[2]); + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep1_ch1()->MaybeStartGathering(); + ep2_ch1()->SetIceParameters(kIceParams[3]); + + ep2_ch1()->SetRemoteIceParameters(kIceParams[2]); + ep2_ch1()->MaybeStartGathering(); + + EXPECT_TRUE_SIMULATED_WAIT(LocalCandidate(ep1_ch1())->generation() != + old_local_candidate1->generation(), + kMediumTimeout, clock); + EXPECT_TRUE_SIMULATED_WAIT(LocalCandidate(ep2_ch1())->generation() != + old_local_candidate2->generation(), + kMediumTimeout, clock); + EXPECT_TRUE_SIMULATED_WAIT(RemoteCandidate(ep1_ch1())->generation() != + old_remote_candidate1->generation(), + kMediumTimeout, clock); + EXPECT_TRUE_SIMULATED_WAIT(RemoteCandidate(ep2_ch1())->generation() != + old_remote_candidate2->generation(), + kMediumTimeout, clock); + EXPECT_EQ(1u, RemoteCandidate(ep2_ch1())->generation()); + EXPECT_EQ(1u, RemoteCandidate(ep1_ch1())->generation()); + } + + void TestSignalRoleConflict() { + rtc::ScopedFakeClock clock; + // Default EP1 is in controlling state. + SetIceTiebreaker(0, kLowTiebreaker); + + SetIceRole(1, ICEROLE_CONTROLLING); + SetIceTiebreaker(1, kHighTiebreaker); + + // Creating channels with both channels role set to CONTROLLING. + CreateChannels(); + // Since both the channels initiated with controlling state and channel2 + // has higher tiebreaker value, channel1 should receive SignalRoleConflict. + EXPECT_TRUE_SIMULATED_WAIT(GetRoleConflict(0), kShortTimeout, clock); + EXPECT_FALSE(GetRoleConflict(1)); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kShortTimeout, clock); + + EXPECT_TRUE(ep1_ch1()->selected_connection() && + ep2_ch1()->selected_connection()); + + TestSendRecv(&clock); + DestroyChannels(); + } + + void TestPacketInfoIsSet(rtc::PacketInfo info) { + EXPECT_NE(info.packet_type, rtc::PacketType::kUnknown); + EXPECT_NE(info.protocol, rtc::PacketInfoProtocolType::kUnknown); + EXPECT_TRUE(info.network_id.has_value()); + } + + void OnReadyToSend(rtc::PacketTransportInternal* transport) { + GetEndpoint(transport)->ready_to_send_ = true; + } + + // We pass the candidates directly to the other side. + void OnCandidateGathered(IceTransportInternal* ch, const Candidate& c) { + if (force_relay_ && c.type() != RELAY_PORT_TYPE) + return; + + if (GetEndpoint(ch)->save_candidates_) { + GetEndpoint(ch)->saved_candidates_.push_back( + {.channel = ch, .candidate = c}); + } else { + main_.PostTask(SafeTask( + safety_, [this, ch, c = c]() mutable { AddCandidate(ch, c); })); + } + } + + void OnNetworkRouteChanged(absl::optional network_route) { + // If the `network_route` is unset, don't count. This is used in the case + // when the network on remote side is down, the signal will be fired with an + // unset network route and it shouldn't trigger a connection switch. + if (network_route) { + ++selected_candidate_pair_switches_; + } + } + + int reset_selected_candidate_pair_switches() { + int switches = selected_candidate_pair_switches_; + selected_candidate_pair_switches_ = 0; + return switches; + } + + void PauseCandidates(int endpoint) { + GetEndpoint(endpoint)->save_candidates_ = true; + } + + void OnCandidatesRemoved(IceTransportInternal* ch, + const std::vector& candidates) { + // Candidate removals are not paused. + main_.PostTask(SafeTask(safety_, [this, ch, candidates]() mutable { + P2PTransportChannel* rch = GetRemoteChannel(ch); + if (rch == nullptr) { + return; + } + for (const Candidate& c : candidates) { + RTC_LOG(LS_INFO) << "Removed remote candidate " << c.ToString(); + rch->RemoveRemoteCandidate(c); + } + })); + } + + // Tcp candidate verification has to be done when they are generated. + void VerifySavedTcpCandidates(int endpoint, absl::string_view tcptype) { + for (auto& data : GetEndpoint(endpoint)->saved_candidates_) { + EXPECT_EQ(data.candidate.protocol(), TCP_PROTOCOL_NAME); + EXPECT_EQ(data.candidate.tcptype(), tcptype); + if (data.candidate.tcptype() == TCPTYPE_ACTIVE_STR) { + EXPECT_EQ(data.candidate.address().port(), DISCARD_PORT); + } else if (data.candidate.tcptype() == TCPTYPE_PASSIVE_STR) { + EXPECT_NE(data.candidate.address().port(), DISCARD_PORT); + } else { + FAIL() << "Unknown tcptype: " << data.candidate.tcptype(); + } + } + } + + void ResumeCandidates(int endpoint) { + Endpoint* ed = GetEndpoint(endpoint); + std::vector candidates = std::move(ed->saved_candidates_); + if (!candidates.empty()) { + main_.PostTask(SafeTask( + safety_, [this, candidates = std::move(candidates)]() mutable { + for (CandidateData& data : candidates) { + AddCandidate(data.channel, data.candidate); + } + })); + } + ed->saved_candidates_.clear(); + ed->save_candidates_ = false; + } + + void AddCandidate(IceTransportInternal* channel, Candidate& candidate) { + P2PTransportChannel* rch = GetRemoteChannel(channel); + if (rch == nullptr) { + return; + } + if (remote_ice_parameter_source_ != FROM_CANDIDATE) { + candidate.set_username(""); + candidate.set_password(""); + } + RTC_LOG(LS_INFO) << "Candidate(" << channel->component() << "->" + << rch->component() << "): " << candidate.ToString(); + rch->AddRemoteCandidate(candidate); + } + + void OnReadPacket(rtc::PacketTransportInternal* transport, + const char* data, + size_t len, + const int64_t& /* packet_time_us */, + int flags) { + std::list& packets = GetPacketList(transport); + packets.push_front(std::string(data, len)); + } + + void OnRoleConflict(IceTransportInternal* channel) { + GetEndpoint(channel)->OnRoleConflict(true); + IceRole new_role = GetEndpoint(channel)->ice_role() == ICEROLE_CONTROLLING + ? ICEROLE_CONTROLLED + : ICEROLE_CONTROLLING; + channel->SetIceRole(new_role); + } + + void OnSentPacket(rtc::PacketTransportInternal* transport, + const rtc::SentPacket& packet) { + TestPacketInfoIsSet(packet.info); + } + + int SendData(IceTransportInternal* channel, const char* data, size_t len) { + rtc::PacketOptions options; + return channel->SendPacket(data, len, options, 0); + } + bool CheckDataOnChannel(IceTransportInternal* channel, + const char* data, + int len) { + return GetChannelData(channel)->CheckData(data, len); + } + static const Candidate* LocalCandidate(P2PTransportChannel* ch) { + return (ch && ch->selected_connection()) + ? &ch->selected_connection()->local_candidate() + : NULL; + } + static const Candidate* RemoteCandidate(P2PTransportChannel* ch) { + return (ch && ch->selected_connection()) + ? &ch->selected_connection()->remote_candidate() + : NULL; + } + Endpoint* GetEndpoint(rtc::PacketTransportInternal* transport) { + if (ep1_.HasTransport(transport)) { + return &ep1_; + } else if (ep2_.HasTransport(transport)) { + return &ep2_; + } else { + return NULL; + } + } + P2PTransportChannel* GetRemoteChannel(IceTransportInternal* ch) { + if (ch == ep1_ch1()) + return ep2_ch1(); + else if (ch == ep1_ch2()) + return ep2_ch2(); + else if (ch == ep2_ch1()) + return ep1_ch1(); + else if (ch == ep2_ch2()) + return ep1_ch2(); + else + return NULL; + } + std::list& GetPacketList( + rtc::PacketTransportInternal* transport) { + return GetChannelData(transport)->ch_packets_; + } + + enum RemoteIceParameterSource { FROM_CANDIDATE, FROM_SETICEPARAMETERS }; + + // How does the test pass ICE parameters to the P2PTransportChannel? + // On the candidate itself, or through SetRemoteIceParameters? + // Goes through the candidate itself by default. + void set_remote_ice_parameter_source(RemoteIceParameterSource source) { + remote_ice_parameter_source_ = source; + } + + void set_force_relay(bool relay) { force_relay_ = relay; } + + void ConnectSignalNominated(Connection* conn) { + conn->SignalNominated.connect(this, + &P2PTransportChannelTestBase::OnNominated); + } + + void OnNominated(Connection* conn) { nominated_ = true; } + bool nominated() { return nominated_; } + + webrtc::test::ScopedKeyValueConfig field_trials_; + + private: + std::unique_ptr vss_; + std::unique_ptr nss_; + std::unique_ptr ss_; + rtc::AutoSocketServerThread main_; + rtc::scoped_refptr safety_ = + PendingTaskSafetyFlag::Create(); + std::unique_ptr stun_server_; + TestTurnServer turn_server_; + rtc::SocksProxyServer socks_server1_; + rtc::SocksProxyServer socks_server2_; + Endpoint ep1_; + Endpoint ep2_; + RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE; + bool force_relay_; + int selected_candidate_pair_switches_ = 0; + + bool nominated_ = false; +}; + +// The tests have only a few outcomes, which we predefine. +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kLocalUdpToLocalUdp("local", + "udp", + "local", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kLocalUdpToStunUdp("local", + "udp", + "stun", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kLocalUdpToPrflxUdp("local", + "udp", + "prflx", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kPrflxUdpToLocalUdp("prflx", + "udp", + "local", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kStunUdpToLocalUdp("stun", + "udp", + "local", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kStunUdpToStunUdp("stun", + "udp", + "stun", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kStunUdpToPrflxUdp("stun", + "udp", + "prflx", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kPrflxUdpToStunUdp("prflx", + "udp", + "stun", + "udp", + 1000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kLocalUdpToRelayUdp("local", + "udp", + "relay", + "udp", + 2000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kPrflxUdpToRelayUdp("prflx", + "udp", + "relay", + "udp", + 2000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kRelayUdpToPrflxUdp("relay", + "udp", + "prflx", + "udp", + 2000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kLocalTcpToLocalTcp("local", + "tcp", + "local", + "tcp", + 3000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kLocalTcpToPrflxTcp("local", + "tcp", + "prflx", + "tcp", + 3000); +const P2PTransportChannelTestBase::Result + P2PTransportChannelTestBase::kPrflxTcpToLocalTcp("prflx", + "tcp", + "local", + "tcp", + 3000); + +// Test the matrix of all the connectivity types we expect to see in the wild. +// Just test every combination of the configs in the Config enum. +class P2PTransportChannelTest : public P2PTransportChannelTestBase { + public: + P2PTransportChannelTest() : P2PTransportChannelTestBase() {} + explicit P2PTransportChannelTest(absl::string_view field_trials) + : P2PTransportChannelTestBase(field_trials) {} + + protected: + void ConfigureEndpoints(Config config1, + Config config2, + int allocator_flags1, + int allocator_flags2) { + ConfigureEndpoint(0, config1); + SetAllocatorFlags(0, allocator_flags1); + SetAllocationStepDelay(0, kMinimumStepDelay); + ConfigureEndpoint(1, config2); + SetAllocatorFlags(1, allocator_flags2); + SetAllocationStepDelay(1, kMinimumStepDelay); + + set_remote_ice_parameter_source(FROM_SETICEPARAMETERS); + } + void ConfigureEndpoint(int endpoint, Config config) { + switch (config) { + case OPEN: + AddAddress(endpoint, kPublicAddrs[endpoint]); + break; + case NAT_FULL_CONE: + case NAT_ADDR_RESTRICTED: + case NAT_PORT_RESTRICTED: + case NAT_SYMMETRIC: + AddAddress(endpoint, kPrivateAddrs[endpoint]); + // Add a single NAT of the desired type + nat() + ->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint], + static_cast(config - NAT_FULL_CONE)) + ->AddClient(kPrivateAddrs[endpoint]); + break; + case NAT_DOUBLE_CONE: + case NAT_SYMMETRIC_THEN_CONE: + AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]); + // Add a two cascaded NATs of the desired types + nat() + ->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint], + (config == NAT_DOUBLE_CONE) ? rtc::NAT_OPEN_CONE + : rtc::NAT_SYMMETRIC) + ->AddTranslator(kPrivateAddrs[endpoint], + kCascadedNatAddrs[endpoint], rtc::NAT_OPEN_CONE) + ->AddClient(kCascadedPrivateAddrs[endpoint]); + break; + case BLOCK_UDP: + case BLOCK_UDP_AND_INCOMING_TCP: + case BLOCK_ALL_BUT_OUTGOING_HTTP: + case PROXY_HTTPS: + case PROXY_SOCKS: + AddAddress(endpoint, kPublicAddrs[endpoint]); + // Block all UDP + fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, kPublicAddrs[endpoint]); + if (config == BLOCK_UDP_AND_INCOMING_TCP) { + // Block TCP inbound to the endpoint + fw()->AddRule(false, rtc::FP_TCP, SocketAddress(), + kPublicAddrs[endpoint]); + } else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) { + // Block all TCP to/from the endpoint except 80/443 out + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + SocketAddress(rtc::IPAddress(INADDR_ANY), 80)); + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + SocketAddress(rtc::IPAddress(INADDR_ANY), 443)); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + } else if (config == PROXY_HTTPS) { + // Block all TCP to/from the endpoint except to the proxy server + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + kHttpsProxyAddrs[endpoint]); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + SetProxy(endpoint, rtc::PROXY_HTTPS); + } else if (config == PROXY_SOCKS) { + // Block all TCP to/from the endpoint except to the proxy server + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + kSocksProxyAddrs[endpoint]); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + SetProxy(endpoint, rtc::PROXY_SOCKS5); + } + break; + default: + RTC_DCHECK_NOTREACHED(); + break; + } + } +}; + +class P2PTransportChannelTestWithFieldTrials + : public P2PTransportChannelTest, + public WithParamInterface { + public: + P2PTransportChannelTestWithFieldTrials() + : P2PTransportChannelTest(GetParam()) {} +}; + +class P2PTransportChannelMatrixTest + : public P2PTransportChannelTestWithFieldTrials { + protected: + static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS]; +}; + +// Shorthands for use in the test matrix. +#define LULU &kLocalUdpToLocalUdp +#define LUSU &kLocalUdpToStunUdp +#define LUPU &kLocalUdpToPrflxUdp +#define PULU &kPrflxUdpToLocalUdp +#define SULU &kStunUdpToLocalUdp +#define SUSU &kStunUdpToStunUdp +#define SUPU &kStunUdpToPrflxUdp +#define PUSU &kPrflxUdpToStunUdp +#define LURU &kLocalUdpToRelayUdp +#define PURU &kPrflxUdpToRelayUdp +#define RUPU &kRelayUdpToPrflxUdp +#define LTLT &kLocalTcpToLocalTcp +#define LTPT &kLocalTcpToPrflxTcp +#define PTLT &kPrflxTcpToLocalTcp +// TODO(?): Enable these once TestRelayServer can accept external TCP. +#define LTRT NULL +#define LSRS NULL + +// Test matrix. Originator behavior defined by rows, receiever by columns. + +// TODO(?): Fix NULLs caused by lack of TCP support in NATSocket. +// TODO(?): Fix NULLs caused by no HTTP proxy support. +// TODO(?): Rearrange rows/columns from best to worst. +const P2PTransportChannelMatrixTest::Result* + P2PTransportChannelMatrixTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = { + // OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH + // PRXS + /*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS, + NULL, LTPT}, + /*CO*/ + {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, + LTRT}, + /*AD*/ + {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, + LTRT}, + /*PO*/ + {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS, NULL, + LTRT}, + /*SY*/ + {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, + LTRT}, + /*2C*/ + {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, + LTRT}, + /*SC*/ + {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, + LTRT}, + /*!U*/ + {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS, NULL, + LTRT}, + /*!T*/ + {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL, + LTRT}, + /*HT*/ + {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, + LSRS}, + /*PR*/ + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL}, + /*PR*/ + {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, + LTRT}, +}; + +// The actual tests that exercise all the various configurations. +// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE +#define P2P_TEST_DECLARATION(x, y, z) \ + TEST_P(P2PTransportChannelMatrixTest, z##Test##x##To##y) { \ + ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_SOCKET, \ + PORTALLOCATOR_ENABLE_SHARED_SOCKET); \ + if (kMatrix[x][y] != NULL) \ + Test(*kMatrix[x][y]); \ + else \ + RTC_LOG(LS_WARNING) << "Not yet implemented"; \ + } + +#define P2P_TEST(x, y) P2P_TEST_DECLARATION(x, y, /* empty argument */) + +#define P2P_TEST_SET(x) \ + P2P_TEST(x, OPEN) \ + P2P_TEST(x, NAT_FULL_CONE) \ + P2P_TEST(x, NAT_ADDR_RESTRICTED) \ + P2P_TEST(x, NAT_PORT_RESTRICTED) \ + P2P_TEST(x, NAT_SYMMETRIC) \ + P2P_TEST(x, NAT_DOUBLE_CONE) \ + P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \ + P2P_TEST(x, BLOCK_UDP) \ + P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \ + P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \ + P2P_TEST(x, PROXY_HTTPS) \ + P2P_TEST(x, PROXY_SOCKS) + +P2P_TEST_SET(OPEN) +P2P_TEST_SET(NAT_FULL_CONE) +P2P_TEST_SET(NAT_ADDR_RESTRICTED) +P2P_TEST_SET(NAT_PORT_RESTRICTED) +P2P_TEST_SET(NAT_SYMMETRIC) +P2P_TEST_SET(NAT_DOUBLE_CONE) +P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE) +P2P_TEST_SET(BLOCK_UDP) +P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP) +P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP) +P2P_TEST_SET(PROXY_HTTPS) +P2P_TEST_SET(PROXY_SOCKS) + +INSTANTIATE_TEST_SUITE_P( + Legacy, + P2PTransportChannelMatrixTest, + // Each field-trial is ~144 tests (some return not-yet-implemented). + Values("", "WebRTC-IceFieldTrials/enable_goog_ping:true/")); +INSTANTIATE_TEST_SUITE_P( + Active, + P2PTransportChannelMatrixTest, + // Each field-trial is ~144 tests (some return not-yet-implemented). + Values("WebRTC-UseActiveIceController/Enabled/", + "WebRTC-IceFieldTrials/enable_goog_ping:true/" + "WebRTC-UseActiveIceController/Enabled/")); + +INSTANTIATE_TEST_SUITE_P(Legacy, + P2PTransportChannelTestWithFieldTrials, + Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelTestWithFieldTrials, + Values("WebRTC-UseActiveIceController/Enabled/")); + +// Test that we restart candidate allocation when local ufrag&pwd changed. +// Standard Ice protocol is used. +TEST_P(P2PTransportChannelTestWithFieldTrials, HandleUfragPwdChange) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + TestHandleIceUfragPasswordChanged(); + DestroyChannels(); +} + +// Same as above test, but with a symmetric NAT. +// We should end up with relay<->prflx candidate pairs, with generation "1". +TEST_P(P2PTransportChannelTestWithFieldTrials, + HandleUfragPwdChangeSymmetricNat) { + ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + TestHandleIceUfragPasswordChanged(); + DestroyChannels(); +} + +// Test the operation of GetStats. +TEST_P(P2PTransportChannelTestWithFieldTrials, GetStats) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && + ep2_ch1()->writable(), + kMediumTimeout, clock); + // Sends and receives 10 packets. + TestSendRecv(&clock); + + // Try sending a packet which is discarded due to the socket being blocked. + virtual_socket_server()->SetSendingBlocked(true); + const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + int len = static_cast(strlen(data)); + EXPECT_EQ(-1, SendData(ep1_ch1(), data, len)); + + IceTransportStats ice_transport_stats; + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + ASSERT_GE(ice_transport_stats.connection_infos.size(), 1u); + ASSERT_GE(ice_transport_stats.candidate_stats_list.size(), 1u); + EXPECT_EQ(ice_transport_stats.selected_candidate_pair_changes, 1u); + ConnectionInfo* best_conn_info = nullptr; + for (ConnectionInfo& info : ice_transport_stats.connection_infos) { + if (info.best_connection) { + best_conn_info = &info; + break; + } + } + ASSERT_TRUE(best_conn_info != nullptr); + EXPECT_TRUE(best_conn_info->receiving); + EXPECT_TRUE(best_conn_info->writable); + EXPECT_FALSE(best_conn_info->timeout); + // Note that discarded packets are counted in sent_total_packets but not + // sent_total_bytes. + EXPECT_EQ(11U, best_conn_info->sent_total_packets); + EXPECT_EQ(1U, best_conn_info->sent_discarded_packets); + EXPECT_EQ(10 * 36U, best_conn_info->sent_total_bytes); + EXPECT_EQ(36U, best_conn_info->sent_discarded_bytes); + EXPECT_EQ(10 * 36U, best_conn_info->recv_total_bytes); + EXPECT_EQ(10U, best_conn_info->packets_received); + + EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_sent); + EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_received); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, GetStatsSwitchConnection) { + rtc::ScopedFakeClock clock; + IceConfig continual_gathering_config = + CreateIceConfig(1000, GATHER_CONTINUALLY); + + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + + AddAddress(0, kAlternateAddrs[1], "rmnet0", rtc::ADAPTER_TYPE_CELLULAR); + + CreateChannels(continual_gathering_config, continual_gathering_config); + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && + ep2_ch1()->writable(), + kMediumTimeout, clock); + // Sends and receives 10 packets. + TestSendRecv(&clock); + + IceTransportStats ice_transport_stats; + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + ASSERT_GE(ice_transport_stats.connection_infos.size(), 2u); + ASSERT_GE(ice_transport_stats.candidate_stats_list.size(), 2u); + EXPECT_EQ(ice_transport_stats.selected_candidate_pair_changes, 1u); + + ConnectionInfo* best_conn_info = nullptr; + for (ConnectionInfo& info : ice_transport_stats.connection_infos) { + if (info.best_connection) { + best_conn_info = &info; + break; + } + } + ASSERT_TRUE(best_conn_info != nullptr); + EXPECT_TRUE(best_conn_info->receiving); + EXPECT_TRUE(best_conn_info->writable); + EXPECT_FALSE(best_conn_info->timeout); + + EXPECT_EQ(10 * 36U, best_conn_info->sent_total_bytes); + EXPECT_EQ(10 * 36U, best_conn_info->recv_total_bytes); + EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_sent); + EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_received); + + auto old_selected_connection = ep1_ch1()->selected_connection(); + ep1_ch1()->RemoveConnectionForTest( + const_cast(old_selected_connection)); + + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kMediumTimeout, clock); + + // Sends and receives 10 packets. + TestSendRecv(&clock); + + IceTransportStats ice_transport_stats2; + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats2)); + + int64_t sum_bytes_sent = 0; + int64_t sum_bytes_received = 0; + for (ConnectionInfo& info : ice_transport_stats.connection_infos) { + sum_bytes_sent += info.sent_total_bytes; + sum_bytes_received += info.recv_total_bytes; + } + + EXPECT_EQ(10 * 36U, sum_bytes_sent); + EXPECT_EQ(10 * 36U, sum_bytes_received); + + EXPECT_EQ(20 * 36U, ice_transport_stats2.bytes_sent); + EXPECT_EQ(20 * 36U, ice_transport_stats2.bytes_received); + + DestroyChannels(); +} + +// Tests that UMAs are recorded when ICE restarts while the channel +// is disconnected. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestUMAIceRestartWhileDisconnected) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + + // Drop all packets so that both channels become not writable. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + const int kWriteTimeoutDelay = 8000; + EXPECT_TRUE_SIMULATED_WAIT(!ep1_ch1()->writable() && !ep2_ch1()->writable(), + kWriteTimeoutDelay, clock); + + ep1_ch1()->SetIceParameters(kIceParams[2]); + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep1_ch1()->MaybeStartGathering(); + EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRestartState", + static_cast(IceRestartState::DISCONNECTED))); + + ep2_ch1()->SetIceParameters(kIceParams[3]); + ep2_ch1()->SetRemoteIceParameters(kIceParams[2]); + ep2_ch1()->MaybeStartGathering(); + EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRestartState", + static_cast(IceRestartState::DISCONNECTED))); + + DestroyChannels(); +} + +// Tests that UMAs are recorded when ICE restarts while the channel +// is connected. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestUMAIceRestartWhileConnected) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + + ep1_ch1()->SetIceParameters(kIceParams[2]); + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep1_ch1()->MaybeStartGathering(); + EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRestartState", + static_cast(IceRestartState::CONNECTED))); + + ep2_ch1()->SetIceParameters(kIceParams[3]); + ep2_ch1()->SetRemoteIceParameters(kIceParams[2]); + ep2_ch1()->MaybeStartGathering(); + EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRestartState", + static_cast(IceRestartState::CONNECTED))); + + DestroyChannels(); +} + +// Tests that UMAs are recorded when ICE restarts while the channel +// is connecting. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestUMAIceRestartWhileConnecting) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + + // Create the channels without waiting for them to become connected. + CreateChannels(); + + ep1_ch1()->SetIceParameters(kIceParams[2]); + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep1_ch1()->MaybeStartGathering(); + EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRestartState", + static_cast(IceRestartState::CONNECTING))); + + ep2_ch1()->SetIceParameters(kIceParams[3]); + ep2_ch1()->SetRemoteIceParameters(kIceParams[2]); + ep2_ch1()->MaybeStartGathering(); + EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRestartState", + static_cast(IceRestartState::CONNECTING))); + + DestroyChannels(); +} + +// Tests that a UMA on ICE regathering is recorded when there is a network +// change if and only if continual gathering is enabled. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestIceRegatheringReasonContinualGatheringByNetworkChange) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + + // ep1 gathers continually but ep2 does not. + IceConfig continual_gathering_config = + CreateIceConfig(1000, GATHER_CONTINUALLY); + IceConfig default_config; + CreateChannels(continual_gathering_config, default_config); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + + // Adding address in ep1 will trigger continual gathering. + AddAddress(0, kAlternateAddrs[0]); + EXPECT_EQ_SIMULATED_WAIT(1, + GetEndpoint(0)->GetIceRegatheringCountForReason( + IceRegatheringReason::NETWORK_CHANGE), + kDefaultTimeout, clock); + + ep2_ch1()->SetIceParameters(kIceParams[3]); + ep2_ch1()->SetRemoteIceParameters(kIceParams[2]); + ep2_ch1()->MaybeStartGathering(); + + AddAddress(1, kAlternateAddrs[1]); + SIMULATED_WAIT(false, kDefaultTimeout, clock); + // ep2 has not enabled continual gathering. + EXPECT_EQ(0, GetEndpoint(1)->GetIceRegatheringCountForReason( + IceRegatheringReason::NETWORK_CHANGE)); + + DestroyChannels(); +} + +// Tests that a UMA on ICE regathering is recorded when there is a network +// failure if and only if continual gathering is enabled. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestIceRegatheringReasonContinualGatheringByNetworkFailure) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + + // ep1 gathers continually but ep2 does not. + IceConfig config1 = CreateIceConfig(1000, GATHER_CONTINUALLY); + config1.regather_on_failed_networks_interval = 2000; + IceConfig config2; + config2.regather_on_failed_networks_interval = 2000; + CreateChannels(config1, config2); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + // Timeout value such that all connections are deleted. + const int kNetworkFailureTimeout = 35000; + SIMULATED_WAIT(false, kNetworkFailureTimeout, clock); + EXPECT_LE(1, GetEndpoint(0)->GetIceRegatheringCountForReason( + IceRegatheringReason::NETWORK_FAILURE)); + EXPECT_METRIC_LE( + 1, webrtc::metrics::NumEvents( + "WebRTC.PeerConnection.IceRegatheringReason", + static_cast(IceRegatheringReason::NETWORK_FAILURE))); + EXPECT_EQ(0, GetEndpoint(1)->GetIceRegatheringCountForReason( + IceRegatheringReason::NETWORK_FAILURE)); + + DestroyChannels(); +} + +// Test that we properly create a connection on a STUN ping from unknown address +// when the signaling is slow. +TEST_P(P2PTransportChannelTestWithFieldTrials, + PeerReflexiveCandidateBeforeSignaling) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // Emulate no remote parameters coming in. + set_remote_ice_parameter_source(FROM_CANDIDATE); + 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. + PauseCandidates(1); + + // Wait until the callee becomes writable to make sure that a ping request is + // received by the caller before their remote ICE credentials are set. + ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout); + // Add two sets of remote ICE credentials, so that the ones used by the + // candidate will be generation 1 instead of 0. + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep1_ch1()->SetRemoteIceParameters(kIceParams[1]); + // The caller should have the selected connection connected to the peer + // reflexive candidate. + const Connection* selected_connection = nullptr; + ASSERT_TRUE_WAIT( + (selected_connection = ep1_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()); + EXPECT_EQ(1u, selected_connection->remote_candidate().generation()); + + ResumeCandidates(1); + // Verify ep1's selected connection is updated to use the 'local' candidate. + EXPECT_EQ_WAIT(LOCAL_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type(), + kMediumTimeout); + EXPECT_EQ(selected_connection, ep1_ch1()->selected_connection()); + DestroyChannels(); +} + +// Test that if we learn a prflx remote candidate, its address is concealed in +// 1. the selected candidate pair accessed via the public API, and +// 2. the candidate pair stats +// until we learn the same address from signaling. +TEST_P(P2PTransportChannelTestWithFieldTrials, + PeerReflexiveRemoteCandidateIsSanitized) { + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + // Emulate no remote parameters coming in. + set_remote_ice_parameter_source(FROM_CANDIDATE); + 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. + PauseCandidates(1); + + ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout); + ep1_ch1()->SetRemoteIceParameters(kIceParams[1]); + ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout); + + // Check the selected candidate pair. + auto pair_ep1 = ep1_ch1()->GetSelectedCandidatePair(); + ASSERT_TRUE(pair_ep1.has_value()); + EXPECT_EQ(PRFLX_PORT_TYPE, pair_ep1->remote_candidate().type()); + EXPECT_TRUE(pair_ep1->remote_candidate().address().ipaddr().IsNil()); + + IceTransportStats ice_transport_stats; + ep1_ch1()->GetStats(&ice_transport_stats); + // Check the candidate pair stats. + ASSERT_EQ(1u, ice_transport_stats.connection_infos.size()); + EXPECT_EQ(PRFLX_PORT_TYPE, + ice_transport_stats.connection_infos[0].remote_candidate.type()); + EXPECT_TRUE(ice_transport_stats.connection_infos[0] + .remote_candidate.address() + .ipaddr() + .IsNil()); + + // Let ep1 receive the remote candidate to update its type from prflx to host. + ResumeCandidates(1); + ASSERT_TRUE_WAIT( + ep1_ch1()->selected_connection() != nullptr && + ep1_ch1()->selected_connection()->remote_candidate().type() == + LOCAL_PORT_TYPE, + kMediumTimeout); + + // We should be able to reveal the address after it is learnt via + // AddIceCandidate. + // + // Check the selected candidate pair. + auto updated_pair_ep1 = ep1_ch1()->GetSelectedCandidatePair(); + ASSERT_TRUE(updated_pair_ep1.has_value()); + EXPECT_EQ(LOCAL_PORT_TYPE, updated_pair_ep1->remote_candidate().type()); + EXPECT_TRUE(HasRemoteAddress(&updated_pair_ep1.value(), kPublicAddrs[1])); + + ep1_ch1()->GetStats(&ice_transport_stats); + // Check the candidate pair stats. + ASSERT_EQ(1u, ice_transport_stats.connection_infos.size()); + EXPECT_EQ(LOCAL_PORT_TYPE, + ice_transport_stats.connection_infos[0].remote_candidate.type()); + EXPECT_TRUE(ice_transport_stats.connection_infos[0] + .remote_candidate.address() + .EqualIPs(kPublicAddrs[1])); + + DestroyChannels(); +} + +// Test that we properly create a connection on a STUN ping from unknown address +// when the signaling is slow and the end points are behind NAT. +TEST_P(P2PTransportChannelTestWithFieldTrials, + PeerReflexiveCandidateBeforeSignalingWithNAT) { + ConfigureEndpoints(OPEN, NAT_SYMMETRIC, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // Emulate no remote parameters coming in. + set_remote_ice_parameter_source(FROM_CANDIDATE); + 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. + PauseCandidates(1); + + // Wait until the callee becomes writable to make sure that a ping request is + // received by the caller before their remote ICE credentials are set. + ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout); + // Add two sets of remote ICE credentials, so that the ones used by the + // candidate will be generation 1 instead of 0. + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep1_ch1()->SetRemoteIceParameters(kIceParams[1]); + + // The caller's selected connection should be connected to the peer reflexive + // candidate. + const Connection* selected_connection = nullptr; + ASSERT_TRUE_WAIT( + (selected_connection = ep1_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()); + EXPECT_EQ(1u, selected_connection->remote_candidate().generation()); + + ResumeCandidates(1); + + EXPECT_EQ_WAIT(PRFLX_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type(), + kMediumTimeout); + EXPECT_EQ(selected_connection, ep1_ch1()->selected_connection()); + DestroyChannels(); +} + +// Test that we properly create a connection on a STUN ping from unknown address +// when the signaling is slow, even if the new candidate is created due to the +// remote peer doing an ICE restart, pairing this candidate across generations. +// +// Previously this wasn't working due to a bug where the peer reflexive +// candidate was only updated for the newest generation candidate pairs, and +// not older-generation candidate pairs created by pairing candidates across +// generations. This resulted in the old-generation prflx candidate being +// prioritized above new-generation candidate pairs. +TEST_P(P2PTransportChannelTestWithFieldTrials, + PeerReflexiveCandidateBeforeSignalingWithIceRestart) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // Only gather relay candidates, so that when the prflx candidate arrives + // it's prioritized above the current candidate pair. + GetEndpoint(0)->allocator_->SetCandidateFilter(CF_RELAY); + GetEndpoint(1)->allocator_->SetCandidateFilter(CF_RELAY); + // Setting this allows us to control when SetRemoteIceParameters is called. + set_remote_ice_parameter_source(FROM_CANDIDATE); + CreateChannels(); + // Wait for the initial connection to be made. + ep1_ch1()->SetRemoteIceParameters(kIceParams[1]); + ep2_ch1()->SetRemoteIceParameters(kIceParams[0]); + EXPECT_TRUE_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), kDefaultTimeout); + + // Simulate an ICE restart on ep2, but don't signal the candidate or new + // ICE parameters until after a prflx connection has been made. + PauseCandidates(1); + ep2_ch1()->SetIceParameters(kIceParams[3]); + + ep1_ch1()->SetRemoteIceParameters(kIceParams[3]); + ep2_ch1()->MaybeStartGathering(); + + // The caller should have the selected connection connected to the peer + // reflexive candidate. + EXPECT_EQ_WAIT(PRFLX_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type(), + kDefaultTimeout); + const Connection* prflx_selected_connection = + ep1_ch1()->selected_connection(); + + // Now simulate the ICE restart on ep1. + ep1_ch1()->SetIceParameters(kIceParams[2]); + + ep2_ch1()->SetRemoteIceParameters(kIceParams[2]); + ep1_ch1()->MaybeStartGathering(); + + // Finally send the candidates from ep2's ICE restart and verify that ep1 uses + // their information to update the peer reflexive candidate. + ResumeCandidates(1); + + EXPECT_EQ_WAIT(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type(), + kDefaultTimeout); + EXPECT_EQ(prflx_selected_connection, ep1_ch1()->selected_connection()); + DestroyChannels(); +} + +// Test that if remote candidates don't have ufrag and pwd, we still work. +TEST_P(P2PTransportChannelTestWithFieldTrials, + RemoteCandidatesWithoutUfragPwd) { + rtc::ScopedFakeClock clock; + set_remote_ice_parameter_source(FROM_SETICEPARAMETERS); + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + const Connection* selected_connection = NULL; + // Wait until the callee's connections are created. + EXPECT_TRUE_SIMULATED_WAIT( + (selected_connection = ep2_ch1()->selected_connection()) != NULL, + kMediumTimeout, clock); + // Wait to make sure the selected connection is not changed. + SIMULATED_WAIT(ep2_ch1()->selected_connection() != selected_connection, + kShortTimeout, clock); + EXPECT_TRUE(ep2_ch1()->selected_connection() == selected_connection); + DestroyChannels(); +} + +// Test that a host behind NAT cannot be reached when incoming_only +// is set to true. +TEST_P(P2PTransportChannelTestWithFieldTrials, IncomingOnlyBlocked) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(NAT_FULL_CONE, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + + SetAllocatorFlags(0, kOnlyLocalPorts); + CreateChannels(); + ep1_ch1()->set_incoming_only(true); + + // Pump for 1 second and verify that the channels are not connected. + SIMULATED_WAIT(false, kShortTimeout, clock); + + EXPECT_FALSE(ep1_ch1()->receiving()); + EXPECT_FALSE(ep1_ch1()->writable()); + EXPECT_FALSE(ep2_ch1()->receiving()); + EXPECT_FALSE(ep2_ch1()->writable()); + + DestroyChannels(); +} + +// Test that a peer behind NAT can connect to a peer that has +// incoming_only flag set. +TEST_P(P2PTransportChannelTestWithFieldTrials, IncomingOnlyOpen) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, NAT_FULL_CONE, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + + SetAllocatorFlags(0, kOnlyLocalPorts); + CreateChannels(); + ep1_ch1()->set_incoming_only(true); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + + DestroyChannels(); +} + +// Test that two peers can connect when one can only make outgoing TCP +// connections. This has been observed in some scenarios involving +// VPNs/firewalls. +TEST_P(P2PTransportChannelTestWithFieldTrials, + CanOnlyMakeOutgoingTcpConnections) { + // The PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS flag is required if the + // application needs this use case to work, since the application must accept + // the tradeoff that more candidates need to be allocated. + // + // TODO(deadbeef): Later, make this flag the default, and do more elegant + // things to ensure extra candidates don't waste resources? + ConfigureEndpoints( + OPEN, OPEN, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS, + kDefaultPortAllocatorFlags); + // In order to simulate nothing working but outgoing TCP connections, prevent + // the endpoint from binding to its interface's address as well as the + // "any" addresses. It can then only make a connection by using "Connect()". + fw()->SetUnbindableIps({rtc::GetAnyIP(AF_INET), rtc::GetAnyIP(AF_INET6), + kPublicAddrs[0].ipaddr()}); + CreateChannels(); + // Expect a "prflx" candidate on the side that can only make outgoing + // connections, endpoint 0. + Test(kPrflxTcpToLocalTcp); + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestTcpConnectionsFromActiveToPassive) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + int kOnlyLocalTcpPorts = PORTALLOCATOR_DISABLE_UDP | + PORTALLOCATOR_DISABLE_STUN | + PORTALLOCATOR_DISABLE_RELAY; + // Disable all protocols except TCP. + SetAllocatorFlags(0, kOnlyLocalTcpPorts); + SetAllocatorFlags(1, kOnlyLocalTcpPorts); + + SetAllowTcpListen(0, true); // actpass. + SetAllowTcpListen(1, false); // active. + + // We want SetRemoteIceParameters to be called as it normally would. + // Otherwise we won't know what parameters to use for the expected + // prflx TCP candidates. + set_remote_ice_parameter_source(FROM_SETICEPARAMETERS); + + // Pause candidate so we could verify the candidate properties. + PauseCandidates(0); + PauseCandidates(1); + CreateChannels(); + + // Verify tcp candidates. + VerifySavedTcpCandidates(0, TCPTYPE_PASSIVE_STR); + VerifySavedTcpCandidates(1, TCPTYPE_ACTIVE_STR); + + // Resume candidates. + ResumeCandidates(0); + ResumeCandidates(1); + + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kShortTimeout, clock); + + TestSendRecv(&clock); + DestroyChannels(); +} + +// Test that tcptype is set on all candidates for a connection running over TCP. +TEST_P(P2PTransportChannelTestWithFieldTrials, TestTcpConnectionTcptypeSet) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(BLOCK_UDP_AND_INCOMING_TCP, OPEN, + PORTALLOCATOR_ENABLE_SHARED_SOCKET, + PORTALLOCATOR_ENABLE_SHARED_SOCKET); + + SetAllowTcpListen(0, false); // active. + SetAllowTcpListen(1, true); // actpass. + CreateChannels(); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + SIMULATED_WAIT(false, kDefaultTimeout, clock); + + EXPECT_EQ(RemoteCandidate(ep1_ch1())->tcptype(), "passive"); + EXPECT_EQ(LocalCandidate(ep1_ch1())->tcptype(), "active"); + EXPECT_EQ(RemoteCandidate(ep2_ch1())->tcptype(), "active"); + EXPECT_EQ(LocalCandidate(ep2_ch1())->tcptype(), "passive"); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, TestIceRoleConflict) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + TestSignalRoleConflict(); +} + +// Tests that the ice configs (protocol, tiebreaker and role) can be passed +// down to ports. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestIceConfigWillPassDownToPort) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + // Give the first connection the higher tiebreaker so its role won't + // change unless we tell it to. + SetIceRole(0, ICEROLE_CONTROLLING); + SetIceTiebreaker(0, kHighTiebreaker); + SetIceRole(1, ICEROLE_CONTROLLING); + SetIceTiebreaker(1, kLowTiebreaker); + + CreateChannels(); + + EXPECT_EQ_SIMULATED_WAIT(2u, ep1_ch1()->ports().size(), kShortTimeout, clock); + + const std::vector ports_before = ep1_ch1()->ports(); + for (size_t i = 0; i < ports_before.size(); ++i) { + EXPECT_EQ(ICEROLE_CONTROLLING, ports_before[i]->GetIceRole()); + EXPECT_EQ(kHighTiebreaker, ports_before[i]->IceTiebreaker()); + } + + ep1_ch1()->SetIceRole(ICEROLE_CONTROLLED); + ep1_ch1()->SetIceTiebreaker(kLowTiebreaker); + + const std::vector ports_after = ep1_ch1()->ports(); + for (size_t i = 0; i < ports_after.size(); ++i) { + EXPECT_EQ(ICEROLE_CONTROLLED, ports_before[i]->GetIceRole()); + // SetIceTiebreaker after ports have been created will fail. So expect the + // original value. + EXPECT_EQ(kHighTiebreaker, ports_before[i]->IceTiebreaker()); + } + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kShortTimeout, clock); + + EXPECT_TRUE(ep1_ch1()->selected_connection() && + ep2_ch1()->selected_connection()); + + TestSendRecv(&clock); + DestroyChannels(); +} + +// Verify that we can set DSCP value and retrieve properly from P2PTC. +TEST_P(P2PTransportChannelTestWithFieldTrials, TestDefaultDscpValue) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + CreateChannels(); + EXPECT_EQ(rtc::DSCP_NO_CHANGE, GetEndpoint(0)->cd1_.ch_->DefaultDscpValue()); + EXPECT_EQ(rtc::DSCP_NO_CHANGE, GetEndpoint(1)->cd1_.ch_->DefaultDscpValue()); + GetEndpoint(0)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6); + GetEndpoint(1)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6); + EXPECT_EQ(rtc::DSCP_CS6, GetEndpoint(0)->cd1_.ch_->DefaultDscpValue()); + EXPECT_EQ(rtc::DSCP_CS6, GetEndpoint(1)->cd1_.ch_->DefaultDscpValue()); + GetEndpoint(0)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41); + GetEndpoint(1)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41); + EXPECT_EQ(rtc::DSCP_AF41, GetEndpoint(0)->cd1_.ch_->DefaultDscpValue()); + EXPECT_EQ(rtc::DSCP_AF41, GetEndpoint(1)->cd1_.ch_->DefaultDscpValue()); + DestroyChannels(); +} + +// Verify IPv6 connection is preferred over IPv4. +TEST_P(P2PTransportChannelTestWithFieldTrials, TestIPv6Connections) { + rtc::ScopedFakeClock clock; + AddAddress(0, kIPv6PublicAddrs[0]); + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kIPv6PublicAddrs[1]); + AddAddress(1, kPublicAddrs[1]); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + // Enable IPv6 + SetAllocatorFlags( + 0, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI); + SetAllocatorFlags( + 1, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI); + + CreateChannels(); + + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kIPv6PublicAddrs[0], + kIPv6PublicAddrs[1]), + kShortTimeout, clock); + + TestSendRecv(&clock); + DestroyChannels(); +} + +// Testing forceful TURN connections. +TEST_P(P2PTransportChannelTestWithFieldTrials, TestForceTurn) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints( + NAT_PORT_RESTRICTED, NAT_SYMMETRIC, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET); + set_force_relay(true); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + CreateChannels(); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + + EXPECT_TRUE(ep1_ch1()->selected_connection() && + ep2_ch1()->selected_connection()); + + EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type()); + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep2_ch1())->type()); + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep2_ch1())->type()); + + TestSendRecv(&clock); + DestroyChannels(); +} + +// Test that if continual gathering is set to true, ICE gathering state will +// not change to "Complete", and vice versa. +TEST_P(P2PTransportChannelTestWithFieldTrials, TestContinualGathering) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + SetAllocationStepDelay(0, kDefaultStepDelay); + SetAllocationStepDelay(1, kDefaultStepDelay); + IceConfig continual_gathering_config = + CreateIceConfig(1000, GATHER_CONTINUALLY); + // By default, ep2 does not gather continually. + IceConfig default_config; + CreateChannels(continual_gathering_config, default_config); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + SIMULATED_WAIT( + IceGatheringState::kIceGatheringComplete == ep1_ch1()->gathering_state(), + kShortTimeout, clock); + EXPECT_EQ(IceGatheringState::kIceGatheringGathering, + ep1_ch1()->gathering_state()); + // By now, ep2 should have completed gathering. + EXPECT_EQ(IceGatheringState::kIceGatheringComplete, + ep2_ch1()->gathering_state()); + + DestroyChannels(); +} + +// Test that a connection succeeds when the P2PTransportChannel uses a pooled +// PortAllocatorSession that has not yet finished gathering candidates. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestUsingPooledSessionBeforeDoneGathering) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // First create a pooled session for each endpoint. + auto& allocator_1 = GetEndpoint(0)->allocator_; + auto& allocator_2 = GetEndpoint(1)->allocator_; + int pool_size = 1; + allocator_1->SetConfiguration(allocator_1->stun_servers(), + allocator_1->turn_servers(), pool_size, + webrtc::NO_PRUNE); + allocator_2->SetConfiguration(allocator_2->stun_servers(), + allocator_2->turn_servers(), pool_size, + webrtc::NO_PRUNE); + const PortAllocatorSession* pooled_session_1 = + allocator_1->GetPooledSession(); + const PortAllocatorSession* pooled_session_2 = + allocator_2->GetPooledSession(); + ASSERT_NE(nullptr, pooled_session_1); + ASSERT_NE(nullptr, pooled_session_2); + // Sanity check that pooled sessions haven't gathered anything yet. + EXPECT_TRUE(pooled_session_1->ReadyPorts().empty()); + EXPECT_TRUE(pooled_session_1->ReadyCandidates().empty()); + EXPECT_TRUE(pooled_session_2->ReadyPorts().empty()); + EXPECT_TRUE(pooled_session_2->ReadyCandidates().empty()); + // Now let the endpoints connect and try exchanging some data. + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + TestSendRecv(&clock); + // Make sure the P2PTransportChannels are actually using ports from the + // pooled sessions. + auto pooled_ports_1 = pooled_session_1->ReadyPorts(); + auto pooled_ports_2 = pooled_session_2->ReadyPorts(); + EXPECT_THAT(pooled_ports_1, + Contains(ep1_ch1()->selected_connection()->PortForTest())); + EXPECT_THAT(pooled_ports_2, + Contains(ep2_ch1()->selected_connection()->PortForTest())); + DestroyChannels(); +} + +// Test that a connection succeeds when the P2PTransportChannel uses a pooled +// PortAllocatorSession that already finished gathering candidates. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestUsingPooledSessionAfterDoneGathering) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // First create a pooled session for each endpoint. + auto& allocator_1 = GetEndpoint(0)->allocator_; + auto& allocator_2 = GetEndpoint(1)->allocator_; + int pool_size = 1; + allocator_1->SetConfiguration(allocator_1->stun_servers(), + allocator_1->turn_servers(), pool_size, + webrtc::NO_PRUNE); + allocator_2->SetConfiguration(allocator_2->stun_servers(), + allocator_2->turn_servers(), pool_size, + webrtc::NO_PRUNE); + const PortAllocatorSession* pooled_session_1 = + allocator_1->GetPooledSession(); + const PortAllocatorSession* pooled_session_2 = + allocator_2->GetPooledSession(); + ASSERT_NE(nullptr, pooled_session_1); + ASSERT_NE(nullptr, pooled_session_2); + // Wait for the pooled sessions to finish gathering before the + // P2PTransportChannels try to use them. + EXPECT_TRUE_SIMULATED_WAIT(pooled_session_1->CandidatesAllocationDone() && + pooled_session_2->CandidatesAllocationDone(), + kDefaultTimeout, clock); + // Now let the endpoints connect and try exchanging some data. + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + TestSendRecv(&clock); + // Make sure the P2PTransportChannels are actually using ports from the + // pooled sessions. + auto pooled_ports_1 = pooled_session_1->ReadyPorts(); + auto pooled_ports_2 = pooled_session_2->ReadyPorts(); + EXPECT_THAT(pooled_ports_1, + Contains(ep1_ch1()->selected_connection()->PortForTest())); + EXPECT_THAT(pooled_ports_2, + Contains(ep2_ch1()->selected_connection()->PortForTest())); + DestroyChannels(); +} + +// Test that when the "presume_writable_when_fully_relayed" flag is set to +// true and there's a TURN-TURN candidate pair, it's presumed to be writable +// as soon as it's created. +// TODO(deadbeef): Move this and other "presumed writable" tests into a test +// class that operates on a single P2PTransportChannel, once an appropriate one +// (which supports TURN servers and TURN candidate gathering) is available. +TEST_P(P2PTransportChannelTestWithFieldTrials, TurnToTurnPresumedWritable) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // Only configure one channel so we can control when the remote candidate + // is added. + GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[0], kIceParams[1]); + IceConfig config; + config.presume_writable_when_fully_relayed = true; + ep1_ch1()->SetIceConfig(config); + ep1_ch1()->MaybeStartGathering(); + EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete, + ep1_ch1()->gathering_state(), kDefaultTimeout); + // Add two remote candidates; a host candidate (with higher priority) + // and TURN candidate. + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 0)); + // Expect that the TURN-TURN candidate pair will be prioritized since it's + // "probably writable". + EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kShortTimeout); + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type()); + // Also expect that the channel instantly indicates that it's writable since + // it has a TURN-TURN pair. + EXPECT_TRUE(ep1_ch1()->writable()); + EXPECT_TRUE(GetEndpoint(0)->ready_to_send_); + // Also make sure we can immediately send packets. + const char* data = "test"; + int len = static_cast(strlen(data)); + EXPECT_EQ(len, SendData(ep1_ch1(), data, len)); + // Prevent pending messages to access endpoints after their destruction. + DestroyChannels(); +} + +// Test that a TURN/peer reflexive candidate pair is also presumed writable. +TEST_P(P2PTransportChannelTestWithFieldTrials, TurnToPrflxPresumedWritable) { + rtc::ScopedFakeClock fake_clock; + + // We need to add artificial network delay to verify that the connection + // is presumed writable before it's actually writable. Without this delay + // it would become writable instantly. + virtual_socket_server()->set_delay_mean(50); + virtual_socket_server()->UpdateDelayDistribution(); + + ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // We want the remote TURN candidate to show up as prflx. To do this we need + // to configure the server to accept packets from an address we haven't + // explicitly installed permission for. + test_turn_server()->set_enable_permission_checks(false); + IceConfig config; + config.presume_writable_when_fully_relayed = true; + GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[0], kIceParams[1]); + GetEndpoint(1)->cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[1], kIceParams[0]); + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + // Don't signal candidates from channel 2, so that channel 1 sees the TURN + // candidate as peer reflexive. + PauseCandidates(1); + ep1_ch1()->MaybeStartGathering(); + ep2_ch1()->MaybeStartGathering(); + + // Wait for the TURN<->prflx connection. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable(), + kShortTimeout, fake_clock); + ASSERT_NE(nullptr, ep1_ch1()->selected_connection()); + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(PRFLX_PORT_TYPE, RemoteCandidate(ep1_ch1())->type()); + // Make sure that at this point the connection is only presumed writable, + // not fully writable. + EXPECT_FALSE(ep1_ch1()->selected_connection()->writable()); + + // Now wait for it to actually become writable. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->writable(), + kShortTimeout, fake_clock); + + // Explitly destroy channels, before fake clock is destroyed. + DestroyChannels(); +} + +// Test that a presumed-writable TURN<->TURN connection is preferred above an +// unreliable connection (one that has failed to be pinged for some time). +TEST_P(P2PTransportChannelTestWithFieldTrials, + PresumedWritablePreferredOverUnreliable) { + rtc::ScopedFakeClock fake_clock; + + ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + IceConfig config; + config.presume_writable_when_fully_relayed = true; + GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[0], kIceParams[1]); + GetEndpoint(1)->cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[1], kIceParams[0]); + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + ep1_ch1()->MaybeStartGathering(); + ep2_ch1()->MaybeStartGathering(); + // Wait for initial connection as usual. + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kShortTimeout, fake_clock); + const Connection* old_selected_connection = ep1_ch1()->selected_connection(); + // Destroy the second channel and wait for the current connection on the + // first channel to become "unreliable", making it no longer writable. + GetEndpoint(1)->cd1_.ch_.reset(); + EXPECT_TRUE_SIMULATED_WAIT(!ep1_ch1()->writable(), kDefaultTimeout, + fake_clock); + EXPECT_NE(nullptr, ep1_ch1()->selected_connection()); + // Add a remote TURN candidate. The first channel should still have a TURN + // port available to make a TURN<->TURN pair that's presumed writable. + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 0)); + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type()); + EXPECT_TRUE(ep1_ch1()->writable()); + EXPECT_TRUE(GetEndpoint(0)->ready_to_send_); + EXPECT_NE(old_selected_connection, ep1_ch1()->selected_connection()); + // Explitly destroy channels, before fake clock is destroyed. + DestroyChannels(); +} + +// Ensure that "SignalReadyToSend" is fired as expected with a "presumed +// writable" connection. Previously this did not work. +TEST_P(P2PTransportChannelTestWithFieldTrials, + SignalReadyToSendWithPresumedWritable) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + // Only test one endpoint, so we can ensure the connection doesn't receive a + // binding response and advance beyond being "presumed" writable. + GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[0], kIceParams[1]); + IceConfig config; + config.presume_writable_when_fully_relayed = true; + ep1_ch1()->SetIceConfig(config); + ep1_ch1()->MaybeStartGathering(); + EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete, + ep1_ch1()->gathering_state(), kDefaultTimeout); + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 0)); + // Sanity checking the type of the connection. + EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kShortTimeout); + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type()); + + // Tell the socket server to block packets (returning EWOULDBLOCK). + virtual_socket_server()->SetSendingBlocked(true); + const char* data = "test"; + int len = static_cast(strlen(data)); + EXPECT_EQ(-1, SendData(ep1_ch1(), data, len)); + + // Reset `ready_to_send_` flag, which is set to true if the event fires as it + // should. + GetEndpoint(0)->ready_to_send_ = false; + virtual_socket_server()->SetSendingBlocked(false); + EXPECT_TRUE(GetEndpoint(0)->ready_to_send_); + EXPECT_EQ(len, SendData(ep1_ch1(), data, len)); + DestroyChannels(); +} + +// Test that role conflict error responses are sent as expected when receiving a +// ping from an unknown address over a TURN connection. Regression test for +// crbug.com/webrtc/9034. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TurnToPrflxSelectedAfterResolvingIceControllingRoleConflict) { + rtc::ScopedFakeClock clock; + // Gather only relay candidates. + ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, + kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_UDP | + PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_TCP, + kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_UDP | + PORTALLOCATOR_DISABLE_STUN | + PORTALLOCATOR_DISABLE_TCP); + // With conflicting ICE roles, endpoint 1 has the higher tie breaker and will + // send a binding error response. + SetIceRole(0, ICEROLE_CONTROLLING); + SetIceTiebreaker(0, kHighTiebreaker); + SetIceRole(1, ICEROLE_CONTROLLING); + SetIceTiebreaker(1, kLowTiebreaker); + // We want the remote TURN candidate to show up as prflx. To do this we need + // to configure the server to accept packets from an address we haven't + // explicitly installed permission for. + test_turn_server()->set_enable_permission_checks(false); + GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[0], kIceParams[1]); + GetEndpoint(1)->cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[1], kIceParams[0]); + // Don't signal candidates from channel 2, so that channel 1 sees the TURN + // candidate as peer reflexive. + PauseCandidates(1); + ep1_ch1()->MaybeStartGathering(); + ep2_ch1()->MaybeStartGathering(); + + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable(), + kMediumTimeout, clock); + + ASSERT_NE(nullptr, ep1_ch1()->selected_connection()); + + EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(PRFLX_PORT_TYPE, RemoteCandidate(ep1_ch1())->type()); + + DestroyChannels(); +} + +// Test that the writability can be established with the piggyback +// acknowledgement in the connectivity check from the remote peer. +TEST_P(P2PTransportChannelTestWithFieldTrials, + CanConnectWithPiggybackCheckAcknowledgementWhenCheckResponseBlocked) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, "WebRTC-PiggybackIceCheckAcknowledgement/Enabled/"); + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts); + IceConfig ep1_config; + IceConfig ep2_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + // Let ep2 be tolerable of the loss of connectivity checks, so that it keeps + // sending pings even after ep1 becomes unwritable as we configure the + // firewall below. + ep2_config.receiving_timeout = 30 * 1000; + ep2_config.ice_unwritable_timeout = 30 * 1000; + ep2_config.ice_unwritable_min_checks = 30; + ep2_config.ice_inactive_timeout = 60 * 1000; + + CreateChannels(ep1_config, ep2_config); + + // Wait until both sides become writable for the first time. + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + // Block the ingress traffic to ep1 so that there is no check response from + // ep2. + ASSERT_NE(nullptr, LocalCandidate(ep1_ch1())); + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_IN, + LocalCandidate(ep1_ch1())->address()); + // Wait until ep1 becomes unwritable. At the same time ep2 should be still + // fine so that it will keep sending pings. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1() != nullptr && !ep1_ch1()->writable(), + kDefaultTimeout, clock); + EXPECT_TRUE(ep2_ch1() != nullptr && ep2_ch1()->writable()); + // Now let the pings from ep2 to flow but block any pings from ep1, so that + // ep1 can only become writable again after receiving an incoming ping from + // ep2 with piggyback acknowledgement of its previously sent pings. Note + // though that ep1 should have stopped sending pings after becoming unwritable + // in the current design. + fw()->ClearRules(); + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_OUT, + LocalCandidate(ep1_ch1())->address()); + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1() != nullptr && ep1_ch1()->writable(), + kDefaultTimeout, clock); + DestroyChannels(); +} + +// Test what happens when we have 2 users behind the same NAT. This can lead +// to interesting behavior because the STUN server will only give out the +// address of the outermost NAT. +class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase, + public WithParamInterface { + public: + P2PTransportChannelSameNatTest() : P2PTransportChannelTestBase(GetParam()) {} + + protected: + void ConfigureEndpoints(Config nat_type, Config config1, Config config2) { + RTC_CHECK_GE(nat_type, NAT_FULL_CONE); + RTC_CHECK_LE(nat_type, NAT_SYMMETRIC); + rtc::NATSocketServer::Translator* outer_nat = nat()->AddTranslator( + kPublicAddrs[0], kNatAddrs[0], + static_cast(nat_type - NAT_FULL_CONE)); + ConfigureEndpoint(outer_nat, 0, config1); + ConfigureEndpoint(outer_nat, 1, config2); + set_remote_ice_parameter_source(FROM_SETICEPARAMETERS); + } + void ConfigureEndpoint(rtc::NATSocketServer::Translator* nat, + int endpoint, + Config config) { + RTC_CHECK(config <= NAT_SYMMETRIC); + if (config == OPEN) { + AddAddress(endpoint, kPrivateAddrs[endpoint]); + nat->AddClient(kPrivateAddrs[endpoint]); + } else { + AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]); + nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint], + static_cast(config - NAT_FULL_CONE)) + ->AddClient(kCascadedPrivateAddrs[endpoint]); + } + } +}; + +INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelSameNatTest, Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelSameNatTest, + Values("WebRTC-UseActiveIceController/Enabled/")); + +TEST_P(P2PTransportChannelSameNatTest, TestConesBehindSameCone) { + ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE); + Test( + P2PTransportChannelTestBase::Result("prflx", "udp", "stun", "udp", 1000)); +} + +// Test what happens when we have multiple available pathways. +// In the future we will try different RTTs and configs for the different +// interfaces, so that we can simulate a user with Ethernet and VPN networks. +class P2PTransportChannelMultihomedTest + : public P2PTransportChannelTestWithFieldTrials { + public: + const Connection* GetConnectionWithRemoteAddress( + P2PTransportChannel* channel, + const SocketAddress& address) { + for (Connection* conn : channel->connections()) { + if (HasRemoteAddress(conn, address)) { + return conn; + } + } + return nullptr; + } + + Connection* GetConnectionWithLocalAddress(P2PTransportChannel* channel, + const SocketAddress& address) { + for (Connection* conn : channel->connections()) { + if (HasLocalAddress(conn, address)) { + return conn; + } + } + return nullptr; + } + + Connection* GetConnection(P2PTransportChannel* channel, + const SocketAddress& local, + const SocketAddress& remote) { + for (Connection* conn : channel->connections()) { + if (HasLocalAddress(conn, local) && HasRemoteAddress(conn, remote)) { + return conn; + } + } + return nullptr; + } + + Connection* GetBestConnection(P2PTransportChannel* channel) { + rtc::ArrayView connections = channel->connections(); + auto it = absl::c_find(connections, channel->selected_connection()); + if (it == connections.end()) { + return nullptr; + } + return *it; + } + + Connection* GetBackupConnection(P2PTransportChannel* channel) { + rtc::ArrayView connections = channel->connections(); + auto it = absl::c_find_if_not(connections, [channel](Connection* conn) { + return conn == channel->selected_connection(); + }); + if (it == connections.end()) { + return nullptr; + } + return *it; + } + + void DestroyAllButBestConnection(P2PTransportChannel* channel) { + const Connection* selected_connection = channel->selected_connection(); + // Copy the list of connections since the original will be modified. + rtc::ArrayView view = channel->connections(); + std::vector connections(view.begin(), view.end()); + for (Connection* conn : connections) { + if (conn != selected_connection) + channel->RemoveConnectionForTest(conn); + } + } +}; + +INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelMultihomedTest, Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelMultihomedTest, + Values("WebRTC-UseActiveIceController/Enabled/")); + +// Test that we can establish connectivity when both peers are multihomed. +TEST_P(P2PTransportChannelMultihomedTest, TestBasic) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(0, kAlternateAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + AddAddress(1, kAlternateAddrs[1]); + Test(kLocalUdpToLocalUdp); +} + +// Test that we can quickly switch links if an interface goes down. +// The controlled side has two interfaces and one will die. +TEST_P(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0]); + // Simulate failing over from Wi-Fi to cell interface. + AddAddress(1, kPublicAddrs[1], "eth0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, kAlternateAddrs[1], "wlan0", rtc::ADAPTER_TYPE_CELLULAR); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Make the receiving timeout shorter for testing. + IceConfig config = CreateIceConfig(1000, GATHER_ONCE); + // Create channels and let them go writable, as usual. + CreateChannels(config, config); + + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + + // Blackhole any traffic to or from the public addrs. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]); + // The selected connections may switch, so keep references to them. + const Connection* selected_connection1 = ep1_ch1()->selected_connection(); + // We should detect loss of receiving within 1 second or so. + EXPECT_TRUE_SIMULATED_WAIT(!selected_connection1->receiving(), kMediumTimeout, + clock); + + // We should switch over to use the alternate addr on both sides + // when we are not receiving. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() && + ep2_ch1()->selected_connection()->receiving(), + kMediumTimeout, clock); + EXPECT_TRUE(LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0])); + EXPECT_TRUE( + RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1])); + EXPECT_TRUE( + LocalCandidate(ep2_ch1())->address().EqualIPs(kAlternateAddrs[1])); + + DestroyChannels(); +} + +// Test that we can quickly switch links if an interface goes down. +// The controlling side has two interfaces and one will die. +TEST_P(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) { + rtc::ScopedFakeClock clock; + // Simulate failing over from Wi-Fi to cell interface. + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, kAlternateAddrs[0], "wlan0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Make the receiving timeout shorter for testing. + IceConfig config = CreateIceConfig(1000, GATHER_ONCE); + // Create channels and let them go writable, as usual. + CreateChannels(config, config); + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + + // Blackhole any traffic to or from the public addrs. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + + // We should detect loss of receiving within 1 second or so. + // We should switch over to use the alternate addr on both sides + // when we are not receiving. + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kAlternateAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + + DestroyChannels(); +} + +// Tests that we can quickly switch links if an interface goes down when +// there are many connections. +TEST_P(P2PTransportChannelMultihomedTest, TestFailoverWithManyConnections) { + rtc::ScopedFakeClock clock; + test_turn_server()->AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP); + RelayServerConfig turn_server; + turn_server.credentials = kRelayCredentials; + turn_server.ports.push_back(ProtocolAddress(kTurnTcpIntAddr, PROTO_TCP)); + GetAllocator(0)->AddTurnServerForTesting(turn_server); + GetAllocator(1)->AddTurnServerForTesting(turn_server); + // Enable IPv6 + SetAllocatorFlags( + 0, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI); + SetAllocatorFlags( + 1, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI); + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + auto& wifi = kPublicAddrs; + auto& cellular = kAlternateAddrs; + auto& wifiIpv6 = kIPv6PublicAddrs; + auto& cellularIpv6 = kIPv6AlternateAddrs; + AddAddress(0, wifi[0], "wifi0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, wifiIpv6[0], "wifi0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, cellular[0], "cellular0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, cellularIpv6[0], "cellular0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "wifi1", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, wifiIpv6[1], "wifi1", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "cellular1", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, cellularIpv6[1], "cellular1", rtc::ADAPTER_TYPE_CELLULAR); + + // Set smaller delay on the TCP TURN server so that TCP TURN candidates + // will be created in time. + virtual_socket_server()->SetDelayOnAddress(kTurnTcpIntAddr, 1); + virtual_socket_server()->SetDelayOnAddress(kTurnUdpExtAddr, 1); + virtual_socket_server()->set_delay_mean(500); + virtual_socket_server()->UpdateDelayDistribution(); + + // Make the receiving timeout shorter for testing. + IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY); + // Create channels and let them go writable, as usual. + CreateChannels(config, config, true /* ice_renomination */); + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifiIpv6[0], + wifiIpv6[1]), + kMediumTimeout, clock); + + // Blackhole any traffic to or from the wifi on endpoint 1. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, wifi[0]); + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, wifiIpv6[0]); + + // The selected connections may switch, so keep references to them. + const Connection* selected_connection1 = ep1_ch1()->selected_connection(); + const Connection* selected_connection2 = ep2_ch1()->selected_connection(); + EXPECT_TRUE_SIMULATED_WAIT( + !selected_connection1->receiving() && !selected_connection2->receiving(), + kMediumTimeout, clock); + + // Per-network best connections will be pinged at relatively higher rate when + // the selected connection becomes not receiving. + Connection* per_network_best_connection1 = + GetConnection(ep1_ch1(), cellularIpv6[0], wifiIpv6[1]); + ASSERT_NE(nullptr, per_network_best_connection1); + int64_t last_ping_sent1 = per_network_best_connection1->last_ping_sent(); + int num_pings_sent1 = per_network_best_connection1->num_pings_sent(); + EXPECT_TRUE_SIMULATED_WAIT( + num_pings_sent1 < per_network_best_connection1->num_pings_sent(), + kMediumTimeout, clock); + ASSERT_GT(per_network_best_connection1->num_pings_sent() - num_pings_sent1, + 0); + int64_t ping_interval1 = + (per_network_best_connection1->last_ping_sent() - last_ping_sent1) / + (per_network_best_connection1->num_pings_sent() - num_pings_sent1); + constexpr int SCHEDULING_DELAY = 200; + EXPECT_LT( + ping_interval1, + WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_DELAY); + + // It should switch over to use the cellular IPv6 addr on endpoint 1 before + // it timed out on writing. + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), cellularIpv6[0], + wifiIpv6[1]), + kMediumTimeout, clock); + + DestroyChannels(); +} + +// Test that when the controlling side switches the selected connection, +// the nomination of the selected connection on the controlled side will +// increase. +TEST_P(P2PTransportChannelMultihomedTest, TestIceRenomination) { + rtc::ScopedFakeClock clock; + // Simulate failing over from Wi-Fi to cell interface. + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, kAlternateAddrs[0], "wlan0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // We want it to set the remote ICE parameters when creating channels. + set_remote_ice_parameter_source(FROM_SETICEPARAMETERS); + // Make the receiving timeout shorter for testing. + IceConfig config = CreateIceConfig(1000, GATHER_ONCE); + // Create channels with ICE renomination and let them go writable as usual. + CreateChannels(config, config, true); + ASSERT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kMediumTimeout, clock); + EXPECT_TRUE_SIMULATED_WAIT( + ep2_ch1()->selected_connection()->remote_nomination() > 0 && + ep1_ch1()->selected_connection()->acked_nomination() > 0, + kDefaultTimeout, clock); + const Connection* selected_connection1 = ep1_ch1()->selected_connection(); + Connection* selected_connection2 = + const_cast(ep2_ch1()->selected_connection()); + uint32_t remote_nomination2 = selected_connection2->remote_nomination(); + // `selected_connection2` should not be nominated any more since the previous + // nomination has been acknowledged. + ConnectSignalNominated(selected_connection2); + SIMULATED_WAIT(nominated(), kMediumTimeout, clock); + EXPECT_FALSE(nominated()); + + // Blackhole any traffic to or from the public addrs. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + + // The selected connection on the controlling side should switch. + EXPECT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() != selected_connection1, kMediumTimeout, + clock); + // The connection on the controlled side should be nominated again + // and have an increased nomination. + EXPECT_TRUE_SIMULATED_WAIT( + ep2_ch1()->selected_connection()->remote_nomination() > + remote_nomination2, + kDefaultTimeout, clock); + + DestroyChannels(); +} + +// Test that if an interface fails temporarily and then recovers quickly, +// the selected connection will not switch. +// The case that it will switch over to the backup connection if the selected +// connection does not recover after enough time is covered in +// TestFailoverControlledSide and TestFailoverControllingSide. +TEST_P(P2PTransportChannelMultihomedTest, + TestConnectionSwitchDampeningControlledSide) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0]); + // Simulate failing over from Wi-Fi to cell interface. + AddAddress(1, kPublicAddrs[1], "eth0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, kAlternateAddrs[1], "wlan0", rtc::ADAPTER_TYPE_CELLULAR); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(); + + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + + // Make the receiving timeout shorter for testing. + IceConfig config = CreateIceConfig(1000, GATHER_ONCE); + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + reset_selected_candidate_pair_switches(); + + // Blackhole any traffic to or from the public addrs. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]); + + // The selected connections may switch, so keep references to them. + const Connection* selected_connection1 = ep1_ch1()->selected_connection(); + // We should detect loss of receiving within 1 second or so. + EXPECT_TRUE_SIMULATED_WAIT(!selected_connection1->receiving(), kMediumTimeout, + clock); + // After a short while, the link recovers itself. + SIMULATED_WAIT(false, 10, clock); + fw()->ClearRules(); + + // We should remain on the public address on both sides and no connection + // switches should have happened. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() && + ep2_ch1()->selected_connection()->receiving(), + kMediumTimeout, clock); + EXPECT_TRUE(RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + EXPECT_TRUE(LocalCandidate(ep2_ch1())->address().EqualIPs(kPublicAddrs[1])); + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); + + DestroyChannels(); +} + +// Test that if an interface fails temporarily and then recovers quickly, +// the selected connection will not switch. +TEST_P(P2PTransportChannelMultihomedTest, + TestConnectionSwitchDampeningControllingSide) { + rtc::ScopedFakeClock clock; + // Simulate failing over from Wi-Fi to cell interface. + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, kAlternateAddrs[0], "wlan0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(); + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + + // Make the receiving timeout shorter for testing. + IceConfig config = CreateIceConfig(1000, GATHER_ONCE); + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + reset_selected_candidate_pair_switches(); + + // Blackhole any traffic to or from the public addrs. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + // The selected connections may switch, so keep references to them. + const Connection* selected_connection1 = ep1_ch1()->selected_connection(); + // We should detect loss of receiving within 1 second or so. + EXPECT_TRUE_SIMULATED_WAIT(!selected_connection1->receiving(), kMediumTimeout, + clock); + // The link recovers after a short while. + SIMULATED_WAIT(false, 10, clock); + fw()->ClearRules(); + + // We should not switch to the alternate addr on both sides because of the + // dampening. + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); + DestroyChannels(); +} + +// Tests that if the remote side's network failed, it won't cause the local +// side to switch connections and networks. +TEST_P(P2PTransportChannelMultihomedTest, TestRemoteFailover) { + rtc::ScopedFakeClock clock; + // The interface names are chosen so that `cellular` would have higher + // candidate priority and higher cost. + auto& wifi = kPublicAddrs; + auto& cellular = kAlternateAddrs; + AddAddress(0, wifi[0], "wifi0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, cellular[0], "cellular0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "wifi0", rtc::ADAPTER_TYPE_WIFI); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + // Create channels and let them go writable, as usual. + CreateChannels(); + // Make the receiving timeout shorter for testing. + // Set the backup connection ping interval to 25s. + IceConfig config = CreateIceConfig(1000, GATHER_ONCE, 25000); + // Ping the best connection more frequently since we don't have traffic. + config.stable_writable_connection_ping_interval = 900; + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + // Need to wait to make sure the connections on both networks are writable. + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifi[0], wifi[1]), + kDefaultTimeout, clock); + Connection* backup_conn = + GetConnectionWithLocalAddress(ep1_ch1(), cellular[0]); + ASSERT_NE(nullptr, backup_conn); + // After a short while, the backup connection will be writable but not + // receiving because backup connection is pinged at a slower rate. + EXPECT_TRUE_SIMULATED_WAIT( + backup_conn->writable() && !backup_conn->receiving(), kDefaultTimeout, + clock); + reset_selected_candidate_pair_switches(); + // Blackhole any traffic to or from the remote WiFi networks. + RTC_LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, wifi[1]); + + int num_switches = 0; + SIMULATED_WAIT((num_switches = reset_selected_candidate_pair_switches()) > 0, + 20000, clock); + EXPECT_EQ(0, num_switches); + DestroyChannels(); +} + +// Tests that a Wifi-Wifi connection has the highest precedence. +TEST_P(P2PTransportChannelMultihomedTest, TestPreferWifiToWifiConnection) { + // The interface names are chosen so that `cellular` would have higher + // candidate priority if it is not for the network type. + auto& wifi = kAlternateAddrs; + auto& cellular = kPublicAddrs; + AddAddress(0, wifi[0], "test0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, cellular[0], "test1", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "test0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "test1", rtc::ADAPTER_TYPE_CELLULAR); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(); + + EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), 1000, 1000); + // Need to wait to make sure the connections on both networks are writable. + EXPECT_TRUE_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifi[0], wifi[1]), + 1000); + DestroyChannels(); +} + +// Tests that a Wifi-Cellular connection has higher precedence than +// a Cellular-Cellular connection. +TEST_P(P2PTransportChannelMultihomedTest, TestPreferWifiOverCellularNetwork) { + // The interface names are chosen so that `cellular` would have higher + // candidate priority if it is not for the network type. + auto& wifi = kAlternateAddrs; + auto& cellular = kPublicAddrs; + AddAddress(0, cellular[0], "test1", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "test0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "test1", rtc::ADAPTER_TYPE_CELLULAR); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(); + + EXPECT_TRUE_WAIT_MARGIN(CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), + cellular[0], wifi[1]), + 1000, 1000); + DestroyChannels(); +} + +// Test that the backup connection is pinged at a rate no faster than +// what was configured. +TEST_P(P2PTransportChannelMultihomedTest, TestPingBackupConnectionRate) { + AddAddress(0, kPublicAddrs[0]); + // Adding alternate address will make sure `kPublicAddrs` has the higher + // priority than others. This is due to FakeNetwork::AddInterface method. + AddAddress(1, kAlternateAddrs[1]); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(); + EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), 1000, 1000); + int backup_ping_interval = 2000; + ep2_ch1()->SetIceConfig( + CreateIceConfig(2000, GATHER_ONCE, backup_ping_interval)); + // After the state becomes COMPLETED, the backup connection will be pinged + // once every `backup_ping_interval` milliseconds. + ASSERT_TRUE_WAIT(ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED, + 1000); + auto connections = ep2_ch1()->connections(); + ASSERT_EQ(2U, connections.size()); + Connection* backup_conn = GetBackupConnection(ep2_ch1()); + EXPECT_TRUE_WAIT(backup_conn->writable(), kMediumTimeout); + int64_t last_ping_response_ms = backup_conn->last_ping_response_received(); + EXPECT_TRUE_WAIT( + last_ping_response_ms < backup_conn->last_ping_response_received(), + kDefaultTimeout); + int time_elapsed = + backup_conn->last_ping_response_received() - last_ping_response_ms; + RTC_LOG(LS_INFO) << "Time elapsed: " << time_elapsed; + EXPECT_GE(time_elapsed, backup_ping_interval); + + DestroyChannels(); +} + +// Test that the connection is pinged at a rate no faster than +// what was configured when stable and writable. +TEST_P(P2PTransportChannelMultihomedTest, TestStableWritableRate) { + AddAddress(0, kPublicAddrs[0]); + // Adding alternate address will make sure `kPublicAddrs` has the higher + // priority than others. This is due to FakeNetwork::AddInterface method. + AddAddress(1, kAlternateAddrs[1]); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(); + EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), 1000, 1000); + // Set a value larger than the default value of 2500 ms + int ping_interval_ms = 3456; + IceConfig config = CreateIceConfig(2 * ping_interval_ms, GATHER_ONCE); + config.stable_writable_connection_ping_interval = ping_interval_ms; + ep2_ch1()->SetIceConfig(config); + // After the state becomes COMPLETED and is stable and writable, the + // connection will be pinged once every `ping_interval_ms` milliseconds. + ASSERT_TRUE_WAIT(ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED, + 1000); + auto connections = ep2_ch1()->connections(); + ASSERT_EQ(2U, connections.size()); + Connection* conn = GetBestConnection(ep2_ch1()); + EXPECT_TRUE_WAIT(conn->writable(), kMediumTimeout); + + int64_t last_ping_response_ms; + // Burn through some pings so the connection is stable. + for (int i = 0; i < 5; i++) { + last_ping_response_ms = conn->last_ping_response_received(); + EXPECT_TRUE_WAIT( + last_ping_response_ms < conn->last_ping_response_received(), + kDefaultTimeout); + } + EXPECT_TRUE(conn->stable(last_ping_response_ms)) << "Connection not stable"; + int time_elapsed = + conn->last_ping_response_received() - last_ping_response_ms; + RTC_LOG(LS_INFO) << "Time elapsed: " << time_elapsed; + EXPECT_GE(time_elapsed, ping_interval_ms); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelMultihomedTest, TestGetState) { + rtc::ScopedFakeClock clock; + AddAddress(0, kAlternateAddrs[0]); + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + // Create channels and let them go writable, as usual. + CreateChannels(); + + // Both transport channels will reach STATE_COMPLETED quickly. + EXPECT_EQ_SIMULATED_WAIT(IceTransportState::STATE_COMPLETED, + ep1_ch1()->GetState(), kShortTimeout, clock); + EXPECT_EQ_SIMULATED_WAIT(IceTransportState::STATE_COMPLETED, + ep2_ch1()->GetState(), kShortTimeout, clock); + DestroyChannels(); +} + +// Tests that when a network interface becomes inactive, if Continual Gathering +// policy is GATHER_CONTINUALLY, the ports associated with that network +// will be removed from the port list of the channel, and the respective +// remote candidates on the other participant will be removed eventually. +TEST_P(P2PTransportChannelMultihomedTest, TestNetworkBecomesInactive) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + // Create channels and let them go writable, as usual. + IceConfig ep1_config = CreateIceConfig(2000, GATHER_CONTINUALLY); + IceConfig ep2_config = CreateIceConfig(2000, GATHER_ONCE); + CreateChannels(ep1_config, ep2_config); + + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + ASSERT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + // More than one port has been created. + EXPECT_LE(1U, ep1_ch1()->ports().size()); + // Endpoint 1 enabled continual gathering; the port will be removed + // when the interface is removed. + RemoveAddress(0, kPublicAddrs[0]); + EXPECT_TRUE(ep1_ch1()->ports().empty()); + // The remote candidates will be removed eventually. + EXPECT_TRUE_SIMULATED_WAIT(ep2_ch1()->remote_candidates().empty(), 1000, + clock); + + size_t num_ports = ep2_ch1()->ports().size(); + EXPECT_LE(1U, num_ports); + size_t num_remote_candidates = ep1_ch1()->remote_candidates().size(); + // Endpoint 2 did not enable continual gathering; the local port will still be + // removed when the interface is removed but the remote candidates on the + // other participant will not be removed. + RemoveAddress(1, kPublicAddrs[1]); + + EXPECT_EQ_SIMULATED_WAIT(0U, ep2_ch1()->ports().size(), kDefaultTimeout, + clock); + SIMULATED_WAIT(0U == ep1_ch1()->remote_candidates().size(), 500, clock); + EXPECT_EQ(num_remote_candidates, ep1_ch1()->remote_candidates().size()); + + DestroyChannels(); +} + +// Tests that continual gathering will create new connections when a new +// interface is added. +TEST_P(P2PTransportChannelMultihomedTest, + TestContinualGatheringOnNewInterface) { + auto& wifi = kAlternateAddrs; + auto& cellular = kPublicAddrs; + AddAddress(0, wifi[0], "test_wifi0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "test_cell1", rtc::ADAPTER_TYPE_CELLULAR); + // Set continual gathering policy. + IceConfig continual_gathering_config = + CreateIceConfig(1000, GATHER_CONTINUALLY); + CreateChannels(continual_gathering_config, continual_gathering_config); + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), kDefaultTimeout, + kDefaultTimeout); + + // Add a new wifi interface on end point 2. We should expect a new connection + // to be created and the new one will be the best connection. + AddAddress(1, wifi[1], "test_wifi1", rtc::ADAPTER_TYPE_WIFI); + const Connection* conn; + EXPECT_TRUE_WAIT((conn = ep1_ch1()->selected_connection()) != nullptr && + HasRemoteAddress(conn, wifi[1]), + kDefaultTimeout); + EXPECT_TRUE_WAIT((conn = ep2_ch1()->selected_connection()) != nullptr && + HasLocalAddress(conn, wifi[1]), + kDefaultTimeout); + + // Add a new cellular interface on end point 1, we should expect a new + // backup connection created using this new interface. + AddAddress(0, cellular[0], "test_cellular0", rtc::ADAPTER_TYPE_CELLULAR); + EXPECT_TRUE_WAIT( + ep1_ch1()->GetState() == IceTransportState::STATE_COMPLETED && + absl::c_any_of(ep1_ch1()->connections(), + [channel = ep1_ch1(), + address = cellular[0]](const Connection* conn) { + return HasLocalAddress(conn, address) && + conn != channel->selected_connection() && + conn->writable(); + }), + kDefaultTimeout); + EXPECT_TRUE_WAIT( + ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED && + absl::c_any_of(ep2_ch1()->connections(), + [channel = ep2_ch1(), + address = cellular[0]](const Connection* conn) { + return HasRemoteAddress(conn, address) && + conn != channel->selected_connection() && + conn->receiving(); + }), + kDefaultTimeout); + + DestroyChannels(); +} + +// Tests that we can switch links via continual gathering. +TEST_P(P2PTransportChannelMultihomedTest, + TestSwitchLinksViaContinualGathering) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Set continual gathering policy. + IceConfig continual_gathering_config = + CreateIceConfig(1000, GATHER_CONTINUALLY); + // Create channels and let them go writable, as usual. + CreateChannels(continual_gathering_config, continual_gathering_config); + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kPublicAddrs[1]), + kMediumTimeout, clock); + + // Add the new address first and then remove the other one. + RTC_LOG(LS_INFO) << "Draining..."; + AddAddress(1, kAlternateAddrs[1]); + RemoveAddress(1, kPublicAddrs[1]); + // We should switch to use the alternate address after an exchange of pings. + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kAlternateAddrs[1]), + kMediumTimeout, clock); + + // Remove one address first and then add another address. + RTC_LOG(LS_INFO) << "Draining again..."; + RemoveAddress(1, kAlternateAddrs[1]); + AddAddress(1, kAlternateAddrs[0]); + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0], + kAlternateAddrs[0]), + kMediumTimeout, clock); + + DestroyChannels(); +} + +// Tests that the backup connection will be restored after it is destroyed. +TEST_P(P2PTransportChannelMultihomedTest, TestRestoreBackupConnection) { + rtc::ScopedFakeClock clock; + auto& wifi = kAlternateAddrs; + auto& cellular = kPublicAddrs; + AddAddress(0, wifi[0], "test_wifi0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, cellular[0], "test_cell0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "test_wifi1", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "test_cell1", rtc::ADAPTER_TYPE_CELLULAR); + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY); + config.regather_on_failed_networks_interval = 2000; + CreateChannels(config, config); + EXPECT_TRUE_SIMULATED_WAIT( + CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifi[0], wifi[1]), + kMediumTimeout, clock); + + // Destroy all backup connections. + DestroyAllButBestConnection(ep1_ch1()); + // Ensure the backup connection is removed first. + EXPECT_TRUE_SIMULATED_WAIT( + GetConnectionWithLocalAddress(ep1_ch1(), cellular[0]) == nullptr, + kDefaultTimeout, clock); + const Connection* conn; + EXPECT_TRUE_SIMULATED_WAIT( + (conn = GetConnectionWithLocalAddress(ep1_ch1(), cellular[0])) != + nullptr && + conn != ep1_ch1()->selected_connection() && conn->writable(), + kDefaultTimeout, clock); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelMultihomedTest, TestVpnDefault) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); +} + +TEST_P(P2PTransportChannelMultihomedTest, TestVpnPreferVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kPreferVpn; + RTC_LOG(LS_INFO) << "KESO: config.vpn_preference: " << config.vpn_preference; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kAlternateAddrs[0]); + + // Check that it switches to non-VPN + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); +} + +TEST_P(P2PTransportChannelMultihomedTest, TestVpnAvoidVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kAvoidVpn; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block non-VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + + // Check that it switches to VPN + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); +} + +TEST_P(P2PTransportChannelMultihomedTest, TestVpnNeverVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kNeverUseVpn; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block non-VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + + // Check that it does not switches to VPN + clock.AdvanceTime(webrtc::TimeDelta::Millis(kDefaultTimeout)); + EXPECT_TRUE_SIMULATED_WAIT(!CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); +} + +TEST_P(P2PTransportChannelMultihomedTest, TestVpnOnlyVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kOnlyUseVpn; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kAlternateAddrs[0]); + + // Check that it does not switch to non-VPN + clock.AdvanceTime(webrtc::TimeDelta::Millis(kDefaultTimeout)); + EXPECT_TRUE_SIMULATED_WAIT(!CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); +} + +// A collection of tests which tests a single P2PTransportChannel by sending +// pings. +class P2PTransportChannelPingTest : public TestWithParam, + public sigslot::has_slots<> { + public: + P2PTransportChannelPingTest() + : field_trials_(GetParam()), + vss_(std::make_unique()), + packet_socket_factory_( + std::make_unique(vss_.get())), + thread_(vss_.get()) {} + + protected: + void PrepareChannel(P2PTransportChannel* ch) { + ch->SetIceRole(ICEROLE_CONTROLLING); + ch->SetIceTiebreaker(kTiebreakerDefault); + ch->SetIceParameters(kIceParams[0]); + ch->SetRemoteIceParameters(kIceParams[1]); + ch->SignalNetworkRouteChanged.connect( + this, &P2PTransportChannelPingTest::OnNetworkRouteChanged); + ch->SignalReadyToSend.connect(this, + &P2PTransportChannelPingTest::OnReadyToSend); + ch->SignalStateChanged.connect( + this, &P2PTransportChannelPingTest::OnChannelStateChanged); + ch->SignalCandidatePairChanged.connect( + this, &P2PTransportChannelPingTest::OnCandidatePairChanged); + } + + Connection* WaitForConnectionTo( + P2PTransportChannel* ch, + absl::string_view ip, + int port_num, + rtc::ThreadProcessingFakeClock* clock = nullptr) { + if (clock == nullptr) { + EXPECT_TRUE_WAIT(GetConnectionTo(ch, ip, port_num) != nullptr, + kMediumTimeout); + } else { + EXPECT_TRUE_SIMULATED_WAIT(GetConnectionTo(ch, ip, port_num) != nullptr, + kMediumTimeout, *clock); + } + return GetConnectionTo(ch, ip, port_num); + } + + Port* GetPort(P2PTransportChannel* ch) { + if (ch->ports().empty()) { + return nullptr; + } + return static_cast(ch->ports()[0]); + } + + Port* GetPrunedPort(P2PTransportChannel* ch) { + if (ch->pruned_ports().empty()) { + return nullptr; + } + return static_cast(ch->pruned_ports()[0]); + } + + Connection* GetConnectionTo(P2PTransportChannel* ch, + absl::string_view ip, + int port_num) { + Port* port = GetPort(ch); + if (!port) { + return nullptr; + } + return port->GetConnection(rtc::SocketAddress(ip, port_num)); + } + + Connection* FindNextPingableConnectionAndPingIt(P2PTransportChannel* ch) { + Connection* conn = ch->FindNextPingableConnection(); + if (conn) { + ch->MarkConnectionPinged(conn); + } + return conn; + } + + int SendData(IceTransportInternal* channel, + const char* data, + size_t len, + int packet_id) { + rtc::PacketOptions options; + options.packet_id = packet_id; + return channel->SendPacket(data, len, options, 0); + } + + Connection* CreateConnectionWithCandidate(P2PTransportChannel* channel, + rtc::ScopedFakeClock* clock, + absl::string_view ip_addr, + int port, + int priority, + bool writable) { + channel->AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, ip_addr, port, priority)); + EXPECT_TRUE_SIMULATED_WAIT( + GetConnectionTo(channel, ip_addr, port) != nullptr, kMediumTimeout, + *clock); + Connection* conn = GetConnectionTo(channel, ip_addr, port); + + if (conn && writable) { + conn->ReceivedPingResponse(LOW_RTT, "id"); // make it writable + } + return conn; + } + + void NominateConnection(Connection* conn, uint32_t remote_nomination = 1U) { + conn->set_remote_nomination(remote_nomination); + conn->SignalNominated(conn); + } + + void OnNetworkRouteChanged(absl::optional network_route) { + last_network_route_ = network_route; + if (last_network_route_) { + last_sent_packet_id_ = last_network_route_->last_sent_packet_id; + } + ++selected_candidate_pair_switches_; + } + + void ReceivePingOnConnection( + Connection* conn, + absl::string_view remote_ufrag, + int priority, + uint32_t nomination, + const absl::optional& piggyback_ping_id) { + IceMessage msg(STUN_BINDING_REQUEST); + msg.AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, + conn->local_candidate().username() + ":" + std::string(remote_ufrag))); + msg.AddAttribute( + std::make_unique(STUN_ATTR_PRIORITY, priority)); + if (nomination != 0) { + msg.AddAttribute(std::make_unique( + STUN_ATTR_NOMINATION, nomination)); + } + if (piggyback_ping_id) { + msg.AddAttribute(std::make_unique( + STUN_ATTR_GOOG_LAST_ICE_CHECK_RECEIVED, piggyback_ping_id.value())); + } + msg.AddMessageIntegrity(conn->local_candidate().password()); + msg.AddFingerprint(); + rtc::ByteBufferWriter buf; + msg.Write(&buf); + conn->OnReadPacket(buf.Data(), buf.Length(), rtc::TimeMicros()); + } + + void ReceivePingOnConnection(Connection* conn, + absl::string_view remote_ufrag, + int priority, + uint32_t nomination = 0) { + ReceivePingOnConnection(conn, remote_ufrag, priority, nomination, + absl::nullopt); + } + + void OnReadyToSend(rtc::PacketTransportInternal* transport) { + channel_ready_to_send_ = true; + } + void OnChannelStateChanged(IceTransportInternal* channel) { + channel_state_ = channel->GetState(); + } + void OnCandidatePairChanged(const CandidatePairChangeEvent& event) { + last_candidate_change_event_ = event; + } + + int last_sent_packet_id() { return last_sent_packet_id_; } + bool channel_ready_to_send() { return channel_ready_to_send_; } + void reset_channel_ready_to_send() { channel_ready_to_send_ = false; } + IceTransportState channel_state() { return channel_state_; } + int reset_selected_candidate_pair_switches() { + int switches = selected_candidate_pair_switches_; + selected_candidate_pair_switches_ = 0; + return switches; + } + + // Return true if the `pair` matches the last network route. + bool CandidatePairMatchesNetworkRoute(CandidatePairInterface* pair) { + if (!pair) { + return !last_network_route_.has_value(); + } else { + return pair->local_candidate().network_id() == + last_network_route_->local.network_id() && + pair->remote_candidate().network_id() == + last_network_route_->remote.network_id(); + } + } + + bool ConnectionMatchesChangeEvent(Connection* conn, + absl::string_view reason) { + if (!conn) { + return !last_candidate_change_event_.has_value(); + } else { + const auto& last_selected_pair = + last_candidate_change_event_->selected_candidate_pair; + return last_selected_pair.local_candidate().IsEquivalent( + conn->local_candidate()) && + last_selected_pair.remote_candidate().IsEquivalent( + conn->remote_candidate()) && + last_candidate_change_event_->last_data_received_ms == + conn->last_data_received() && + last_candidate_change_event_->reason == reason; + } + } + + int64_t LastEstimatedDisconnectedTimeMs() const { + if (!last_candidate_change_event_.has_value()) { + return 0; + } else { + return last_candidate_change_event_->estimated_disconnected_time_ms; + } + } + + rtc::SocketServer* ss() const { return vss_.get(); } + + rtc::PacketSocketFactory* packet_socket_factory() const { + return packet_socket_factory_.get(); + } + + webrtc::test::ScopedKeyValueConfig field_trials_; + + private: + std::unique_ptr vss_; + std::unique_ptr packet_socket_factory_; + rtc::AutoSocketServerThread thread_; + int selected_candidate_pair_switches_ = 0; + int last_sent_packet_id_ = -1; + bool channel_ready_to_send_ = false; + absl::optional last_candidate_change_event_; + IceTransportState channel_state_ = IceTransportState::STATE_INIT; + absl::optional last_network_route_; +}; + +INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelPingTest, Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelPingTest, + Values("WebRTC-UseActiveIceController/Enabled/")); + +TEST_P(P2PTransportChannelPingTest, TestTriggeredChecks) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("trigger checks", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn1 != nullptr); + ASSERT_TRUE(conn2 != nullptr); + + // Before a triggered check, the first connection to ping is the + // highest priority one. + EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch)); + + // Receiving a ping causes a triggered check which should make conn1 + // be pinged first instead of conn2, even though conn2 has a higher + // priority. + conn1->ReceivedPing(); + EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch)); +} + +TEST_P(P2PTransportChannelPingTest, TestAllConnectionsPingedSufficiently) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("ping sufficiently", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn1 != nullptr); + ASSERT_TRUE(conn2 != nullptr); + + // Low-priority connection becomes writable so that the other connection + // is not pruned. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_TRUE_WAIT( + conn1->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL && + conn2->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL, + kDefaultTimeout); +} + +// Verify that the connections are pinged at the right time. +TEST_P(P2PTransportChannelPingTest, TestStunPingIntervals) { + rtc::ScopedFakeClock clock; + int RTT_RATIO = 4; + int SCHEDULING_RANGE = 200; + int RTT_RANGE = 10; + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("TestChannel", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1); + + ASSERT_TRUE(conn != nullptr); + SIMULATED_WAIT(conn->num_pings_sent() == 1, kDefaultTimeout, clock); + + // Initializing. + + int64_t start = clock.TimeNanos(); + SIMULATED_WAIT(conn->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL, + kDefaultTimeout, clock); + int64_t ping_interval_ms = (clock.TimeNanos() - start) / + rtc::kNumNanosecsPerMillisec / + (MIN_PINGS_AT_WEAK_PING_INTERVAL - 1); + EXPECT_EQ(ping_interval_ms, WEAK_PING_INTERVAL); + + // Stabilizing. + + conn->ReceivedPingResponse(LOW_RTT, "id"); + int ping_sent_before = conn->num_pings_sent(); + start = clock.TimeNanos(); + // The connection becomes strong but not stable because we haven't been able + // to converge the RTT. + SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout, + clock); + ping_interval_ms = (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec; + EXPECT_GE(ping_interval_ms, + WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL); + EXPECT_LE( + ping_interval_ms, + WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_RANGE); + + // Stabilized. + + // The connection becomes stable after receiving more than RTT_RATIO rtt + // samples. + for (int i = 0; i < RTT_RATIO; i++) { + conn->ReceivedPingResponse(LOW_RTT, "id"); + } + ping_sent_before = conn->num_pings_sent(); + start = clock.TimeNanos(); + SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout, + clock); + ping_interval_ms = (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec; + EXPECT_GE(ping_interval_ms, + STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL); + EXPECT_LE( + ping_interval_ms, + STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_RANGE); + + // Destabilized. + + conn->ReceivedPingResponse(LOW_RTT, "id"); + // Create a in-flight ping. + conn->Ping(clock.TimeNanos() / rtc::kNumNanosecsPerMillisec); + start = clock.TimeNanos(); + // In-flight ping timeout and the connection will be unstable. + SIMULATED_WAIT( + !conn->stable(clock.TimeNanos() / rtc::kNumNanosecsPerMillisec), + kMediumTimeout, clock); + int64_t duration_ms = + (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec; + EXPECT_GE(duration_ms, 2 * conn->rtt() - RTT_RANGE); + EXPECT_LE(duration_ms, 2 * conn->rtt() + RTT_RANGE); + // The connection become unstable due to not receiving ping responses. + ping_sent_before = conn->num_pings_sent(); + SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout, + clock); + // The interval is expected to be + // WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL. + start = clock.TimeNanos(); + ping_sent_before = conn->num_pings_sent(); + SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout, + clock); + ping_interval_ms = (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec; + EXPECT_GE(ping_interval_ms, + WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL); + EXPECT_LE( + ping_interval_ms, + WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_RANGE); +} + +// Test that we start pinging as soon as we have a connection and remote ICE +// parameters. +TEST_P(P2PTransportChannelPingTest, PingingStartedAsSoonAsPossible) { + rtc::ScopedFakeClock clock; + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("TestChannel", 1, &pa, &field_trials_); + ch.SetIceRole(ICEROLE_CONTROLLING); + ch.SetIceTiebreaker(kTiebreakerDefault); + ch.SetIceParameters(kIceParams[0]); + ch.MaybeStartGathering(); + EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete, ch.gathering_state(), + kDefaultTimeout); + + // Simulate a binding request being received, creating a peer reflexive + // candidate pair while we still don't have remote ICE parameters. + IceMessage request(STUN_BINDING_REQUEST); + request.AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(STUN_ATTR_PRIORITY, + prflx_priority)); + Port* port = GetPort(&ch); + ASSERT_NE(nullptr, port); + port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), PROTO_UDP, + &request, kIceUfrag[1], false); + Connection* conn = GetConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_NE(nullptr, conn); + + // Simulate waiting for a second (and change) and verify that no pings were + // sent, since we don't yet have remote ICE parameters. + SIMULATED_WAIT(conn->num_pings_sent() > 0, 1025, clock); + EXPECT_EQ(0, conn->num_pings_sent()); + + // Set remote ICE parameters. Now we should be able to ping. Ensure that + // the first ping is sent as soon as possible, within one simulated clock + // tick. + ch.SetRemoteIceParameters(kIceParams[1]); + EXPECT_TRUE_SIMULATED_WAIT(conn->num_pings_sent() > 0, 1, clock); +} + +TEST_P(P2PTransportChannelPingTest, TestNoTriggeredChecksWhenWritable) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("trigger checks", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn1 != nullptr); + ASSERT_TRUE(conn2 != nullptr); + + EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch)); + EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch)); + conn1->ReceivedPingResponse(LOW_RTT, "id"); + ASSERT_TRUE(conn1->writable()); + conn1->ReceivedPing(); + + // Ping received, but the connection is already writable, so no + // "triggered check" and conn2 is pinged before conn1 because it has + // a higher priority. + EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch)); +} + +TEST_P(P2PTransportChannelPingTest, TestFailedConnectionNotPingable) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("Do not ping failed connections", 1, &pa, + &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + + EXPECT_EQ(conn1, ch.FindNextPingableConnection()); + conn1->Prune(); // A pruned connection may still be pingable. + EXPECT_EQ(conn1, ch.FindNextPingableConnection()); + conn1->FailAndPrune(); + EXPECT_TRUE(nullptr == ch.FindNextPingableConnection()); +} + +TEST_P(P2PTransportChannelPingTest, TestSignalStateChanged) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("state change", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + // Pruning the connection reduces the set of active connections and changes + // the channel state. + conn1->Prune(); + EXPECT_EQ_WAIT(IceTransportState::STATE_FAILED, channel_state(), + kDefaultTimeout); +} + +// Test adding remote candidates with different ufrags. If a remote candidate +// is added with an old ufrag, it will be discarded. If it is added with a +// ufrag that was not seen before, it will be used to create connections +// although the ICE pwd in the remote candidate will be set when the ICE +// parameters arrive. If a remote candidate is added with the current ICE +// ufrag, its pwd and generation will be set properly. +TEST_P(P2PTransportChannelPingTest, TestAddRemoteCandidateWithVariousUfrags) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("add candidate", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + // Add a candidate with a future ufrag. + ch.AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1, kIceUfrag[2])); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + const Candidate& candidate = conn1->remote_candidate(); + EXPECT_EQ(kIceUfrag[2], candidate.username()); + EXPECT_TRUE(candidate.password().empty()); + EXPECT_TRUE(FindNextPingableConnectionAndPingIt(&ch) == nullptr); + + // Set the remote ICE parameters with the "future" ufrag. + // This should set the ICE pwd in the remote candidate of `conn1`, making + // it pingable. + ch.SetRemoteIceParameters(kIceParams[2]); + EXPECT_EQ(kIceUfrag[2], candidate.username()); + EXPECT_EQ(kIcePwd[2], candidate.password()); + EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch)); + + // Add a candidate with an old ufrag. No connection will be created. + ch.AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2, kIceUfrag[1])); + rtc::Thread::Current()->ProcessMessages(500); + EXPECT_TRUE(GetConnectionTo(&ch, "2.2.2.2", 2) == nullptr); + + // Add a candidate with the current ufrag, its pwd and generation will be + // assigned, even if the generation is not set. + ch.AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 0, kIceUfrag[2])); + Connection* conn3 = nullptr; + ASSERT_TRUE_WAIT((conn3 = GetConnectionTo(&ch, "3.3.3.3", 3)) != nullptr, + kMediumTimeout); + const Candidate& new_candidate = conn3->remote_candidate(); + EXPECT_EQ(kIcePwd[2], new_candidate.password()); + EXPECT_EQ(1U, new_candidate.generation()); + + // Check that the pwd of all remote candidates are properly assigned. + for (const RemoteCandidate& candidate : ch.remote_candidates()) { + EXPECT_TRUE(candidate.username() == kIceUfrag[1] || + candidate.username() == kIceUfrag[2]); + if (candidate.username() == kIceUfrag[1]) { + EXPECT_EQ(kIcePwd[1], candidate.password()); + } else if (candidate.username() == kIceUfrag[2]) { + EXPECT_EQ(kIcePwd[2], candidate.password()); + } + } +} + +TEST_P(P2PTransportChannelPingTest, ConnectionResurrection) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("connection resurrection", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + + // Create conn1 and keep track of original candidate priority. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + uint32_t remote_priority = conn1->remote_candidate().priority(); + + // Create a higher priority candidate and make the connection + // receiving/writable. This will prune conn1. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPing(); + conn2->ReceivedPingResponse(LOW_RTT, "id"); + + // Wait for conn2 to be selected. + EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kMediumTimeout); + // Destroy the connection to test SignalUnknownAddress. + ch.RemoveConnectionForTest(conn1); + EXPECT_TRUE_WAIT(GetConnectionTo(&ch, "1.1.1.1", 1) == nullptr, + kMediumTimeout); + + // Create a minimal STUN message with prflx priority. + IceMessage request(STUN_BINDING_REQUEST); + request.AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(STUN_ATTR_PRIORITY, + prflx_priority)); + EXPECT_NE(prflx_priority, remote_priority); + + Port* port = GetPort(&ch); + // conn1 should be resurrected with original priority. + port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), PROTO_UDP, + &request, kIceUfrag[1], false); + conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(conn1->remote_candidate().priority(), remote_priority); + + // conn3, a real prflx connection, should have prflx priority. + port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 1), PROTO_UDP, + &request, kIceUfrag[1], false); + Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 1); + ASSERT_TRUE(conn3 != nullptr); + EXPECT_EQ(conn3->remote_candidate().priority(), prflx_priority); +} + +TEST_P(P2PTransportChannelPingTest, TestReceivingStateChange) { + rtc::ScopedFakeClock clock; + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_); + PrepareChannel(&ch); + // Default receiving timeout and checking receiving interval should not be too + // small. + EXPECT_LE(1000, ch.config().receiving_timeout_or_default()); + EXPECT_LE(200, ch.check_receiving_interval()); + ch.SetIceConfig(CreateIceConfig(500, GATHER_ONCE)); + EXPECT_EQ(500, ch.config().receiving_timeout_or_default()); + EXPECT_EQ(50, ch.check_receiving_interval()); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + conn1->ReceivedPing(); + conn1->OnReadPacket("ABC", 3, rtc::TimeMicros()); + EXPECT_TRUE_SIMULATED_WAIT(ch.receiving(), kShortTimeout, clock); + EXPECT_TRUE_SIMULATED_WAIT(!ch.receiving(), kShortTimeout, clock); +} + +// The controlled side will select a connection as the "selected connection" +// based on priority until the controlling side nominates a connection, at which +// point the controlled side will select that connection as the +// "selected connection". Plus, SignalNetworkRouteChanged will be fired if the +// selected connection changes and SignalReadyToSend will be fired if the new +// selected connection is writable. +TEST_P(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + // Channel is not ready to send because it is not writable. + EXPECT_FALSE(channel_ready_to_send()); + int last_packet_id = 0; + const char* data = "ABCDEFGH"; + int len = static_cast(strlen(data)); + EXPECT_EQ(-1, SendData(&ch, data, len, ++last_packet_id)); + EXPECT_EQ(-1, last_sent_packet_id()); + + // A connection needs to be writable before it is selected for transmission. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + EXPECT_TRUE(ConnectionMatchesChangeEvent( + conn1, "remote candidate generation maybe changed")); + EXPECT_EQ(len, SendData(&ch, data, len, ++last_packet_id)); + + // When a higher priority candidate comes in, the new connection is chosen + // as the selected connection. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + EXPECT_TRUE( + ConnectionMatchesChangeEvent(conn2, "candidate pair state changed")); + EXPECT_TRUE(channel_ready_to_send()); + EXPECT_EQ(last_packet_id, last_sent_packet_id()); + + // If a stun request with use-candidate attribute arrives, the receiving + // connection will be set as the selected connection, even though + // its priority is lower. + EXPECT_EQ(len, SendData(&ch, data, len, ++last_packet_id)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 1)); + Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3); + ASSERT_TRUE(conn3 != nullptr); + // Because it has a lower priority, the selected connection is still conn2. + EXPECT_EQ(conn2, ch.selected_connection()); + conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable. + // But if it is nominated via use_candidate, it is chosen as the selected + // connection. + NominateConnection(conn3); + ASSERT_EQ(conn3, ch.selected_connection()); + + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn3)); + EXPECT_TRUE( + ConnectionMatchesChangeEvent(conn3, "nomination on the controlled side")); + EXPECT_EQ(last_packet_id, last_sent_packet_id()); + EXPECT_TRUE(channel_ready_to_send()); + + // Even if another higher priority candidate arrives, it will not be set as + // the selected connection because the selected connection is nominated by + // the controlling side. + EXPECT_EQ(len, SendData(&ch, data, len, ++last_packet_id)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "4.4.4.4", 4, 100)); + Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4); + ASSERT_TRUE(conn4 != nullptr); + EXPECT_EQ(conn3, ch.selected_connection()); + // But if it is nominated via use_candidate and writable, it will be set as + // the selected connection. + NominateConnection(conn4); + // Not switched yet because conn4 is not writable. + EXPECT_EQ(conn3, ch.selected_connection()); + reset_channel_ready_to_send(); + // The selected connection switches after conn4 becomes writable. + conn4->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn4, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn4)); + EXPECT_TRUE( + ConnectionMatchesChangeEvent(conn4, "candidate pair state changed")); + EXPECT_EQ(last_packet_id, last_sent_packet_id()); + // SignalReadyToSend is fired again because conn4 is writable. + EXPECT_TRUE(channel_ready_to_send()); +} + +// Test the field trial send_ping_on_nomination_ice_controlled +// that sends a ping directly when a connection has been nominated +// i.e on the ICE_CONTROLLED-side. +TEST_P(P2PTransportChannelPingTest, TestPingOnNomination) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/send_ping_on_nomination_ice_controlled:true/"); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + + // A connection needs to be writable before it is selected for transmission. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // When a higher priority candidate comes in, the new connection is chosen + // as the selected connection. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // Now nominate conn1 (low prio), it shall be choosen. + const int before = conn1->num_pings_sent(); + NominateConnection(conn1); + ASSERT_EQ(conn1, ch.selected_connection()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // And the additional ping should have been sent directly. + EXPECT_EQ(conn1->num_pings_sent(), before + 1); +} + +// Test the field trial send_ping_on_switch_ice_controlling +// that sends a ping directly when switching to a new connection +// on the ICE_CONTROLLING-side. +TEST_P(P2PTransportChannelPingTest, TestPingOnSwitch) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/send_ping_on_switch_ice_controlling:true/"); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.SetIceRole(ICEROLE_CONTROLLING); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + + // A connection needs to be writable before it is selected for transmission. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // When a higher priority candidate comes in, the new connection is chosen + // as the selected connection. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + + const int before = conn2->num_pings_sent(); + + conn2->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // And the additional ping should have been sent directly. + EXPECT_EQ(conn2->num_pings_sent(), before + 1); +} + +// Test the field trial send_ping_on_switch_ice_controlling +// that sends a ping directly when selecteing a new connection +// on the ICE_CONTROLLING-side (i.e also initial selection). +TEST_P(P2PTransportChannelPingTest, TestPingOnSelected) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/send_ping_on_selected_ice_controlling:true/"); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.SetIceRole(ICEROLE_CONTROLLING); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + + const int before = conn1->num_pings_sent(); + + // A connection needs to be writable before it is selected for transmission. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // And the additional ping should have been sent directly. + EXPECT_EQ(conn1->num_pings_sent(), before + 1); +} + +// The controlled side will select a connection as the "selected connection" +// based on requests from an unknown address before the controlling side +// nominates a connection, and will nominate a connection from an unknown +// address if the request contains the use_candidate attribute. Plus, it will +// also sends back a ping response and set the ICE pwd in the remote candidate +// appropriately. +TEST_P(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + // A minimal STUN message with prflx priority. + IceMessage request(STUN_BINDING_REQUEST); + request.AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(STUN_ATTR_PRIORITY, + prflx_priority)); + TestUDPPort* port = static_cast(GetPort(&ch)); + port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), PROTO_UDP, + &request, kIceUfrag[1], false); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(conn1->stats().sent_ping_responses, 1u); + EXPECT_NE(conn1, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout); + + // Another connection is nominated via use_candidate. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + // Because it has a lower priority, the selected connection is still conn1. + EXPECT_EQ(conn1, ch.selected_connection()); + // When it is nominated via use_candidate and writable, it is chosen as the + // selected connection. + conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable. + NominateConnection(conn2); + EXPECT_EQ(conn2, ch.selected_connection()); + + // Another request with unknown address, it will not be set as the selected + // connection because the selected connection was nominated by the controlling + // side. + port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 3), PROTO_UDP, + &request, kIceUfrag[1], false); + Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3); + ASSERT_TRUE(conn3 != nullptr); + EXPECT_EQ(conn3->stats().sent_ping_responses, 1u); + conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable. + EXPECT_EQ(conn2, ch.selected_connection()); + + // However if the request contains use_candidate attribute, it will be + // selected as the selected connection. + request.AddAttribute( + std::make_unique(STUN_ATTR_USE_CANDIDATE)); + port->SignalUnknownAddress(port, rtc::SocketAddress("4.4.4.4", 4), PROTO_UDP, + &request, kIceUfrag[1], false); + Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4); + ASSERT_TRUE(conn4 != nullptr); + EXPECT_EQ(conn4->stats().sent_ping_responses, 1u); + // conn4 is not the selected connection yet because it is not writable. + EXPECT_EQ(conn2, ch.selected_connection()); + conn4->ReceivedPingResponse(LOW_RTT, "id"); // Become writable. + EXPECT_EQ_WAIT(conn4, ch.selected_connection(), kDefaultTimeout); + + // Test that the request from an unknown address contains a ufrag from an old + // generation. + // port->set_sent_binding_response(false); + ch.SetRemoteIceParameters(kIceParams[2]); + ch.SetRemoteIceParameters(kIceParams[3]); + port->SignalUnknownAddress(port, rtc::SocketAddress("5.5.5.5", 5), PROTO_UDP, + &request, kIceUfrag[2], false); + Connection* conn5 = WaitForConnectionTo(&ch, "5.5.5.5", 5); + ASSERT_TRUE(conn5 != nullptr); + EXPECT_EQ(conn5->stats().sent_ping_responses, 1u); + EXPECT_EQ(kIcePwd[2], conn5->remote_candidate().password()); +} + +// The controlled side will select a connection as the "selected connection" +// based on media received until the controlling side nominates a connection, +// at which point the controlled side will select that connection as +// the "selected connection". +TEST_P(P2PTransportChannelPingTest, TestSelectConnectionBasedOnMediaReceived) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 10)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout); + + // If a data packet is received on conn2, the selected connection should + // switch to conn2 because the controlled side must mirror the media path + // chosen by the controlling side. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable and receiving. + conn2->OnReadPacket("ABC", 3, rtc::TimeMicros()); + EXPECT_EQ(conn2, ch.selected_connection()); + conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable. + + // Now another STUN message with an unknown address and use_candidate will + // nominate the selected connection. + IceMessage request(STUN_BINDING_REQUEST); + request.AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(STUN_ATTR_PRIORITY, + prflx_priority)); + request.AddAttribute( + std::make_unique(STUN_ATTR_USE_CANDIDATE)); + Port* port = GetPort(&ch); + port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 3), PROTO_UDP, + &request, kIceUfrag[1], false); + Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3); + ASSERT_TRUE(conn3 != nullptr); + EXPECT_NE(conn3, ch.selected_connection()); // Not writable yet. + conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable. + EXPECT_EQ_WAIT(conn3, ch.selected_connection(), kDefaultTimeout); + + // Now another data packet will not switch the selected connection because the + // selected connection was nominated by the controlling side. + conn2->ReceivedPing(); + conn2->ReceivedPingResponse(LOW_RTT, "id"); + conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + EXPECT_EQ_WAIT(conn3, ch.selected_connection(), kDefaultTimeout); +} + +TEST_P(P2PTransportChannelPingTest, + TestControlledAgentDataReceivingTakesHigherPrecedenceThanPriority) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + // The connections have decreasing priority. + Connection* conn1 = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, true); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = + CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, true); + ASSERT_TRUE(conn2 != nullptr); + + // Initially, connections are selected based on priority. + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // conn2 receives data; it becomes selected. + // Advance the clock by 1ms so that the last data receiving timestamp of + // conn2 is larger. + SIMULATED_WAIT(false, 1, clock); + conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // conn1 also receives data; it becomes selected due to priority again. + conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // conn2 received data more recently; it is selected now because it + // received data more recently. + SIMULATED_WAIT(false, 1, clock); + // Need to become writable again because it was pruned. + conn2->ReceivedPingResponse(LOW_RTT, "id"); + conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // Make sure sorting won't reselect candidate pair. + SIMULATED_WAIT(false, 10, clock); + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); +} + +TEST_P(P2PTransportChannelPingTest, + TestControlledAgentNominationTakesHigherPrecedenceThanDataReceiving) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + // The connections have decreasing priority. + Connection* conn1 = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, true); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = + CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, true); + ASSERT_TRUE(conn2 != nullptr); + + // conn1 received data; it is the selected connection. + // Advance the clock to have a non-zero last-data-receiving time. + SIMULATED_WAIT(false, 1, clock); + conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // conn2 is nominated; it becomes the selected connection. + NominateConnection(conn2); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // conn1 is selected because it has higher priority and also nominated. + NominateConnection(conn1); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // Make sure sorting won't reselect candidate pair. + SIMULATED_WAIT(false, 10, clock); + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); +} + +TEST_P(P2PTransportChannelPingTest, + TestControlledAgentSelectsConnectionWithHigherNomination) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + // The connections have decreasing priority. + Connection* conn1 = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, true); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = + CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, true); + ASSERT_TRUE(conn2 != nullptr); + + // conn1 is the selected connection because it has a higher priority, + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout, + clock); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + reset_selected_candidate_pair_switches(); + + // conn2 is nominated; it becomes selected. + NominateConnection(conn2); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_EQ(conn2, ch.selected_connection()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // conn1 is selected because of its priority. + NominateConnection(conn1); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_EQ(conn1, ch.selected_connection()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // conn2 gets higher remote nomination; it is selected again. + NominateConnection(conn2, 2U); + EXPECT_EQ(1, reset_selected_candidate_pair_switches()); + EXPECT_EQ(conn2, ch.selected_connection()); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // Make sure sorting won't reselect candidate pair. + SIMULATED_WAIT(false, 100, clock); + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); +} + +TEST_P(P2PTransportChannelPingTest, TestEstimatedDisconnectedTime) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + // The connections have decreasing priority. + Connection* conn1 = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", /* port= */ 1, + /* priority= */ 10, /* writable= */ true); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = + CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", /* port= */ 2, + /* priority= */ 9, /* writable= */ true); + ASSERT_TRUE(conn2 != nullptr); + + // conn1 is the selected connection because it has a higher priority, + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout, + clock); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + // No estimateded disconnect time at first connect <=> value is 0. + EXPECT_EQ(LastEstimatedDisconnectedTimeMs(), 0); + + // Use nomination to force switching of selected connection. + int nomination = 1; + + { + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + // This will not parse as STUN, and is considered data + conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + clock.AdvanceTime(webrtc::TimeDelta::Seconds(2)); + + // conn2 is nominated; it becomes selected. + NominateConnection(conn2, nomination++); + EXPECT_EQ(conn2, ch.selected_connection()); + // We got data 2s ago...guess that we lost 2s of connectivity. + EXPECT_EQ(LastEstimatedDisconnectedTimeMs(), 2000); + } + + { + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + + clock.AdvanceTime(webrtc::TimeDelta::Seconds(2)); + ReceivePingOnConnection(conn2, kIceUfrag[1], 1, nomination++); + + clock.AdvanceTime(webrtc::TimeDelta::Millis(500)); + + ReceivePingOnConnection(conn1, kIceUfrag[1], 1, nomination++); + EXPECT_EQ(conn1, ch.selected_connection()); + // We got ping 500ms ago...guess that we lost 500ms of connectivity. + EXPECT_EQ(LastEstimatedDisconnectedTimeMs(), 500); + } +} + +TEST_P(P2PTransportChannelPingTest, + TestControlledAgentIgnoresSmallerNomination) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + Connection* conn = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, false); + ReceivePingOnConnection(conn, kIceUfrag[1], 1, 2U); + EXPECT_EQ(2U, conn->remote_nomination()); + // Smaller nomination is ignored. + ReceivePingOnConnection(conn, kIceUfrag[1], 1, 1U); + EXPECT_EQ(2U, conn->remote_nomination()); +} + +TEST_P(P2PTransportChannelPingTest, + TestControlledAgentWriteStateTakesHigherPrecedenceThanNomination) { + rtc::ScopedFakeClock clock; + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + // The connections have decreasing priority. + Connection* conn1 = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, false); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = + CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, false); + ASSERT_TRUE(conn2 != nullptr); + + NominateConnection(conn1); + // There is no selected connection because no connection is writable. + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); + + // conn2 becomes writable; it is selected even though it is not nominated. + conn2->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(), + kDefaultTimeout, clock); + EXPECT_EQ_SIMULATED_WAIT(conn2, ch.selected_connection(), kDefaultTimeout, + clock); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2)); + + // If conn1 is also writable, it will become selected. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(), + kDefaultTimeout, clock); + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout, + clock); + EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1)); + + // Make sure sorting won't reselect candidate pair. + SIMULATED_WAIT(false, 10, clock); + EXPECT_EQ(0, reset_selected_candidate_pair_switches()); +} + +// Test that if a new remote candidate has the same address and port with +// an old one, it will be used to create a new connection. +TEST_P(P2PTransportChannelPingTest, TestAddRemoteCandidateWithAddressReuse) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("candidate reuse", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + const std::string host_address = "1.1.1.1"; + const int port_num = 1; + + // kIceUfrag[1] is the current generation ufrag. + Candidate candidate = CreateUdpCandidate(LOCAL_PORT_TYPE, host_address, + port_num, 1, kIceUfrag[1]); + ch.AddRemoteCandidate(candidate); + Connection* conn1 = WaitForConnectionTo(&ch, host_address, port_num); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(0u, conn1->remote_candidate().generation()); + + // Simply adding the same candidate again won't create a new connection. + ch.AddRemoteCandidate(candidate); + Connection* conn2 = GetConnectionTo(&ch, host_address, port_num); + EXPECT_EQ(conn1, conn2); + + // Update the ufrag of the candidate and add it again. + candidate.set_username(kIceUfrag[2]); + ch.AddRemoteCandidate(candidate); + conn2 = GetConnectionTo(&ch, host_address, port_num); + EXPECT_NE(conn1, conn2); + EXPECT_EQ(kIceUfrag[2], conn2->remote_candidate().username()); + EXPECT_EQ(1u, conn2->remote_candidate().generation()); + + // Verify that a ping with the new ufrag can be received on the new + // connection. + EXPECT_EQ(0, conn2->last_ping_received()); + ReceivePingOnConnection(conn2, kIceUfrag[2], 1 /* priority */); + EXPECT_GT(conn2->last_ping_received(), 0); +} + +// When the current selected connection is strong, lower-priority connections +// will be pruned. Otherwise, lower-priority connections are kept. +TEST_P(P2PTransportChannelPingTest, TestDontPruneWhenWeak) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(nullptr, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + + // When a higher-priority, nominated candidate comes in, the connections with + // lower-priority are pruned. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + NominateConnection(conn2); + EXPECT_TRUE_SIMULATED_WAIT(conn1->pruned(), kMediumTimeout, clock); + + ch.SetIceConfig(CreateIceConfig(500, GATHER_ONCE)); + // Wait until conn2 becomes not receiving. + EXPECT_TRUE_SIMULATED_WAIT(!conn2->receiving(), kMediumTimeout, clock); + + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 1)); + Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3, &clock); + ASSERT_TRUE(conn3 != nullptr); + // The selected connection should still be conn2. Even through conn3 has lower + // priority and is not receiving/writable, it is not pruned because the + // selected connection is not receiving. + SIMULATED_WAIT(conn3->pruned(), kShortTimeout, clock); + EXPECT_FALSE(conn3->pruned()); +} + +TEST_P(P2PTransportChannelPingTest, TestDontPruneHighPriorityConnections) { + rtc::ScopedFakeClock clock; + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + Connection* conn1 = + CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 100, true); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = + CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 200, false); + ASSERT_TRUE(conn2 != nullptr); + // Even if conn1 is writable, nominated, receiving data, it should not prune + // conn2. + NominateConnection(conn1); + SIMULATED_WAIT(false, 1, clock); + conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros()); + SIMULATED_WAIT(conn2->pruned(), 100, clock); + EXPECT_FALSE(conn2->pruned()); +} + +// Test that GetState returns the state correctly. +TEST_P(P2PTransportChannelPingTest, TestGetState) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials_); + EXPECT_EQ(webrtc::IceTransportState::kNew, ch.GetIceTransportState()); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + // After gathering we are still in the kNew state because we aren't checking + // any connections yet. + EXPECT_EQ(webrtc::IceTransportState::kNew, ch.GetIceTransportState()); + EXPECT_EQ(IceTransportState::STATE_INIT, ch.GetState()); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1)); + // Checking candidates that have been added with gathered candidates. + ASSERT_GT(ch.connections().size(), 0u); + EXPECT_EQ(webrtc::IceTransportState::kChecking, ch.GetIceTransportState()); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock); + ASSERT_TRUE(conn1 != nullptr); + ASSERT_TRUE(conn2 != nullptr); + // Now there are two connections, so the transport channel is connecting. + EXPECT_EQ(IceTransportState::STATE_CONNECTING, ch.GetState()); + // No connections are writable yet, so we should still be in the kChecking + // state. + EXPECT_EQ(webrtc::IceTransportState::kChecking, ch.GetIceTransportState()); + // `conn1` becomes writable and receiving; it then should prune `conn2`. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_TRUE_SIMULATED_WAIT(conn2->pruned(), kShortTimeout, clock); + EXPECT_EQ(IceTransportState::STATE_COMPLETED, ch.GetState()); + EXPECT_EQ(webrtc::IceTransportState::kConnected, ch.GetIceTransportState()); + conn1->Prune(); // All connections are pruned. + // Need to wait until the channel state is updated. + EXPECT_EQ_SIMULATED_WAIT(IceTransportState::STATE_FAILED, ch.GetState(), + kShortTimeout, clock); + EXPECT_EQ(webrtc::IceTransportState::kFailed, ch.GetIceTransportState()); +} + +// Test that when a low-priority connection is pruned, it is not deleted +// right away, and it can become active and be pruned again. +TEST_P(P2PTransportChannelPingTest, TestConnectionPrunedAgain) { + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials_); + PrepareChannel(&ch); + IceConfig config = CreateIceConfig(1000, GATHER_ONCE); + config.receiving_switching_delay = 800; + ch.SetIceConfig(config); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(nullptr, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout, + clock); + + // Add a low-priority connection `conn2`, which will be pruned, but it will + // not be deleted right away. Once the current selected connection becomes not + // receiving, `conn2` will start to ping and upon receiving the ping response, + // it will become the selected connection. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock); + ASSERT_TRUE(conn2 != nullptr); + EXPECT_TRUE_SIMULATED_WAIT(!conn2->active(), kDefaultTimeout, clock); + // `conn2` should not send a ping yet. + EXPECT_EQ(IceCandidatePairState::WAITING, conn2->state()); + EXPECT_EQ(IceTransportState::STATE_COMPLETED, ch.GetState()); + // Wait for `conn1` becoming not receiving. + EXPECT_TRUE_SIMULATED_WAIT(!conn1->receiving(), kMediumTimeout, clock); + // Make sure conn2 is not deleted. + conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock); + ASSERT_TRUE(conn2 != nullptr); + EXPECT_EQ_SIMULATED_WAIT(IceCandidatePairState::IN_PROGRESS, conn2->state(), + kDefaultTimeout, clock); + conn2->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_SIMULATED_WAIT(conn2, ch.selected_connection(), kDefaultTimeout, + clock); + EXPECT_EQ(IceTransportState::STATE_CONNECTING, ch.GetState()); + + // When `conn1` comes back again, `conn2` will be pruned again. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout, + clock); + EXPECT_TRUE_SIMULATED_WAIT(!conn2->active(), kDefaultTimeout, clock); + EXPECT_EQ(IceTransportState::STATE_COMPLETED, ch.GetState()); +} + +// Test that if all connections in a channel has timed out on writing, they +// will all be deleted. We use Prune to simulate write_time_out. +TEST_P(P2PTransportChannelPingTest, TestDeleteConnectionsIfAllWriteTimedout) { + rtc::ScopedFakeClock clock; + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + // Have one connection only but later becomes write-time-out. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + conn1->ReceivedPing(); // Becomes receiving + conn1->Prune(); + EXPECT_TRUE_SIMULATED_WAIT(ch.connections().empty(), kShortTimeout, clock); + + // Have two connections but both become write-time-out later. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPing(); // Becomes receiving + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 2)); + Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3, &clock); + ASSERT_TRUE(conn3 != nullptr); + conn3->ReceivedPing(); // Becomes receiving + // Now prune both conn2 and conn3; they will be deleted soon. + conn2->Prune(); + conn3->Prune(); + EXPECT_TRUE_SIMULATED_WAIT(ch.connections().empty(), kShortTimeout, clock); +} + +// Tests that after a port allocator session is started, it will be stopped +// when a new connection becomes writable and receiving. Also tests that if a +// connection belonging to an old session becomes writable, it won't stop +// the current port allocator session. +TEST_P(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials_); + PrepareChannel(&ch); + ch.SetIceConfig(CreateIceConfig(2000, GATHER_ONCE)); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts()); + + // Start a new session. Even though conn1, which belongs to an older + // session, becomes unwritable and writable again, it should not stop the + // current session. + ch.SetIceParameters(kIceParams[1]); + ch.MaybeStartGathering(); + conn1->Prune(); + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_TRUE(ch.allocator_session()->IsGettingPorts()); + + // But if a new connection created from the new session becomes writable, + // it will stop the current session. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 100)); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts()); +} + +// Test that the ICE role is updated even on ports that has been removed. +// These ports may still have connections that need a correct role, in case that +// the connections on it may still receive stun pings. +TEST_P(P2PTransportChannelPingTest, TestIceRoleUpdatedOnRemovedPort) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa, + &field_trials_); + // Starts with ICEROLE_CONTROLLING. + PrepareChannel(&ch); + IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY); + ch.SetIceConfig(config); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + + Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn != nullptr); + + // Make a fake signal to remove the ports in the p2ptransportchannel. then + // change the ICE role and expect it to be updated. + std::vector ports(1, conn->PortForTest()); + ch.allocator_session()->SignalPortsPruned(ch.allocator_session(), ports); + ch.SetIceRole(ICEROLE_CONTROLLED); + EXPECT_EQ(ICEROLE_CONTROLLED, conn->PortForTest()->GetIceRole()); +} + +// Test that the ICE role is updated even on ports with inactive networks. +// These ports may still have connections that need a correct role, for the +// pings sent by those connections until they're replaced by newer-generation +// connections. +TEST_P(P2PTransportChannelPingTest, TestIceRoleUpdatedOnPortAfterIceRestart) { + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa, + &field_trials_); + // Starts with ICEROLE_CONTROLLING. + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + + Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn != nullptr); + + // Do an ICE restart, change the role, and expect the old port to have its + // role updated. + ch.SetIceParameters(kIceParams[1]); + ch.MaybeStartGathering(); + ch.SetIceRole(ICEROLE_CONTROLLED); + EXPECT_EQ(ICEROLE_CONTROLLED, conn->PortForTest()->GetIceRole()); +} + +// Test that after some amount of time without receiving data, the connection +// will be destroyed. The port will only be destroyed after it is marked as +// "pruned." +TEST_P(P2PTransportChannelPingTest, TestPortDestroyedAfterTimeoutAndPruned) { + rtc::ScopedFakeClock fake_clock; + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa, + &field_trials_); + PrepareChannel(&ch); + ch.SetIceRole(ICEROLE_CONTROLLED); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + + Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn != nullptr); + + // Simulate 2 minutes going by. This should be enough time for the port to + // time out. + for (int second = 0; second < 120; ++second) { + fake_clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + } + EXPECT_EQ(nullptr, GetConnectionTo(&ch, "1.1.1.1", 1)); + // Port will not be removed because it is not pruned yet. + PortInterface* port = GetPort(&ch); + ASSERT_NE(nullptr, port); + + // If the session prunes all ports, the port will be destroyed. + ch.allocator_session()->PruneAllPorts(); + EXPECT_EQ_SIMULATED_WAIT(nullptr, GetPort(&ch), 1, fake_clock); + EXPECT_EQ_SIMULATED_WAIT(nullptr, GetPrunedPort(&ch), 1, fake_clock); +} + +TEST_P(P2PTransportChannelPingTest, TestMaxOutstandingPingsFieldTrial) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, "WebRTC-IceFieldTrials/max_outstanding_pings:3/"); + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("max", 1, &pa, &field_trials); + ch.SetIceConfig(ch.config()); + PrepareChannel(&ch); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn1 != nullptr); + ASSERT_TRUE(conn2 != nullptr); + + EXPECT_TRUE_WAIT(conn1->num_pings_sent() == 3 && conn2->num_pings_sent() == 3, + kDefaultTimeout); + + // Check that these connections don't send any more pings. + EXPECT_EQ(nullptr, ch.FindNextPingableConnection()); +} + +class P2PTransportChannelMostLikelyToWorkFirstTest + : public P2PTransportChannelPingTest { + public: + P2PTransportChannelMostLikelyToWorkFirstTest() + : turn_server_(rtc::Thread::Current(), + ss(), + kTurnUdpIntAddr, + kTurnUdpExtAddr) { + network_manager_.AddInterface(kPublicAddrs[0]); + allocator_.reset( + CreateBasicPortAllocator(&network_manager_, ss(), ServerAddresses(), + kTurnUdpIntAddr, rtc::SocketAddress())); + allocator_->set_flags(allocator_->flags() | PORTALLOCATOR_DISABLE_STUN | + PORTALLOCATOR_DISABLE_TCP); + allocator_->set_step_delay(kMinimumStepDelay); + } + + P2PTransportChannel& StartTransportChannel( + bool prioritize_most_likely_to_work, + int stable_writable_connection_ping_interval, + const webrtc::FieldTrialsView* field_trials = nullptr) { + channel_.reset( + new P2PTransportChannel("checks", 1, allocator(), field_trials)); + IceConfig config = channel_->config(); + config.prioritize_most_likely_candidate_pairs = + prioritize_most_likely_to_work; + config.stable_writable_connection_ping_interval = + stable_writable_connection_ping_interval; + channel_->SetIceConfig(config); + PrepareChannel(channel_.get()); + channel_->MaybeStartGathering(); + return *channel_.get(); + } + + BasicPortAllocator* allocator() { return allocator_.get(); } + TestTurnServer* turn_server() { return &turn_server_; } + + // This verifies the next pingable connection has the expected candidates' + // types and, for relay local candidate, the expected relay protocol and ping + // it. + void VerifyNextPingableConnection( + absl::string_view local_candidate_type, + absl::string_view remote_candidate_type, + absl::string_view relay_protocol_type = UDP_PROTOCOL_NAME) { + Connection* conn = FindNextPingableConnectionAndPingIt(channel_.get()); + ASSERT_TRUE(conn != nullptr); + EXPECT_EQ(conn->local_candidate().type(), local_candidate_type); + if (conn->local_candidate().type() == RELAY_PORT_TYPE) { + EXPECT_EQ(conn->local_candidate().relay_protocol(), relay_protocol_type); + } + EXPECT_EQ(conn->remote_candidate().type(), remote_candidate_type); + } + + private: + std::unique_ptr allocator_; + rtc::FakeNetworkManager network_manager_; + TestTurnServer turn_server_; + std::unique_ptr channel_; +}; + +INSTANTIATE_TEST_SUITE_P(Legacy, + P2PTransportChannelMostLikelyToWorkFirstTest, + Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelMostLikelyToWorkFirstTest, + Values("WebRTC-UseActiveIceController/Enabled/")); + +// Test that Relay/Relay connections will be pinged first when no other +// connections have been pinged yet, unless we need to ping a trigger check or +// we have a selected connection. +TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest, + TestRelayRelayFirstWhenNothingPingedYet) { + const int max_strong_interval = 500; + P2PTransportChannel& ch = + StartTransportChannel(true, max_strong_interval, &field_trials_); + EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout); + EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE); + EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE); + + ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1)); + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + + EXPECT_TRUE_WAIT(ch.connections().size() == 4, kDefaultTimeout); + + // Relay/Relay should be the first pingable connection. + Connection* conn = FindNextPingableConnectionAndPingIt(&ch); + ASSERT_TRUE(conn != nullptr); + EXPECT_EQ(conn->local_candidate().type(), RELAY_PORT_TYPE); + EXPECT_EQ(conn->remote_candidate().type(), RELAY_PORT_TYPE); + + // Unless that we have a trigger check waiting to be pinged. + Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + EXPECT_EQ(conn2->local_candidate().type(), LOCAL_PORT_TYPE); + EXPECT_EQ(conn2->remote_candidate().type(), LOCAL_PORT_TYPE); + conn2->ReceivedPing(); + EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch)); + + // Make conn3 the selected connection. + Connection* conn3 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn3 != nullptr); + EXPECT_EQ(conn3->local_candidate().type(), LOCAL_PORT_TYPE); + EXPECT_EQ(conn3->remote_candidate().type(), RELAY_PORT_TYPE); + conn3->ReceivedPingResponse(LOW_RTT, "id"); + ASSERT_TRUE(conn3->writable()); + conn3->ReceivedPing(); + + /* + + TODO(honghaiz): Re-enable this once we use fake clock for this test to fix + the flakiness. The following test becomes flaky because we now ping the + connections with fast rates until every connection is pinged at least three + times. The selected connection may have been pinged before + `max_strong_interval`, so it may not be the next connection to be pinged as + expected in the test. + + // Verify that conn3 will be the "selected connection" since it is readable + // and writable. After `MAX_CURRENT_STRONG_INTERVAL`, it should be the next + // pingable connection. + EXPECT_TRUE_WAIT(conn3 == ch.selected_connection(), kDefaultTimeout); + WAIT(false, max_strong_interval + 100); + conn3->ReceivedPingResponse(LOW_RTT, "id"); + ASSERT_TRUE(conn3->writable()); + EXPECT_EQ(conn3, FindNextPingableConnectionAndPingIt(&ch)); + + */ +} + +// Test that Relay/Relay connections will be pinged first when everything has +// been pinged even if the Relay/Relay connection wasn't the first to be pinged +// in the first round. +TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest, + TestRelayRelayFirstWhenEverythingPinged) { + P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials_); + EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout); + EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE); + EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE); + + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + EXPECT_TRUE_WAIT(ch.connections().size() == 2, kDefaultTimeout); + + // Initially, only have Local/Local and Local/Relay. + VerifyNextPingableConnection(LOCAL_PORT_TYPE, LOCAL_PORT_TYPE); + VerifyNextPingableConnection(RELAY_PORT_TYPE, LOCAL_PORT_TYPE); + + // Remote Relay candidate arrives. + ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 2)); + EXPECT_TRUE_WAIT(ch.connections().size() == 4, kDefaultTimeout); + + // Relay/Relay should be the first since it hasn't been pinged before. + VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE); + + // Local/Relay is the final one. + VerifyNextPingableConnection(LOCAL_PORT_TYPE, RELAY_PORT_TYPE); + + // Now, every connection has been pinged once. The next one should be + // Relay/Relay. + VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE); +} + +// Test that when we receive a new remote candidate, they will be tried first +// before we re-ping Relay/Relay connections again. +TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest, + TestNoStarvationOnNonRelayConnection) { + P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials_); + EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout); + EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE); + EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE); + + ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1)); + EXPECT_TRUE_WAIT(ch.connections().size() == 2, kDefaultTimeout); + + // Initially, only have Relay/Relay and Local/Relay. Ping Relay/Relay first. + VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE); + + // Next, ping Local/Relay. + VerifyNextPingableConnection(LOCAL_PORT_TYPE, RELAY_PORT_TYPE); + + // Remote Local candidate arrives. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + EXPECT_TRUE_WAIT(ch.connections().size() == 4, kDefaultTimeout); + + // Local/Local should be the first since it hasn't been pinged before. + VerifyNextPingableConnection(LOCAL_PORT_TYPE, LOCAL_PORT_TYPE); + + // Relay/Local is the final one. + VerifyNextPingableConnection(RELAY_PORT_TYPE, LOCAL_PORT_TYPE); + + // Now, every connection has been pinged once. The next one should be + // Relay/Relay. + VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE); +} + +// Test skip_relay_to_non_relay_connections field-trial. +// I.e that we never create connection between relay and non-relay. +TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest, + TestSkipRelayToNonRelayConnectionsFieldTrial) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/skip_relay_to_non_relay_connections:true/"); + P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials); + EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout); + EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE); + EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE); + + // Remote Relay candidate arrives. + ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1)); + EXPECT_TRUE_WAIT(ch.connections().size() == 1, kDefaultTimeout); + + // Remote Local candidate arrives. + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + EXPECT_TRUE_WAIT(ch.connections().size() == 2, kDefaultTimeout); +} + +// Test the ping sequence is UDP Relay/Relay followed by TCP Relay/Relay, +// followed by the rest. +TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest, TestTcpTurn) { + // Add a Tcp Turn server. + turn_server()->AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP); + RelayServerConfig config; + config.credentials = kRelayCredentials; + config.ports.push_back(ProtocolAddress(kTurnTcpIntAddr, PROTO_TCP)); + allocator()->AddTurnServerForTesting(config); + + P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials_); + EXPECT_TRUE_WAIT(ch.ports().size() == 3, kDefaultTimeout); + EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE); + EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE); + EXPECT_EQ(ch.ports()[2]->Type(), RELAY_PORT_TYPE); + + // Remote Relay candidate arrives. + ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1)); + EXPECT_TRUE_WAIT(ch.connections().size() == 3, kDefaultTimeout); + + // UDP Relay/Relay should be pinged first. + VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE); + + // TCP Relay/Relay is the next. + VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE, + TCP_PROTOCOL_NAME); + + // Finally, Local/Relay will be pinged. + VerifyNextPingableConnection(LOCAL_PORT_TYPE, RELAY_PORT_TYPE); +} + +class P2PTransportChannelResolverTest : public TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelResolverTest, Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelResolverTest, + Values("WebRTC-UseActiveIceController/Enabled/")); + +// Test that a resolver is created, asked for a result, and destroyed +// when the address is a hostname. The destruction should happen even +// if the channel is not destroyed. +TEST_P(P2PTransportChannelResolverTest, HostnameCandidateIsResolved) { + webrtc::test::ScopedKeyValueConfig field_trials(GetParam()); + ResolverFactoryFixture resolver_fixture; + std::unique_ptr socket_server = + rtc::CreateDefaultSocketServer(); + rtc::AutoSocketServerThread main_thread(socket_server.get()); + rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get()); + FakePortAllocator allocator(rtc::Thread::Current(), &packet_socket_factory, + &field_trials); + webrtc::IceTransportInit init; + init.set_port_allocator(&allocator); + init.set_async_dns_resolver_factory(&resolver_fixture); + init.set_field_trials(&field_trials); + auto channel = P2PTransportChannel::Create("tn", 0, std::move(init)); + Candidate hostname_candidate; + SocketAddress hostname_address("fake.test", 1000); + hostname_candidate.set_address(hostname_address); + channel->AddRemoteCandidate(hostname_candidate); + + ASSERT_EQ_WAIT(1u, channel->remote_candidates().size(), kDefaultTimeout); + const RemoteCandidate& candidate = channel->remote_candidates()[0]; + 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_P(P2PTransportChannelTestWithFieldTrials, + PeerReflexiveCandidateBeforeSignalingWithMdnsName) { + // 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_.set_mdns_responder( + std::make_unique(rtc::Thread::Current())); + + ResolverFactoryFixture resolver_fixture; + GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture; + 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); + const auto& local_candidate = GetEndpoint(0)->saved_candidates_[0].candidate; + // 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 = ep2_ch1()->selected_connection()) != nullptr, + kMediumTimeout); + EXPECT_EQ(PRFLX_PORT_TYPE, selected_connection->remote_candidate().type()); + 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. + resolver_fixture.SetAddressToReturn(local_address); + ResumeCandidates(0); + // Verify ep2's selected connection is updated to use the 'local' candidate. + EXPECT_EQ_WAIT(LOCAL_PORT_TYPE, + ep2_ch1()->selected_connection()->remote_candidate().type(), + kMediumTimeout); + 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_P(P2PTransportChannelTestWithFieldTrials, + PeerReflexiveCandidateDuringResolvingHostCandidateWithMdnsName) { + ResolverFactoryFixture resolver_fixture; + // Prevent resolution until triggered by FireDelayedResolution. + resolver_fixture.DelayResolution(); + + // 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_.set_mdns_responder( + std::make_unique(rtc::Thread::Current())); + GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture; + 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); + const auto& local_candidate = GetEndpoint(0)->saved_candidates_[0].candidate; + // The IP address of ep1's host candidate should be obfuscated. + ASSERT_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 ep1 signal its hostname candidate to ep2. + ResumeCandidates(0); + // 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. + resolver_fixture.SetAddressToReturn(local_address); + + // 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. + + resolver_fixture.FireDelayedResolution(); + + 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_P(P2PTransportChannelTestWithFieldTrials, + CanConnectWithHostCandidateWithMdnsName) { + ResolverFactoryFixture resolver_fixture; + + // 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_.set_mdns_responder( + std::make_unique(rtc::Thread::Current())); + GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture; + 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); + const auto& local_candidate_ep1 = + GetEndpoint(0)->saved_candidates_[0].candidate; + // The IP address of ep1's host candidate should be obfuscated. + 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 receive the correct resolution. + rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address()); + resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr()); + + resolver_fixture.SetAddressToReturn(resolved_address_ep1); + // 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(LOCAL_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type()); + EXPECT_EQ(PRFLX_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + + 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_P(P2PTransportChannelTestWithFieldTrials, + CandidatesSanitizedInStatsWhenMdnsObfuscationEnabled) { + ResolverFactoryFixture resolver_fixture; + + // 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_.set_mdns_responder( + std::make_unique(rtc::Thread::Current())); + GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture; + 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_) { + const auto& local_candidate_ep1 = candidates_data.candidate; + 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()); + resolver_fixture.SetAddressToReturn(resolved_address_ep1); + 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); + + IceTransportStats ice_transport_stats1; + IceTransportStats ice_transport_stats2; + ep1_ch1()->GetStats(&ice_transport_stats1); + ep2_ch1()->GetStats(&ice_transport_stats2); + EXPECT_EQ(3u, ice_transport_stats1.connection_infos.size()); + EXPECT_EQ(3u, ice_transport_stats1.candidate_stats_list.size()); + EXPECT_EQ(3u, ice_transport_stats2.connection_infos.size()); + // Check the stats of ep1 seen by ep1. + for (const auto& connection_info : ice_transport_stats1.connection_infos) { + 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 : ice_transport_stats2.connection_infos) { + 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(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + ConnectingIncreasesSelectedCandidatePairChanges) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + + IceTransportStats ice_transport_stats; + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(0u, ice_transport_stats.selected_candidate_pair_changes); + + // Let the channels connect. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kMediumTimeout, clock); + + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(1u, ice_transport_stats.selected_candidate_pair_changes); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + DisconnectedIncreasesSelectedCandidatePairChanges) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + + IceTransportStats ice_transport_stats; + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(0u, ice_transport_stats.selected_candidate_pair_changes); + + // Let the channels connect. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kMediumTimeout, clock); + + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(1u, ice_transport_stats.selected_candidate_pair_changes); + + // Prune connections and wait for disconnect. + for (Connection* con : ep1_ch1()->connections()) { + con->Prune(); + } + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() == nullptr, + kMediumTimeout, clock); + + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(2u, ice_transport_stats.selected_candidate_pair_changes); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + NewSelectionIncreasesSelectedCandidatePairChanges) { + rtc::ScopedFakeClock clock; + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + CreateChannels(); + + IceTransportStats ice_transport_stats; + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(0u, ice_transport_stats.selected_candidate_pair_changes); + + // Let the channels connect. + EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kMediumTimeout, clock); + + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_EQ(1u, ice_transport_stats.selected_candidate_pair_changes); + + // Prune the currently selected connection and wait for selection + // of a new one. + const Connection* selected_connection = ep1_ch1()->selected_connection(); + for (Connection* con : ep1_ch1()->connections()) { + if (con == selected_connection) { + con->Prune(); + } + } + EXPECT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() != nullptr && + (ep1_ch1()->GetStats(&ice_transport_stats), + ice_transport_stats.selected_candidate_pair_changes >= 2u), + kMediumTimeout, clock); + + ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats)); + EXPECT_GE(ice_transport_stats.selected_candidate_pair_changes, 2u); + + DestroyChannels(); +} + +// A similar test as above to check the selected candidate pair is sanitized +// when it is queried via GetSelectedCandidatePair. +TEST_P(P2PTransportChannelTestWithFieldTrials, + SelectedCandidatePairSanitizedWhenMdnsObfuscationEnabled) { + ResolverFactoryFixture resolver_fixture; + + // ep1 and ep2 will 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_.set_mdns_responder( + std::make_unique(rtc::Thread::Current())); + GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture; + 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); + ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout); + const auto& candidates_data = GetEndpoint(0)->saved_candidates_[0]; + const auto& local_candidate_ep1 = candidates_data.candidate; + ASSERT_TRUE(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()); + resolver_fixture.SetAddressToReturn(resolved_address_ep1); + + ResumeCandidates(0); + ResumeCandidates(1); + + ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr && + ep2_ch1()->selected_connection() != nullptr, + kMediumTimeout); + + const auto pair_ep1 = ep1_ch1()->GetSelectedCandidatePair(); + ASSERT_TRUE(pair_ep1.has_value()); + EXPECT_EQ(LOCAL_PORT_TYPE, pair_ep1->local_candidate().type()); + EXPECT_TRUE(pair_ep1->local_candidate().address().IsUnresolvedIP()); + + const auto pair_ep2 = ep2_ch1()->GetSelectedCandidatePair(); + ASSERT_TRUE(pair_ep2.has_value()); + EXPECT_EQ(LOCAL_PORT_TYPE, pair_ep2->remote_candidate().type()); + EXPECT_TRUE(pair_ep2->remote_candidate().address().IsUnresolvedIP()); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + NoPairOfLocalRelayCandidateWithRemoteMdnsCandidate) { + const int kOnlyRelayPorts = cricket::PORTALLOCATOR_DISABLE_UDP | + cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_TCP; + // We use one endpoint to test the behavior of adding remote candidates, and + // this endpoint only gathers relay candidates. + ConfigureEndpoints(OPEN, OPEN, kOnlyRelayPorts, kDefaultPortAllocatorFlags); + GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT, + kIceParams[0], kIceParams[1]); + IceConfig config; + // Start gathering and we should have only a single relay port. + ep1_ch1()->SetIceConfig(config); + ep1_ch1()->MaybeStartGathering(); + EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete, + ep1_ch1()->gathering_state(), kDefaultTimeout); + EXPECT_EQ(1u, ep1_ch1()->ports().size()); + // Add a plain remote host candidate and three remote mDNS candidates with the + // host, srflx and relay types. Note that the candidates differ in their + // ports. + cricket::Candidate host_candidate = CreateUdpCandidate( + LOCAL_PORT_TYPE, "1.1.1.1", 1 /* port */, 0 /* priority */); + ep1_ch1()->AddRemoteCandidate(host_candidate); + + std::vector mdns_candidates; + mdns_candidates.push_back(CreateUdpCandidate(LOCAL_PORT_TYPE, "example.local", + 2 /* port */, 0 /* priority */)); + mdns_candidates.push_back(CreateUdpCandidate(STUN_PORT_TYPE, "example.local", + 3 /* port */, 0 /* priority */)); + mdns_candidates.push_back(CreateUdpCandidate(RELAY_PORT_TYPE, "example.local", + 4 /* port */, 0 /* priority */)); + // We just resolve the hostname to 1.1.1.1, and add the candidates with this + // address directly to simulate the process of adding remote candidates with + // the name resolution. + for (auto& mdns_candidate : mdns_candidates) { + rtc::SocketAddress resolved_address(mdns_candidate.address()); + resolved_address.SetResolvedIP(0x1111); // 1.1.1.1 + mdns_candidate.set_address(resolved_address); + EXPECT_FALSE(mdns_candidate.address().IsUnresolvedIP()); + ep1_ch1()->AddRemoteCandidate(mdns_candidate); + } + + // All remote candidates should have been successfully added. + EXPECT_EQ(4u, ep1_ch1()->remote_candidates().size()); + + // Expect that there is no connection paired with any mDNS candidate. + ASSERT_EQ(1u, ep1_ch1()->connections().size()); + ASSERT_NE(nullptr, ep1_ch1()->connections()[0]); + EXPECT_EQ( + "1.1.1.1:1", + ep1_ch1()->connections()[0]->remote_candidate().address().ToString()); + DestroyChannels(); +} + +class MockMdnsResponder : public webrtc::MdnsResponderInterface { + public: + MOCK_METHOD(void, + CreateNameForAddress, + (const rtc::IPAddress&, NameCreatedCallback), + (override)); + MOCK_METHOD(void, + RemoveNameForAddress, + (const rtc::IPAddress&, NameRemovedCallback), + (override)); +}; + +TEST_P(P2PTransportChannelTestWithFieldTrials, + SrflxCandidateCanBeGatheredBeforeMdnsCandidateToCreateConnection) { + // ep1 and ep2 will only gather host and srflx candidates with base addresses + // kPublicAddrs[0] and kPublicAddrs[1], respectively, and we use a shared + // socket in gathering. + const auto kOnlyLocalAndStunPorts = + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET; + // ep1 is configured with a NAT so that we do gather a srflx candidate. + ConfigureEndpoints(NAT_FULL_CONE, OPEN, kOnlyLocalAndStunPorts, + kOnlyLocalAndStunPorts); + // ICE parameter will be set up when creating the channels. + set_remote_ice_parameter_source(FROM_SETICEPARAMETERS); + // Use a mock mDNS responder, which does not complete the name registration by + // ignoring the completion callback. + auto mock_mdns_responder = std::make_unique(); + EXPECT_CALL(*mock_mdns_responder, CreateNameForAddress(_, _)) + .Times(1) + .WillOnce(Return()); + GetEndpoint(0)->network_manager_.set_mdns_responder( + std::move(mock_mdns_responder)); + + CreateChannels(); + + // We should be able to form a srflx-host connection to ep2. + ASSERT_TRUE_WAIT((ep1_ch1()->selected_connection()) != nullptr, + kMediumTimeout); + EXPECT_EQ(STUN_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type()); + EXPECT_EQ(LOCAL_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + + DestroyChannels(); +} + +// Test that after changing the candidate filter from relay-only to allowing all +// types of candidates when doing continual gathering, we can gather without ICE +// restart the other types of candidates that are now enabled and form candidate +// pairs. Also, we verify that the relay candidates gathered previously are not +// removed and are still usable for necessary route switching. +TEST_P(P2PTransportChannelTestWithFieldTrials, + SurfaceHostCandidateOnCandidateFilterChangeFromRelayToAll) { + rtc::ScopedFakeClock clock; + + ConfigureEndpoints( + OPEN, OPEN, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_RELAY); + ep2->allocator_->SetCandidateFilter(CF_RELAY); + // Enable continual gathering and also resurfacing gathered candidates upon + // the candidate filter changed in the ICE configuration. + IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + ice_config.surface_ice_candidates_on_ice_transport_type_changed = true; + CreateChannels(ice_config, ice_config); + ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + EXPECT_EQ(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type()); + EXPECT_EQ(RELAY_PORT_TYPE, + ep2_ch1()->selected_connection()->local_candidate().type()); + + // Loosen the candidate filter at ep1. + ep1->allocator_->SetCandidateFilter(CF_ALL); + EXPECT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() != nullptr && + ep1_ch1()->selected_connection()->local_candidate().type() == + LOCAL_PORT_TYPE, + kDefaultTimeout, clock); + EXPECT_EQ(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + + // Loosen the candidate filter at ep2. + ep2->allocator_->SetCandidateFilter(CF_ALL); + EXPECT_TRUE_SIMULATED_WAIT( + ep2_ch1()->selected_connection() != nullptr && + ep2_ch1()->selected_connection()->local_candidate().type() == + LOCAL_PORT_TYPE, + kDefaultTimeout, clock); + // We have migrated to a host-host candidate pair. + EXPECT_EQ(LOCAL_PORT_TYPE, + ep2_ch1()->selected_connection()->remote_candidate().type()); + + // Block the traffic over non-relay-to-relay routes and expect a route change. + fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[0], kPublicAddrs[1]); + fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[1], kPublicAddrs[0]); + fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[0], kTurnUdpExtAddr); + fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[1], kTurnUdpExtAddr); + + // We should be able to reuse the previously gathered relay candidates. + EXPECT_EQ_SIMULATED_WAIT( + RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type(), + kDefaultTimeout, clock); + EXPECT_EQ(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + DestroyChannels(); +} + +// A similar test as SurfaceHostCandidateOnCandidateFilterChangeFromRelayToAll, +// and we should surface server-reflexive candidates that are enabled after +// changing the candidate filter. +TEST_P(P2PTransportChannelTestWithFieldTrials, + SurfaceSrflxCandidateOnCandidateFilterChangeFromRelayToNoHost) { + rtc::ScopedFakeClock clock; + // We need an actual NAT so that the host candidate is not equivalent to the + // srflx candidate; otherwise, the host candidate would still surface even + // though we disable it via the candidate filter below. This is a result of + // the following limitation in the current implementation: + // 1. We don't generate the srflx candidate when we have public IP. + // 2. We keep the host candidate in this case in CheckCandidateFilter even + // though we intend to filter them. + ConfigureEndpoints( + NAT_FULL_CONE, NAT_FULL_CONE, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_RELAY); + ep2->allocator_->SetCandidateFilter(CF_RELAY); + // Enable continual gathering and also resurfacing gathered candidates upon + // the candidate filter changed in the ICE configuration. + IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + ice_config.surface_ice_candidates_on_ice_transport_type_changed = true; + CreateChannels(ice_config, ice_config); + ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + const uint32_t kCandidateFilterNoHost = CF_ALL & ~CF_HOST; + // Loosen the candidate filter at ep1. + ep1->allocator_->SetCandidateFilter(kCandidateFilterNoHost); + EXPECT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() != nullptr && + ep1_ch1()->selected_connection()->local_candidate().type() == + STUN_PORT_TYPE, + kDefaultTimeout, clock); + EXPECT_EQ(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + + // Loosen the candidate filter at ep2. + ep2->allocator_->SetCandidateFilter(kCandidateFilterNoHost); + EXPECT_TRUE_SIMULATED_WAIT( + ep2_ch1()->selected_connection() != nullptr && + ep2_ch1()->selected_connection()->local_candidate().type() == + STUN_PORT_TYPE, + kDefaultTimeout, clock); + // We have migrated to a srflx-srflx candidate pair. + EXPECT_EQ(STUN_PORT_TYPE, + ep2_ch1()->selected_connection()->remote_candidate().type()); + + // Block the traffic over non-relay-to-relay routes and expect a route change. + fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[0], kPublicAddrs[1]); + fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[1], kPublicAddrs[0]); + fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[0], kTurnUdpExtAddr); + fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[1], kTurnUdpExtAddr); + // We should be able to reuse the previously gathered relay candidates. + EXPECT_EQ_SIMULATED_WAIT( + RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type(), + kDefaultTimeout, clock); + EXPECT_EQ(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + DestroyChannels(); +} + +// This is the complement to +// SurfaceHostCandidateOnCandidateFilterChangeFromRelayToAll, and instead of +// gathering continually we only gather once, which makes the config +// `surface_ice_candidates_on_ice_transport_type_changed` ineffective after the +// gathering stopped. +TEST_P(P2PTransportChannelTestWithFieldTrials, + CannotSurfaceTheNewlyAllowedOnFilterChangeIfNotGatheringContinually) { + rtc::ScopedFakeClock clock; + + ConfigureEndpoints( + OPEN, OPEN, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_RELAY); + ep2->allocator_->SetCandidateFilter(CF_RELAY); + // Only gather once. + IceConfig ice_config = CreateIceConfig(1000, GATHER_ONCE); + ice_config.surface_ice_candidates_on_ice_transport_type_changed = true; + CreateChannels(ice_config, ice_config); + ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + // Loosen the candidate filter at ep1. + ep1->allocator_->SetCandidateFilter(CF_ALL); + // Wait for a period for any potential surfacing of new candidates. + SIMULATED_WAIT(false, kDefaultTimeout, clock); + EXPECT_EQ(RELAY_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type()); + + // Loosen the candidate filter at ep2. + ep2->allocator_->SetCandidateFilter(CF_ALL); + EXPECT_EQ(RELAY_PORT_TYPE, + ep2_ch1()->selected_connection()->local_candidate().type()); + DestroyChannels(); +} + +// Test that when the candidate filter is updated to be more restrictive, +// candidates that 1) have already been gathered and signaled 2) but no longer +// match the filter, are not removed. +TEST_P(P2PTransportChannelTestWithFieldTrials, + RestrictingCandidateFilterDoesNotRemoveRegatheredCandidates) { + rtc::ScopedFakeClock clock; + + ConfigureEndpoints( + OPEN, OPEN, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_ALL); + ep2->allocator_->SetCandidateFilter(CF_ALL); + // Enable continual gathering and also resurfacing gathered candidates upon + // the candidate filter changed in the ICE configuration. + IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + ice_config.surface_ice_candidates_on_ice_transport_type_changed = true; + // Pause candidates so we can gather all types of candidates. See + // P2PTransportChannel::OnConnectionStateChange, where we would stop the + // gathering when we have a strongly connected candidate pair. + PauseCandidates(0); + PauseCandidates(1); + CreateChannels(ice_config, ice_config); + + // We have gathered host, srflx and relay candidates. + EXPECT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 3u, + kDefaultTimeout, clock); + ResumeCandidates(0); + ResumeCandidates(1); + ASSERT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() != nullptr && + LOCAL_PORT_TYPE == + ep1_ch1()->selected_connection()->local_candidate().type() && + ep2_ch1()->selected_connection() != nullptr && + LOCAL_PORT_TYPE == + ep1_ch1()->selected_connection()->remote_candidate().type(), + kDefaultTimeout, clock); + ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + // Test that we have a host-host candidate pair selected and the number of + // candidates signaled to the remote peer stays the same. + auto test_invariants = [this]() { + EXPECT_EQ(LOCAL_PORT_TYPE, + ep1_ch1()->selected_connection()->local_candidate().type()); + EXPECT_EQ(LOCAL_PORT_TYPE, + ep1_ch1()->selected_connection()->remote_candidate().type()); + EXPECT_THAT(ep2_ch1()->remote_candidates(), SizeIs(3)); + }; + + test_invariants(); + + // Set a more restrictive candidate filter at ep1. + ep1->allocator_->SetCandidateFilter(CF_HOST | CF_REFLEXIVE); + SIMULATED_WAIT(false, kDefaultTimeout, clock); + test_invariants(); + + ep1->allocator_->SetCandidateFilter(CF_HOST); + SIMULATED_WAIT(false, kDefaultTimeout, clock); + test_invariants(); + + ep1->allocator_->SetCandidateFilter(CF_NONE); + SIMULATED_WAIT(false, kDefaultTimeout, clock); + test_invariants(); + DestroyChannels(); +} + +// Verify that things break unless +// - both parties use the surface_ice_candidates_on_ice_transport_type_changed +// - both parties loosen candidate filter at the same time (approx.). +// +// i.e surface_ice_candidates_on_ice_transport_type_changed requires +// coordination outside of webrtc to function properly. +TEST_P(P2PTransportChannelTestWithFieldTrials, SurfaceRequiresCoordination) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/skip_relay_to_non_relay_connections:true/"); + rtc::ScopedFakeClock clock; + + ConfigureEndpoints( + OPEN, OPEN, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET, + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_RELAY); + ep2->allocator_->SetCandidateFilter(CF_ALL); + // Enable continual gathering and also resurfacing gathered candidates upon + // the candidate filter changed in the ICE configuration. + IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + ice_config.surface_ice_candidates_on_ice_transport_type_changed = true; + // Pause candidates gathering so we can gather all types of candidates. See + // P2PTransportChannel::OnConnectionStateChange, where we would stop the + // gathering when we have a strongly connected candidate pair. + PauseCandidates(0); + PauseCandidates(1); + CreateChannels(ice_config, ice_config); + + // On the caller we only have relay, + // on the callee we have host, srflx and relay. + EXPECT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 1u, + kDefaultTimeout, clock); + EXPECT_TRUE_SIMULATED_WAIT(ep2->saved_candidates_.size() == 3u, + kDefaultTimeout, clock); + + ResumeCandidates(0); + ResumeCandidates(1); + ASSERT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() != nullptr && + RELAY_PORT_TYPE == + ep1_ch1()->selected_connection()->local_candidate().type() && + ep2_ch1()->selected_connection() != nullptr && + RELAY_PORT_TYPE == + ep1_ch1()->selected_connection()->remote_candidate().type(), + kDefaultTimeout, clock); + ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr, + kDefaultTimeout, clock); + + // Wait until the callee discards it's candidates + // since they don't manage to connect. + SIMULATED_WAIT(false, 300000, clock); + + // And then loosen caller candidate filter. + ep1->allocator_->SetCandidateFilter(CF_ALL); + + SIMULATED_WAIT(false, kDefaultTimeout, clock); + + // No p2p connection will be made, it will remain on relay. + EXPECT_TRUE(ep1_ch1()->selected_connection() != nullptr && + RELAY_PORT_TYPE == + ep1_ch1()->selected_connection()->local_candidate().type() && + ep2_ch1()->selected_connection() != nullptr && + RELAY_PORT_TYPE == + ep1_ch1()->selected_connection()->remote_candidate().type()); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampening0) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, "WebRTC-IceFieldTrials/initial_select_dampening:0/"); + + constexpr int kMargin = 10; + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.MaybeStartGathering(); + + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(nullptr, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + // It shall not be selected until 0ms has passed....i.e it should be connected + // directly. + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kMargin, clock); +} + +TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampening) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, "WebRTC-IceFieldTrials/initial_select_dampening:100/"); + + constexpr int kMargin = 10; + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.MaybeStartGathering(); + + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(nullptr, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + // It shall not be selected until 100ms has passed. + SIMULATED_WAIT(conn1 == ch.selected_connection(), 100 - kMargin, clock); + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock); +} + +TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampeningPingReceived) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/initial_select_dampening_ping_received:100/"); + + constexpr int kMargin = 10; + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.MaybeStartGathering(); + + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(nullptr, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + conn1->ReceivedPing("id1"); // + // It shall not be selected until 100ms has passed. + SIMULATED_WAIT(conn1 == ch.selected_connection(), 100 - kMargin, clock); + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock); +} + +TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampeningBoth) { + webrtc::test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-IceFieldTrials/" + "initial_select_dampening:100,initial_select_dampening_ping_received:" + "50/"); + + constexpr int kMargin = 10; + rtc::ScopedFakeClock clock; + clock.AdvanceTime(webrtc::TimeDelta::Seconds(1)); + + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + P2PTransportChannel ch("test channel", 1, &pa, &field_trials); + PrepareChannel(&ch); + ch.SetIceConfig(ch.config()); + ch.MaybeStartGathering(); + + ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100)); + Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock); + ASSERT_TRUE(conn1 != nullptr); + EXPECT_EQ(nullptr, ch.selected_connection()); + conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving + // It shall not be selected until 100ms has passed....but only wait ~50 now. + SIMULATED_WAIT(conn1 == ch.selected_connection(), 50 - kMargin, clock); + // Now receiving ping and new timeout should kick in. + conn1->ReceivedPing("id1"); // + EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock); +} + +class P2PTransportChannelIceControllerTest : public TestWithParam { +}; + +INSTANTIATE_TEST_SUITE_P(Legacy, + P2PTransportChannelIceControllerTest, + Values("")); +INSTANTIATE_TEST_SUITE_P(Active, + P2PTransportChannelIceControllerTest, + Values("WebRTC-UseActiveIceController/Enabled/")); + +TEST_P(P2PTransportChannelIceControllerTest, InjectIceController) { + webrtc::test::ScopedKeyValueConfig field_trials(GetParam()); + std::unique_ptr socket_server = + rtc::CreateDefaultSocketServer(); + rtc::AutoSocketServerThread main_thread(socket_server.get()); + rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get()); + MockIceControllerFactory factory; + FakePortAllocator pa(rtc::Thread::Current(), &packet_socket_factory, + &field_trials); + EXPECT_CALL(factory, RecordIceControllerCreated()).Times(1); + webrtc::IceTransportInit init; + init.set_port_allocator(&pa); + init.set_ice_controller_factory(&factory); + init.set_field_trials(&field_trials); + auto dummy = + P2PTransportChannel::Create("transport_name", + /* component= */ 77, std::move(init)); +} + +TEST(P2PTransportChannel, InjectActiveIceController) { + webrtc::test::ScopedKeyValueConfig field_trials( + "WebRTC-UseActiveIceController/Enabled/"); + std::unique_ptr socket_server = + rtc::CreateDefaultSocketServer(); + rtc::AutoSocketServerThread main_thread(socket_server.get()); + rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get()); + MockActiveIceControllerFactory factory; + FakePortAllocator pa(rtc::Thread::Current(), &packet_socket_factory, + &field_trials); + EXPECT_CALL(factory, RecordActiveIceControllerCreated()).Times(1); + webrtc::IceTransportInit init; + init.set_port_allocator(&pa); + init.set_active_ice_controller_factory(&factory); + init.set_field_trials(&field_trials); + auto dummy = + P2PTransportChannel::Create("transport_name", + /* component= */ 77, std::move(init)); +} + +class ForgetLearnedStateController : public cricket::BasicIceController { + public: + explicit ForgetLearnedStateController( + const cricket::IceControllerFactoryArgs& args) + : cricket::BasicIceController(args) {} + + SwitchResult SortAndSwitchConnection(IceSwitchReason reason) override { + auto result = cricket::BasicIceController::SortAndSwitchConnection(reason); + if (forget_connnection_) { + result.connections_to_forget_state_on.push_back(forget_connnection_); + forget_connnection_ = nullptr; + } + result.recheck_event.emplace(IceSwitchReason::ICE_CONTROLLER_RECHECK, 100); + return result; + } + + void ForgetThisConnectionNextTimeSortAndSwitchConnectionIsCalled( + Connection* con) { + forget_connnection_ = con; + } + + private: + Connection* forget_connnection_ = nullptr; +}; + +class ForgetLearnedStateControllerFactory + : public cricket::IceControllerFactoryInterface { + public: + std::unique_ptr Create( + const cricket::IceControllerFactoryArgs& args) override { + auto controller = std::make_unique(args); + // Keep a pointer to allow modifying calls. + // Must not be used after the p2ptransportchannel has been destructed. + controller_ = controller.get(); + return controller; + } + virtual ~ForgetLearnedStateControllerFactory() = default; + + ForgetLearnedStateController* controller_; +}; + +TEST_P(P2PTransportChannelPingTest, TestForgetLearnedState) { + ForgetLearnedStateControllerFactory factory; + FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(), + &field_trials_); + webrtc::IceTransportInit init; + init.set_port_allocator(&pa); + init.set_ice_controller_factory(&factory); + init.set_field_trials(&field_trials_); + auto ch = + P2PTransportChannel::Create("ping sufficiently", 1, std::move(init)); + + PrepareChannel(ch.get()); + ch->MaybeStartGathering(); + ch->AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1)); + ch->AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2)); + + Connection* conn1 = WaitForConnectionTo(ch.get(), "1.1.1.1", 1); + Connection* conn2 = WaitForConnectionTo(ch.get(), "2.2.2.2", 2); + ASSERT_TRUE(conn1 != nullptr); + ASSERT_TRUE(conn2 != nullptr); + + // Wait for conn1 to be selected. + conn1->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_EQ_WAIT(conn1, ch->selected_connection(), kMediumTimeout); + + conn2->ReceivedPingResponse(LOW_RTT, "id"); + EXPECT_TRUE(conn2->writable()); + + // Now let the ice controller signal to P2PTransportChannel that it + // should Forget conn2. + factory.controller_ + ->ForgetThisConnectionNextTimeSortAndSwitchConnectionIsCalled(conn2); + + // We don't have a mock Connection, so verify this by checking that it + // is no longer writable. + EXPECT_EQ_WAIT(false, conn2->writable(), kMediumTimeout); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + DisableDnsLookupsWithTransportPolicyRelay) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + auto* ep1 = GetEndpoint(0); + ep1->allocator_->SetCandidateFilter(CF_RELAY); + + std::unique_ptr mock_async_resolver = + std::make_unique(); + // This test expects resolution to not be started. + EXPECT_CALL(*mock_async_resolver, Start(_, _)).Times(0); + + webrtc::MockAsyncDnsResolverFactory mock_async_resolver_factory; + ON_CALL(mock_async_resolver_factory, Create()) + .WillByDefault( + [&mock_async_resolver]() { return std::move(mock_async_resolver); }); + + ep1->async_dns_resolver_factory_ = &mock_async_resolver_factory; + + CreateChannels(); + + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100)); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + DisableDnsLookupsWithTransportPolicyNone) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + auto* ep1 = GetEndpoint(0); + ep1->allocator_->SetCandidateFilter(CF_NONE); + + std::unique_ptr mock_async_resolver = + std::make_unique(); + // This test expects resolution to not be started. + EXPECT_CALL(*mock_async_resolver, Start(_, _)).Times(0); + + webrtc::MockAsyncDnsResolverFactory mock_async_resolver_factory; + ON_CALL(mock_async_resolver_factory, Create()) + .WillByDefault( + [&mock_async_resolver]() { return std::move(mock_async_resolver); }); + + ep1->async_dns_resolver_factory_ = &mock_async_resolver_factory; + + CreateChannels(); + + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100)); + + DestroyChannels(); +} + +TEST_P(P2PTransportChannelTestWithFieldTrials, + EnableDnsLookupsWithTransportPolicyNoHost) { + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + auto* ep1 = GetEndpoint(0); + ep1->allocator_->SetCandidateFilter(CF_ALL & ~CF_HOST); + + std::unique_ptr mock_async_resolver = + std::make_unique(); + bool lookup_started = false; + EXPECT_CALL(*mock_async_resolver, Start(_, _)) + .WillOnce(Assign(&lookup_started, true)); + + webrtc::MockAsyncDnsResolverFactory mock_async_resolver_factory; + EXPECT_CALL(mock_async_resolver_factory, Create()) + .WillOnce( + [&mock_async_resolver]() { return std::move(mock_async_resolver); }); + + ep1->async_dns_resolver_factory_ = &mock_async_resolver_factory; + + CreateChannels(); + + ep1_ch1()->AddRemoteCandidate( + CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100)); + + EXPECT_TRUE(lookup_started); + + DestroyChannels(); +} + +class GatherAfterConnectedTest + : public P2PTransportChannelTest, + public WithParamInterface> { + public: + GatherAfterConnectedTest() + : P2PTransportChannelTest(std::get<1>(GetParam())) {} +}; + +TEST_P(GatherAfterConnectedTest, GatherAfterConnected) { + const bool stop_gather_on_strongly_connected = std::get<0>(GetParam()); + const std::string field_trial = + std::string("WebRTC-IceFieldTrials/stop_gather_on_strongly_connected:") + + (stop_gather_on_strongly_connected ? "true/" : "false/"); + webrtc::test::ScopedKeyValueConfig field_trials(field_trials_, field_trial); + + rtc::ScopedFakeClock clock; + // Use local + relay + constexpr uint32_t flags = + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET | + PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_TCP; + ConfigureEndpoints(OPEN, OPEN, flags, flags); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_ALL); + ep2->allocator_->SetCandidateFilter(CF_ALL); + + // Use step delay 3s which is long enough for + // connection to be established before managing to gather relay candidates. + int delay = 3000; + SetAllocationStepDelay(0, delay); + SetAllocationStepDelay(1, delay); + IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + CreateChannels(ice_config, ice_config); + + PauseCandidates(0); + PauseCandidates(1); + + // We have gathered host candidates but not relay. + ASSERT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 1u && + ep2->saved_candidates_.size() == 1u, + kDefaultTimeout, clock); + + ResumeCandidates(0); + ResumeCandidates(1); + + PauseCandidates(0); + PauseCandidates(1); + + ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->remote_candidates().size() == 1 && + ep2_ch1()->remote_candidates().size() == 1, + kDefaultTimeout, clock); + + ASSERT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection(), + kDefaultTimeout, clock); + + clock.AdvanceTime(webrtc::TimeDelta::Millis(10 * delay)); + + if (stop_gather_on_strongly_connected) { + // The relay candidates gathered has not been propagated to channel. + EXPECT_EQ(ep1->saved_candidates_.size(), 0u); + EXPECT_EQ(ep2->saved_candidates_.size(), 0u); + } else { + // The relay candidates gathered has been propagated to channel. + EXPECT_EQ(ep1->saved_candidates_.size(), 1u); + EXPECT_EQ(ep2->saved_candidates_.size(), 1u); + } +} + +TEST_P(GatherAfterConnectedTest, GatherAfterConnectedMultiHomed) { + const bool stop_gather_on_strongly_connected = std::get<0>(GetParam()); + const std::string field_trial = + std::string("WebRTC-IceFieldTrials/stop_gather_on_strongly_connected:") + + (stop_gather_on_strongly_connected ? "true/" : "false/"); + webrtc::test::ScopedKeyValueConfig field_trials(field_trials_, field_trial); + + rtc::ScopedFakeClock clock; + // Use local + relay + constexpr uint32_t flags = + kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET | + PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_TCP; + AddAddress(0, kAlternateAddrs[0]); + ConfigureEndpoints(OPEN, OPEN, flags, flags); + auto* ep1 = GetEndpoint(0); + auto* ep2 = GetEndpoint(1); + ep1->allocator_->SetCandidateFilter(CF_ALL); + ep2->allocator_->SetCandidateFilter(CF_ALL); + + // Use step delay 3s which is long enough for + // connection to be established before managing to gather relay candidates. + int delay = 3000; + SetAllocationStepDelay(0, delay); + SetAllocationStepDelay(1, delay); + IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY); + CreateChannels(ice_config, ice_config); + + PauseCandidates(0); + PauseCandidates(1); + + // We have gathered host candidates but not relay. + ASSERT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 2u && + ep2->saved_candidates_.size() == 1u, + kDefaultTimeout, clock); + + ResumeCandidates(0); + ResumeCandidates(1); + + PauseCandidates(0); + PauseCandidates(1); + + ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->remote_candidates().size() == 1 && + ep2_ch1()->remote_candidates().size() == 2, + kDefaultTimeout, clock); + + ASSERT_TRUE_SIMULATED_WAIT( + ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection(), + kDefaultTimeout, clock); + + clock.AdvanceTime(webrtc::TimeDelta::Millis(10 * delay)); + + if (stop_gather_on_strongly_connected) { + // The relay candidates gathered has not been propagated to channel. + EXPECT_EQ(ep1->saved_candidates_.size(), 0u); + EXPECT_EQ(ep2->saved_candidates_.size(), 0u); + } else { + // The relay candidates gathered has been propagated. + EXPECT_EQ(ep1->saved_candidates_.size(), 2u); + EXPECT_EQ(ep2->saved_candidates_.size(), 1u); + } +} + +INSTANTIATE_TEST_SUITE_P(Legacy, + GatherAfterConnectedTest, + Combine(Values(true, false), Values(""))); +INSTANTIATE_TEST_SUITE_P( + Active, + GatherAfterConnectedTest, + Combine(Values(true, false), + Values("WebRTC-UseActiveIceController/Enabled/"))); + +// Tests no candidates are generated with old ice ufrag/passwd after an ice +// restart even if continual gathering is enabled. +TEST_P(P2PTransportChannelTestWithFieldTrials, + TestIceNoOldCandidatesAfterIceRestart) { + rtc::ScopedFakeClock clock; + AddAddress(0, kAlternateAddrs[0]); + ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags); + + // gathers continually. + IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY); + CreateChannels(config, config); + + EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); + + PauseCandidates(0); + + ep1_ch1()->SetIceParameters(kIceParams[3]); + ep1_ch1()->MaybeStartGathering(); + + EXPECT_TRUE_SIMULATED_WAIT(GetEndpoint(0)->saved_candidates_.size() > 0, + kDefaultTimeout, clock); + + for (const auto& cd : GetEndpoint(0)->saved_candidates_) { + EXPECT_EQ(cd.candidate.username(), kIceUfrag[3]); + } + + DestroyChannels(); +} + +} // namespace cricket -- cgit v1.2.3