5889 lines
250 KiB
C++
5889 lines
250 KiB
C++
/*
|
|
* 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 <list>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#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_async_resolver.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/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/thread.h"
|
|
#include "rtc_base/virtual_socket_server.h"
|
|
#include "system_wrappers/include/metrics.h"
|
|
#include "test/field_trial.h"
|
|
|
|
namespace {
|
|
|
|
using rtc::SocketAddress;
|
|
using ::testing::_;
|
|
using ::testing::Assign;
|
|
using ::testing::Contains;
|
|
using ::testing::DoAll;
|
|
using ::testing::InSequence;
|
|
using ::testing::InvokeWithoutArgs;
|
|
using ::testing::NiceMock;
|
|
using ::testing::Return;
|
|
using ::testing::SetArgPointee;
|
|
using ::testing::SizeIs;
|
|
|
|
// 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;
|
|
|
|
enum { MSG_ADD_CANDIDATES, MSG_REMOVE_CANDIDATES };
|
|
|
|
cricket::IceConfig CreateIceConfig(
|
|
int receiving_timeout,
|
|
cricket::ContinualGatheringPolicy continual_gathering_policy,
|
|
absl::optional<int> 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(const std::string& type,
|
|
const std::string& ip,
|
|
int port,
|
|
int priority,
|
|
const std::string& 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,
|
|
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<cricket::RelayServerConfig> turn_servers(1, turn_server);
|
|
|
|
cricket::BasicPortAllocator* allocator =
|
|
new cricket::BasicPortAllocator(network_manager);
|
|
allocator->Initialize();
|
|
allocator->SetConfiguration(stun_servers, turn_servers, 0, webrtc::NO_PRUNE);
|
|
return allocator;
|
|
}
|
|
|
|
class MockIceControllerFactory : public cricket::IceControllerFactoryInterface {
|
|
public:
|
|
~MockIceControllerFactory() override = default;
|
|
std::unique_ptr<cricket::IceControllerInterface> Create(
|
|
const cricket::IceControllerFactoryArgs& args) override {
|
|
RecordIceControllerCreated();
|
|
return std::make_unique<cricket::BasicIceController>(args);
|
|
}
|
|
|
|
MOCK_METHOD(void, RecordIceControllerCreated, ());
|
|
};
|
|
|
|
} // 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 rtc::MessageHandler,
|
|
public sigslot::has_slots<> {
|
|
public:
|
|
P2PTransportChannelTestBase()
|
|
: vss_(new rtc::VirtualSocketServer()),
|
|
nss_(new rtc::NATSocketServer(vss_.get())),
|
|
ss_(new rtc::FirewallSocketServer(nss_.get())),
|
|
main_(ss_.get()),
|
|
stun_server_(TestStunServer::Create(&main_, kStunAddr)),
|
|
turn_server_(&main_, 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_, stun_servers,
|
|
kTurnUdpIntAddr, rtc::SocketAddress()));
|
|
ep2_.allocator_.reset(
|
|
CreateBasicPortAllocator(&ep2_.network_manager_, stun_servers,
|
|
kTurnUdpIntAddr, rtc::SocketAddress()));
|
|
webrtc::metrics::Reset();
|
|
}
|
|
|
|
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(const std::string& controlling_type,
|
|
const std::string& controlling_protocol,
|
|
const std::string& controlled_type,
|
|
const std::string& 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<std::string> ch_packets_;
|
|
std::unique_ptr<P2PTransportChannel> ch_;
|
|
};
|
|
|
|
struct CandidatesData : public rtc::MessageData {
|
|
CandidatesData(IceTransportInternal* ch, const Candidate& c)
|
|
: channel(ch), candidates(1, c) {}
|
|
CandidatesData(IceTransportInternal* ch, const std::vector<Candidate>& cc)
|
|
: channel(ch), candidates(cc) {}
|
|
IceTransportInternal* channel;
|
|
Candidates candidates;
|
|
};
|
|
|
|
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<BasicPortAllocator> allocator_;
|
|
webrtc::AsyncResolverFactory* async_resolver_factory_;
|
|
ChannelData cd1_;
|
|
ChannelData cd2_;
|
|
IceRole role_;
|
|
uint64_t tiebreaker_;
|
|
bool role_conflict_;
|
|
bool save_candidates_;
|
|
std::vector<std::unique_ptr<CandidatesData>> saved_candidates_;
|
|
bool ready_to_send_ = false;
|
|
std::map<IceRegatheringReason, int> 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_.reset(CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
|
|
ice_ep1_cd1_ch, ice_ep2_cd1_ch));
|
|
ep2_.cd1_.ch_.reset(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);
|
|
}
|
|
|
|
P2PTransportChannel* CreateChannel(int endpoint,
|
|
int component,
|
|
const IceParameters& local_ice,
|
|
const IceParameters& remote_ice) {
|
|
P2PTransportChannel* channel = new P2PTransportChannel(
|
|
"test content name", component, GetAllocator(endpoint),
|
|
GetEndpoint(endpoint)->async_resolver_factory_);
|
|
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() {
|
|
main_.Clear(this);
|
|
ep1_.cd1_.ch_.reset();
|
|
ep2_.cd1_.ch_.reset();
|
|
ep1_.cd2_.ch_.reset();
|
|
ep2_.cd2_.ch_.reset();
|
|
}
|
|
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,
|
|
const std::string& ifname,
|
|
rtc::AdapterType adapter_type) {
|
|
GetEndpoint(endpoint)->network_manager_.AddInterface(addr, ifname,
|
|
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<int>(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(
|
|
std::unique_ptr<CandidatesData>(new CandidatesData(ch, c)));
|
|
} else {
|
|
main_.Post(RTC_FROM_HERE, this, MSG_ADD_CANDIDATES,
|
|
new CandidatesData(ch, c));
|
|
}
|
|
}
|
|
|
|
void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> 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<Candidate>& candidates) {
|
|
// Candidate removals are not paused.
|
|
CandidatesData* candidates_data = new CandidatesData(ch, candidates);
|
|
main_.Post(RTC_FROM_HERE, this, MSG_REMOVE_CANDIDATES, candidates_data);
|
|
}
|
|
|
|
// Tcp candidate verification has to be done when they are generated.
|
|
void VerifySavedTcpCandidates(int endpoint, const std::string& tcptype) {
|
|
for (auto& data : GetEndpoint(endpoint)->saved_candidates_) {
|
|
for (auto& candidate : data->candidates) {
|
|
EXPECT_EQ(candidate.protocol(), TCP_PROTOCOL_NAME);
|
|
EXPECT_EQ(candidate.tcptype(), tcptype);
|
|
if (candidate.tcptype() == TCPTYPE_ACTIVE_STR) {
|
|
EXPECT_EQ(candidate.address().port(), DISCARD_PORT);
|
|
} else if (candidate.tcptype() == TCPTYPE_PASSIVE_STR) {
|
|
EXPECT_NE(candidate.address().port(), DISCARD_PORT);
|
|
} else {
|
|
FAIL() << "Unknown tcptype: " << candidate.tcptype();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResumeCandidates(int endpoint) {
|
|
Endpoint* ed = GetEndpoint(endpoint);
|
|
for (auto& candidate : ed->saved_candidates_) {
|
|
main_.Post(RTC_FROM_HERE, this, MSG_ADD_CANDIDATES, candidate.release());
|
|
}
|
|
ed->saved_candidates_.clear();
|
|
ed->save_candidates_ = false;
|
|
}
|
|
|
|
void OnMessage(rtc::Message* msg) {
|
|
switch (msg->message_id) {
|
|
case MSG_ADD_CANDIDATES: {
|
|
std::unique_ptr<CandidatesData> data(
|
|
static_cast<CandidatesData*>(msg->pdata));
|
|
P2PTransportChannel* rch = GetRemoteChannel(data->channel);
|
|
if (!rch) {
|
|
return;
|
|
}
|
|
for (auto& c : data->candidates) {
|
|
if (remote_ice_parameter_source_ != FROM_CANDIDATE) {
|
|
c.set_username("");
|
|
c.set_password("");
|
|
}
|
|
RTC_LOG(LS_INFO) << "Candidate(" << data->channel->component() << "->"
|
|
<< rch->component() << "): " << c.ToString();
|
|
rch->AddRemoteCandidate(c);
|
|
}
|
|
break;
|
|
}
|
|
case MSG_REMOVE_CANDIDATES: {
|
|
std::unique_ptr<CandidatesData> data(
|
|
static_cast<CandidatesData*>(msg->pdata));
|
|
P2PTransportChannel* rch = GetRemoteChannel(data->channel);
|
|
if (!rch) {
|
|
return;
|
|
}
|
|
for (Candidate& c : data->candidates) {
|
|
RTC_LOG(LS_INFO) << "Removed remote candidate " << c.ToString();
|
|
rch->RemoveRemoteCandidate(c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnReadPacket(rtc::PacketTransportInternal* transport,
|
|
const char* data,
|
|
size_t len,
|
|
const int64_t& /* packet_time_us */,
|
|
int flags) {
|
|
std::list<std::string>& 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<std::string>& 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_; }
|
|
|
|
private:
|
|
std::unique_ptr<rtc::VirtualSocketServer> vss_;
|
|
std::unique_ptr<rtc::NATSocketServer> nss_;
|
|
std::unique_ptr<rtc::FirewallSocketServer> ss_;
|
|
rtc::AutoSocketServerThread main_;
|
|
std::unique_ptr<TestStunServer> 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 {
|
|
protected:
|
|
static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS];
|
|
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<rtc::NATType>(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_NOTREACHED();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 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 P2PTransportChannelTest::Result*
|
|
P2PTransportChannelTest::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},
|
|
};
|
|
|
|
class P2PTransportChannelTestWithFieldTrials
|
|
: public P2PTransportChannelTest,
|
|
public ::testing::WithParamInterface<std::string> {
|
|
public:
|
|
void Test(const Result& expected) override {
|
|
webrtc::test::ScopedFieldTrials field_trials(GetParam());
|
|
P2PTransportChannelTest::Test(expected);
|
|
}
|
|
};
|
|
|
|
// 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(P2PTransportChannelTestWithFieldTrials, 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(
|
|
P2PTransportChannelTestWithFieldTrials,
|
|
P2PTransportChannelTestWithFieldTrials,
|
|
// Each field-trial is ~144 tests (some return not-yet-implemented).
|
|
testing::Values("", "WebRTC-IceFieldTrials/enable_goog_ping:true/"));
|
|
|
|
// Test that we restart candidate allocation when local ufrag&pwd changed.
|
|
// Standard Ice protocol is used.
|
|
TEST_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, HandleUfragPwdChangeSymmetricNat) {
|
|
ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags,
|
|
kDefaultPortAllocatorFlags);
|
|
CreateChannels();
|
|
TestHandleIceUfragPasswordChanged();
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Test the operation of GetStats.
|
|
TEST_F(P2PTransportChannelTest, 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);
|
|
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->new_connection);
|
|
EXPECT_TRUE(best_conn_info->receiving);
|
|
EXPECT_TRUE(best_conn_info->writable);
|
|
EXPECT_FALSE(best_conn_info->timeout);
|
|
EXPECT_EQ(10U, best_conn_info->sent_total_packets);
|
|
EXPECT_EQ(0U, best_conn_info->sent_discarded_packets);
|
|
EXPECT_EQ(10 * 36U, best_conn_info->sent_total_bytes);
|
|
EXPECT_EQ(10 * 36U, best_conn_info->recv_total_bytes);
|
|
EXPECT_EQ(10U, best_conn_info->packets_received);
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Tests that UMAs are recorded when ICE restarts while the channel
|
|
// is disconnected.
|
|
TEST_F(P2PTransportChannelTest, 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<int>(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<int>(IceRestartState::DISCONNECTED)));
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Tests that UMAs are recorded when ICE restarts while the channel
|
|
// is connected.
|
|
TEST_F(P2PTransportChannelTest, 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<int>(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<int>(IceRestartState::CONNECTED)));
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Tests that UMAs are recorded when ICE restarts while the channel
|
|
// is connecting.
|
|
TEST_F(P2PTransportChannelTest, 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<int>(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<int>(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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
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<int>(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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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(
|
|
updated_pair_ep1->remote_candidate().address().EqualIPs(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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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<PortInterface*> 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<PortInterface*> 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, 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_F(P2PTransportChannelTest, TurnToTurnPresumedWritable) {
|
|
ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
|
|
kDefaultPortAllocatorFlags);
|
|
// Only configure one channel so we can control when the remote candidate
|
|
// is added.
|
|
GetEndpoint(0)->cd1_.ch_.reset(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<int>(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_F(P2PTransportChannelTest, 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_.reset(CreateChannel(
|
|
0, ICE_CANDIDATE_COMPONENT_DEFAULT, kIceParams[0], kIceParams[1]));
|
|
GetEndpoint(1)->cd1_.ch_.reset(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_F(P2PTransportChannelTest, 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_.reset(CreateChannel(
|
|
0, ICE_CANDIDATE_COMPONENT_DEFAULT, kIceParams[0], kIceParams[1]));
|
|
GetEndpoint(1)->cd1_.ch_.reset(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_F(P2PTransportChannelTest, 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_.reset(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<int>(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_F(P2PTransportChannelTest,
|
|
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_.reset(CreateChannel(
|
|
0, ICE_CANDIDATE_COMPONENT_DEFAULT, kIceParams[0], kIceParams[1]));
|
|
GetEndpoint(1)->cd1_.ch_.reset(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_F(P2PTransportChannelTest,
|
|
CanConnectWithPiggybackCheckAcknowledgementWhenCheckResponseBlocked) {
|
|
webrtc::test::ScopedFieldTrials 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 {
|
|
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<rtc::NATType>(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<rtc::NATType>(config - NAT_FULL_CONE))
|
|
->AddClient(kCascadedPrivateAddrs[endpoint]);
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_F(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 P2PTransportChannelTestBase {
|
|
public:
|
|
const Connection* GetConnectionWithRemoteAddress(
|
|
P2PTransportChannel* channel,
|
|
const SocketAddress& address) {
|
|
for (Connection* conn : channel->connections()) {
|
|
if (conn->remote_candidate().address().EqualIPs(address)) {
|
|
return conn;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Connection* GetConnectionWithLocalAddress(P2PTransportChannel* channel,
|
|
const SocketAddress& address) {
|
|
for (Connection* conn : channel->connections()) {
|
|
if (conn->local_candidate().address().EqualIPs(address)) {
|
|
return conn;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Connection* GetConnection(P2PTransportChannel* channel,
|
|
const SocketAddress& local,
|
|
const SocketAddress& remote) {
|
|
for (Connection* conn : channel->connections()) {
|
|
if (conn->local_candidate().address().EqualIPs(local) &&
|
|
conn->remote_candidate().address().EqualIPs(remote)) {
|
|
return conn;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void DestroyAllButBestConnection(P2PTransportChannel* channel) {
|
|
const Connection* selected_connection = channel->selected_connection();
|
|
for (Connection* conn : channel->connections()) {
|
|
if (conn != selected_connection) {
|
|
conn->Destroy();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Test that we can establish connectivity when both peers are multihomed.
|
|
TEST_F(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_F(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_F(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_F(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)->AddTurnServer(turn_server);
|
|
GetAllocator(1)->AddTurnServer(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_F(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<Connection*>(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_F(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_F(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_F(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_F(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_F(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_F(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 = connections[1];
|
|
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_F(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_F(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_F(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 &&
|
|
conn->remote_candidate().address().EqualIPs(wifi[1]),
|
|
kDefaultTimeout);
|
|
EXPECT_TRUE_WAIT((conn = ep2_ch1()->selected_connection()) != nullptr &&
|
|
conn->local_candidate().address().EqualIPs(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 &&
|
|
(conn = GetConnectionWithLocalAddress(ep1_ch1(), cellular[0])) !=
|
|
nullptr &&
|
|
conn != ep1_ch1()->selected_connection() && conn->writable(),
|
|
kDefaultTimeout);
|
|
EXPECT_TRUE_WAIT(
|
|
ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED &&
|
|
(conn = GetConnectionWithRemoteAddress(ep2_ch1(), cellular[0])) !=
|
|
nullptr &&
|
|
conn != ep2_ch1()->selected_connection() && conn->receiving(),
|
|
kDefaultTimeout);
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Tests that we can switch links via continual gathering.
|
|
TEST_F(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_F(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();
|
|
}
|
|
|
|
// A collection of tests which tests a single P2PTransportChannel by sending
|
|
// pings.
|
|
class P2PTransportChannelPingTest : public ::testing::Test,
|
|
public sigslot::has_slots<> {
|
|
public:
|
|
P2PTransportChannelPingTest()
|
|
: vss_(new rtc::VirtualSocketServer()), thread_(vss_.get()) {}
|
|
|
|
protected:
|
|
void PrepareChannel(P2PTransportChannel* ch) {
|
|
ch->SetIceRole(ICEROLE_CONTROLLING);
|
|
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,
|
|
const std::string& 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<Port*>(ch->ports()[0]);
|
|
}
|
|
|
|
Port* GetPrunedPort(P2PTransportChannel* ch) {
|
|
if (ch->pruned_ports().empty()) {
|
|
return nullptr;
|
|
}
|
|
return static_cast<Port*>(ch->pruned_ports()[0]);
|
|
}
|
|
|
|
Connection* GetConnectionTo(P2PTransportChannel* ch,
|
|
const std::string& 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,
|
|
const std::string& 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<rtc::NetworkRoute> 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,
|
|
const std::string& remote_ufrag,
|
|
int priority,
|
|
uint32_t nomination,
|
|
const absl::optional<std::string>& piggyback_ping_id) {
|
|
IceMessage msg;
|
|
msg.SetType(STUN_BINDING_REQUEST);
|
|
msg.AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_USERNAME,
|
|
conn->local_candidate().username() + ":" + remote_ufrag));
|
|
msg.AddAttribute(
|
|
std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY, priority));
|
|
if (nomination != 0) {
|
|
msg.AddAttribute(std::make_unique<StunUInt32Attribute>(
|
|
STUN_ATTR_NOMINATION, nomination));
|
|
}
|
|
if (piggyback_ping_id) {
|
|
msg.AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_LAST_ICE_CHECK_RECEIVED, piggyback_ping_id.value()));
|
|
}
|
|
msg.SetTransactionID(rtc::CreateRandomString(kStunTransactionIdLength));
|
|
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,
|
|
const std::string& 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, std::string 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;
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<rtc::VirtualSocketServer> vss_;
|
|
rtc::AutoSocketServerThread thread_;
|
|
int selected_candidate_pair_switches_ = 0;
|
|
int last_sent_packet_id_ = -1;
|
|
bool channel_ready_to_send_ = false;
|
|
absl::optional<CandidatePairChangeEvent> last_candidate_change_event_;
|
|
IceTransportState channel_state_ = IceTransportState::STATE_INIT;
|
|
absl::optional<rtc::NetworkRoute> last_network_route_;
|
|
};
|
|
|
|
TEST_F(P2PTransportChannelPingTest, TestTriggeredChecks) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("trigger checks", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestAllConnectionsPingedSufficiently) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("ping sufficiently", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestStunPingIntervals) {
|
|
rtc::ScopedFakeClock clock;
|
|
int RTT_RATIO = 4;
|
|
int SCHEDULING_RANGE = 200;
|
|
int RTT_RANGE = 10;
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("TestChannel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, PingingStartedAsSoonAsPossible) {
|
|
rtc::ScopedFakeClock clock;
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("TestChannel", 1, &pa);
|
|
ch.SetIceRole(ICEROLE_CONTROLLING);
|
|
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;
|
|
request.SetType(STUN_BINDING_REQUEST);
|
|
request.AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_USERNAME, kIceUfrag[1]));
|
|
uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
|
|
request.AddAttribute(std::make_unique<StunUInt32Attribute>(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_F(P2PTransportChannelPingTest, TestNoTriggeredChecksWhenWritable) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("trigger checks", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestFailedConnectionNotPingable) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("Do not ping failed connections", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestSignalStateChanged) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("state change", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestAddRemoteCandidateWithVariousUfrags) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("add candidate", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, ConnectionResurrection) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("connection resurrection", 1, &pa);
|
|
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.
|
|
conn1->Destroy();
|
|
EXPECT_TRUE_WAIT(GetConnectionTo(&ch, "1.1.1.1", 1) == nullptr,
|
|
kMediumTimeout);
|
|
|
|
// Create a minimal STUN message with prflx priority.
|
|
IceMessage request;
|
|
request.SetType(STUN_BINDING_REQUEST);
|
|
request.AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_USERNAME, kIceUfrag[1]));
|
|
uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
|
|
request.AddAttribute(std::make_unique<StunUInt32Attribute>(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_F(P2PTransportChannelPingTest, TestReceivingStateChange) {
|
|
rtc::ScopedFakeClock clock;
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("receiving state change", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("receiving state change", 1, &pa);
|
|
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<int>(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_F(P2PTransportChannelPingTest, TestPingOnNomination) {
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
"WebRTC-IceFieldTrials/send_ping_on_nomination_ice_controlled:true/");
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("receiving state change", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestPingOnSwitch) {
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
"WebRTC-IceFieldTrials/send_ping_on_switch_ice_controlling:true/");
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("receiving state change", 1, &pa);
|
|
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);
|
|
}
|
|
|
|
// 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_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("receiving state change", 1, &pa);
|
|
PrepareChannel(&ch);
|
|
ch.SetIceRole(ICEROLE_CONTROLLED);
|
|
ch.MaybeStartGathering();
|
|
// A minimal STUN message with prflx priority.
|
|
IceMessage request;
|
|
request.SetType(STUN_BINDING_REQUEST);
|
|
request.AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_USERNAME, kIceUfrag[1]));
|
|
uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
|
|
request.AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY,
|
|
prflx_priority));
|
|
TestUDPPort* port = static_cast<TestUDPPort*>(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<StunByteStringAttribute>(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_F(P2PTransportChannelPingTest, TestSelectConnectionBasedOnMediaReceived) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("receiving state change", 1, &pa);
|
|
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;
|
|
request.SetType(STUN_BINDING_REQUEST);
|
|
request.AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_USERNAME, kIceUfrag[1]));
|
|
uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
|
|
request.AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY,
|
|
prflx_priority));
|
|
request.AddAttribute(
|
|
std::make_unique<StunByteStringAttribute>(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_F(P2PTransportChannelPingTest,
|
|
TestControlledAgentDataReceivingTakesHigherPrecedenceThanPriority) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest,
|
|
TestControlledAgentNominationTakesHigherPrecedenceThanDataReceiving) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest,
|
|
TestControlledAgentSelectsConnectionWithHigherNomination) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest,
|
|
TestControlledAgentIgnoresSmallerNomination) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest,
|
|
TestControlledAgentWriteStateTakesHigherPrecedenceThanNomination) {
|
|
rtc::ScopedFakeClock clock;
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestAddRemoteCandidateWithAddressReuse) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("candidate reuse", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestDontPruneWhenWeak) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestDontPruneHighPriorityConnections) {
|
|
rtc::ScopedFakeClock clock;
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestGetState) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestConnectionPrunedAgain) {
|
|
rtc::ScopedFakeClock clock;
|
|
clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestDeleteConnectionsIfAllWriteTimedout) {
|
|
rtc::ScopedFakeClock clock;
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestIceRoleUpdatedOnRemovedPort) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa);
|
|
// 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<PortInterface*> 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_F(P2PTransportChannelPingTest, TestIceRoleUpdatedOnPortAfterIceRestart) {
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa);
|
|
// 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_F(P2PTransportChannelPingTest, TestPortDestroyedAfterTimeoutAndPruned) {
|
|
rtc::ScopedFakeClock fake_clock;
|
|
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestMaxOutstandingPingsFieldTrial) {
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
"WebRTC-IceFieldTrials/max_outstanding_pings:3/");
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("max", 1, &pa);
|
|
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(), kTurnUdpIntAddr, kTurnUdpExtAddr) {
|
|
network_manager_.AddInterface(kPublicAddrs[0]);
|
|
allocator_.reset(
|
|
CreateBasicPortAllocator(&network_manager_, 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) {
|
|
channel_.reset(new P2PTransportChannel("checks", 1, allocator()));
|
|
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(
|
|
const std::string& local_candidate_type,
|
|
const std::string& remote_candidate_type,
|
|
const std::string& 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<BasicPortAllocator> allocator_;
|
|
rtc::FakeNetworkManager network_manager_;
|
|
TestTurnServer turn_server_;
|
|
std::unique_ptr<P2PTransportChannel> channel_;
|
|
};
|
|
|
|
// 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_F(P2PTransportChannelMostLikelyToWorkFirstTest,
|
|
TestRelayRelayFirstWhenNothingPingedYet) {
|
|
const int max_strong_interval = 500;
|
|
P2PTransportChannel& ch = StartTransportChannel(true, max_strong_interval);
|
|
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_F(P2PTransportChannelMostLikelyToWorkFirstTest,
|
|
TestRelayRelayFirstWhenEverythingPinged) {
|
|
P2PTransportChannel& ch = StartTransportChannel(true, 500);
|
|
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_F(P2PTransportChannelMostLikelyToWorkFirstTest,
|
|
TestNoStarvationOnNonRelayConnection) {
|
|
P2PTransportChannel& ch = StartTransportChannel(true, 500);
|
|
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_F(P2PTransportChannelMostLikelyToWorkFirstTest,
|
|
TestSkipRelayToNonRelayConnectionsFieldTrial) {
|
|
webrtc::test::ScopedFieldTrials field_trials(
|
|
"WebRTC-IceFieldTrials/skip_relay_to_non_relay_connections:true/");
|
|
P2PTransportChannel& ch = StartTransportChannel(true, 500);
|
|
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_F(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()->AddTurnServer(config);
|
|
|
|
P2PTransportChannel& ch = StartTransportChannel(true, 500);
|
|
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);
|
|
}
|
|
|
|
// 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(P2PTransportChannelResolverTest, HostnameCandidateIsResolved) {
|
|
rtc::MockAsyncResolver mock_async_resolver;
|
|
EXPECT_CALL(mock_async_resolver, GetError()).WillOnce(Return(0));
|
|
EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
|
|
.WillOnce(Return(true));
|
|
// Destroy is called asynchronously after the address is resolved,
|
|
// so we need a variable to wait on.
|
|
bool destroy_called = false;
|
|
EXPECT_CALL(mock_async_resolver, Destroy(_))
|
|
.WillOnce(Assign(&destroy_called, true));
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(&mock_async_resolver));
|
|
|
|
FakePortAllocator allocator(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel channel("tn", 0, &allocator,
|
|
&mock_async_resolver_factory);
|
|
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());
|
|
WAIT(destroy_called, kShortTimeout);
|
|
}
|
|
|
|
// Test that if we signal a hostname candidate after the remote endpoint
|
|
// discovers a prflx remote candidate with the same underlying IP address, the
|
|
// prflx candidate is updated to a host candidate after the name resolution is
|
|
// done.
|
|
TEST_F(P2PTransportChannelTest,
|
|
PeerReflexiveCandidateBeforeSignalingWithMdnsName) {
|
|
rtc::MockAsyncResolver mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(&mock_async_resolver));
|
|
|
|
// ep1 and ep2 will only gather host candidates with addresses
|
|
// kPublicAddrs[0] and kPublicAddrs[1], respectively.
|
|
ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
|
|
// 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<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
|
|
GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
CreateChannels();
|
|
// Pause sending candidates from both endpoints until we find out what port
|
|
// number is assgined to ep1's host candidate.
|
|
PauseCandidates(0);
|
|
PauseCandidates(1);
|
|
ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
|
|
ASSERT_EQ(1u, GetEndpoint(0)->saved_candidates_[0]->candidates.size());
|
|
const auto& local_candidate =
|
|
GetEndpoint(0)->saved_candidates_[0]->candidates[0];
|
|
// The IP address of ep1's host candidate should be obfuscated.
|
|
EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
|
|
// This is the underlying private IP address of the same candidate at ep1.
|
|
const auto local_address = rtc::SocketAddress(
|
|
kPublicAddrs[0].ipaddr(), local_candidate.address().port());
|
|
// 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.
|
|
{
|
|
InSequence sequencer;
|
|
EXPECT_CALL(mock_async_resolver, Start(_));
|
|
EXPECT_CALL(mock_async_resolver, GetError()).WillOnce(Return(0));
|
|
// Let the mock resolver of ep2 receives the correct resolution.
|
|
EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(local_address), Return(true)));
|
|
}
|
|
// Destroy is called asynchronously after the address is resolved,
|
|
// so we need a variable to wait on.
|
|
bool destroy_called = false;
|
|
EXPECT_CALL(mock_async_resolver, Destroy(_))
|
|
.WillOnce(Assign(&destroy_called, true));
|
|
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());
|
|
|
|
WAIT(destroy_called, kShortTimeout);
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Test that if we discover a prflx candidate during the process of name
|
|
// resolution for a remote hostname candidate, we update the prflx candidate to
|
|
// a host candidate if the hostname candidate turns out to have the same IP
|
|
// address after the resolution completes.
|
|
TEST_F(P2PTransportChannelTest,
|
|
PeerReflexiveCandidateDuringResolvingHostCandidateWithMdnsName) {
|
|
auto mock_async_resolver = new NiceMock<rtc::MockAsyncResolver>();
|
|
ON_CALL(*mock_async_resolver, Destroy).WillByDefault([mock_async_resolver] {
|
|
delete mock_async_resolver;
|
|
});
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(mock_async_resolver));
|
|
|
|
// ep1 and ep2 will only gather host candidates with addresses
|
|
// kPublicAddrs[0] and kPublicAddrs[1], respectively.
|
|
ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
|
|
// ICE parameter will be set up when creating the channels.
|
|
set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
|
|
GetEndpoint(0)->network_manager_.set_mdns_responder(
|
|
std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
|
|
GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
CreateChannels();
|
|
// Pause sending candidates from both endpoints until we find out what port
|
|
// number is assgined to ep1's host candidate.
|
|
PauseCandidates(0);
|
|
PauseCandidates(1);
|
|
ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
|
|
ASSERT_EQ(1u, GetEndpoint(0)->saved_candidates_[0]->candidates.size());
|
|
const auto& local_candidate =
|
|
GetEndpoint(0)->saved_candidates_[0]->candidates[0];
|
|
// The IP address of ep1's host candidate should be obfuscated.
|
|
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());
|
|
bool mock_async_resolver_started = false;
|
|
// Not signaling done yet, and only make sure we are in the process of
|
|
// resolution.
|
|
EXPECT_CALL(*mock_async_resolver, Start(_))
|
|
.WillOnce(InvokeWithoutArgs([&mock_async_resolver_started]() {
|
|
mock_async_resolver_started = true;
|
|
}));
|
|
// Let ep1 signal its hostname candidate to ep2.
|
|
ResumeCandidates(0);
|
|
ASSERT_TRUE_WAIT(mock_async_resolver_started, kMediumTimeout);
|
|
// Now that ep2 is in the process of resolving the hostname candidate signaled
|
|
// by ep1. Let ep2 signal its host candidate with an IP address to ep1, so
|
|
// that ep1 can form a candidate pair, select it and start to ping ep2.
|
|
ResumeCandidates(1);
|
|
ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout);
|
|
// Let the mock resolver of ep2 receives the correct resolution.
|
|
EXPECT_CALL(*mock_async_resolver, GetResolvedAddress(_, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(local_address), Return(true)));
|
|
// Upon receiving a ping from ep1, ep2 adds a prflx candidate from the
|
|
// unknown address and establishes a connection.
|
|
//
|
|
// There is a caveat in our implementation associated with this expectation.
|
|
// See the big comment in P2PTransportChannel::OnUnknownAddress.
|
|
ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
|
|
EXPECT_EQ(PRFLX_PORT_TYPE,
|
|
ep2_ch1()->selected_connection()->remote_candidate().type());
|
|
// ep2 should also be able resolve the hostname candidate. The resolved remote
|
|
// host candidate should be merged with the prflx remote candidate.
|
|
mock_async_resolver->SignalDone(mock_async_resolver);
|
|
EXPECT_EQ_WAIT(LOCAL_PORT_TYPE,
|
|
ep2_ch1()->selected_connection()->remote_candidate().type(),
|
|
kMediumTimeout);
|
|
EXPECT_EQ(1u, ep2_ch1()->remote_candidates().size());
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
// Test that if we only gather and signal a host candidate, the IP address of
|
|
// which is obfuscated by an mDNS name, and if the peer can complete the name
|
|
// resolution with the correct IP address, we can have a p2p connection.
|
|
TEST_F(P2PTransportChannelTest, CanConnectWithHostCandidateWithMdnsName) {
|
|
NiceMock<rtc::MockAsyncResolver> mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(&mock_async_resolver));
|
|
|
|
// ep1 and ep2 will only gather host candidates with addresses
|
|
// kPublicAddrs[0] and kPublicAddrs[1], respectively.
|
|
ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
|
|
// ICE parameter will be set up when creating the channels.
|
|
set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
|
|
GetEndpoint(0)->network_manager_.set_mdns_responder(
|
|
std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
|
|
GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
CreateChannels();
|
|
// Pause sending candidates from both endpoints until we find out what port
|
|
// number is assgined to ep1's host candidate.
|
|
PauseCandidates(0);
|
|
PauseCandidates(1);
|
|
ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
|
|
ASSERT_EQ(1u, GetEndpoint(0)->saved_candidates_[0]->candidates.size());
|
|
const auto& local_candidate_ep1 =
|
|
GetEndpoint(0)->saved_candidates_[0]->candidates[0];
|
|
// 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());
|
|
|
|
EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(resolved_address_ep1), Return(true)));
|
|
// Let ep1 signal its hostname candidate to ep2.
|
|
ResumeCandidates(0);
|
|
|
|
// We should be able to receive a ping from ep2 and establish a connection
|
|
// with a peer reflexive candidate from ep2.
|
|
ASSERT_TRUE_WAIT((ep1_ch1()->selected_connection()) != nullptr,
|
|
kMediumTimeout);
|
|
EXPECT_EQ(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_F(P2PTransportChannelTest,
|
|
CandidatesSanitizedInStatsWhenMdnsObfuscationEnabled) {
|
|
NiceMock<rtc::MockAsyncResolver> mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(&mock_async_resolver));
|
|
|
|
// ep1 and ep2 will gather host candidates with addresses
|
|
// kPublicAddrs[0] and kPublicAddrs[1], respectively. ep1 also gathers a srflx
|
|
// and a relay candidates.
|
|
ConfigureEndpoints(OPEN, OPEN,
|
|
kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_TCP,
|
|
kOnlyLocalPorts);
|
|
// ICE parameter will be set up when creating the channels.
|
|
set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
|
|
GetEndpoint(0)->network_manager_.set_mdns_responder(
|
|
std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
|
|
GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
CreateChannels();
|
|
// Pause sending candidates from both endpoints until we find out what port
|
|
// number is assigned to ep1's host candidate.
|
|
PauseCandidates(0);
|
|
PauseCandidates(1);
|
|
// Ep1 has a UDP host, a srflx and a relay candidates.
|
|
ASSERT_EQ_WAIT(3u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
|
|
ASSERT_EQ_WAIT(1u, GetEndpoint(1)->saved_candidates_.size(), kMediumTimeout);
|
|
|
|
for (const auto& candidates_data : GetEndpoint(0)->saved_candidates_) {
|
|
ASSERT_EQ(1u, candidates_data->candidates.size());
|
|
const auto& local_candidate_ep1 = candidates_data->candidates[0];
|
|
if (local_candidate_ep1.type() == LOCAL_PORT_TYPE) {
|
|
// This is the underlying private IP address of the same candidate at ep1,
|
|
// and let the mock resolver of ep2 receive the correct resolution.
|
|
rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
|
|
resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr());
|
|
EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
|
|
.WillOnce(
|
|
DoAll(SetArgPointee<1>(resolved_address_ep1), Return(true)));
|
|
break;
|
|
}
|
|
}
|
|
ResumeCandidates(0);
|
|
ResumeCandidates(1);
|
|
|
|
ASSERT_EQ_WAIT(kIceGatheringComplete, ep1_ch1()->gathering_state(),
|
|
kMediumTimeout);
|
|
// We should have the following candidate pairs on both endpoints:
|
|
// ep1_host <-> ep2_host, ep1_srflx <-> ep2_host, ep1_relay <-> ep2_host
|
|
ASSERT_EQ_WAIT(3u, ep1_ch1()->connections().size(), kMediumTimeout);
|
|
ASSERT_EQ_WAIT(3u, ep2_ch1()->connections().size(), kMediumTimeout);
|
|
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
SelectedCandidatePairSanitizedWhenMdnsObfuscationEnabled) {
|
|
NiceMock<rtc::MockAsyncResolver> mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(&mock_async_resolver));
|
|
|
|
// ep1 and ep2 will gather host candidates with addresses
|
|
// kPublicAddrs[0] and kPublicAddrs[1], respectively.
|
|
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<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
|
|
GetEndpoint(1)->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
CreateChannels();
|
|
// Pause sending candidates from both endpoints until we find out what port
|
|
// number is assigned to ep1's host candidate.
|
|
PauseCandidates(0);
|
|
PauseCandidates(1);
|
|
ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
|
|
const auto& candidates_data = GetEndpoint(0)->saved_candidates_[0];
|
|
ASSERT_EQ(1u, candidates_data->candidates.size());
|
|
const auto& local_candidate_ep1 = candidates_data->candidates[0];
|
|
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());
|
|
EXPECT_CALL(mock_async_resolver, GetResolvedAddress(_, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(resolved_address_ep1), Return(true)));
|
|
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_F(P2PTransportChannelTest,
|
|
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_.reset(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<cricket::Candidate> 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_F(P2PTransportChannelTest,
|
|
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<MockMdnsResponder>();
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest,
|
|
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_F(P2PTransportChannelTest, SurfaceRequiresCoordination) {
|
|
webrtc::test::ScopedFieldTrials 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_F(P2PTransportChannelPingTest, TestInitialSelectDampening0) {
|
|
webrtc::test::ScopedFieldTrials 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(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestInitialSelectDampening) {
|
|
webrtc::test::ScopedFieldTrials 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(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestInitialSelectDampeningPingReceived) {
|
|
webrtc::test::ScopedFieldTrials 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(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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_F(P2PTransportChannelPingTest, TestInitialSelectDampeningBoth) {
|
|
webrtc::test::ScopedFieldTrials 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(), nullptr);
|
|
P2PTransportChannel ch("test channel", 1, &pa);
|
|
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);
|
|
}
|
|
|
|
TEST(P2PTransportChannel, InjectIceController) {
|
|
MockIceControllerFactory factory;
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
EXPECT_CALL(factory, RecordIceControllerCreated()).Times(1);
|
|
auto dummy = std::make_unique<cricket::P2PTransportChannel>(
|
|
"transport_name",
|
|
/* component= */ 77, &pa,
|
|
/* async_resolver_factory = */ nullptr,
|
|
/* event_log = */ nullptr, &factory);
|
|
}
|
|
|
|
class ForgetLearnedStateController : public cricket::BasicIceController {
|
|
public:
|
|
explicit ForgetLearnedStateController(
|
|
const cricket::IceControllerFactoryArgs& args)
|
|
: cricket::BasicIceController(args) {}
|
|
|
|
SwitchResult SortAndSwitchConnection(IceControllerEvent 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 =
|
|
IceControllerEvent(IceControllerEvent::ICE_CONTROLLER_RECHECK);
|
|
result.recheck_event->recheck_delay_ms = 100;
|
|
return result;
|
|
}
|
|
|
|
void ForgetThisConnectionNextTimeSortAndSwitchConnectionIsCalled(
|
|
Connection* con) {
|
|
forget_connnection_ = con;
|
|
}
|
|
|
|
private:
|
|
Connection* forget_connnection_ = nullptr;
|
|
};
|
|
|
|
class ForgetLearnedStateControllerFactory
|
|
: public cricket::IceControllerFactoryInterface {
|
|
public:
|
|
std::unique_ptr<cricket::IceControllerInterface> Create(
|
|
const cricket::IceControllerFactoryArgs& args) override {
|
|
auto controller = std::make_unique<ForgetLearnedStateController>(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_F(P2PTransportChannelPingTest, TestForgetLearnedState) {
|
|
ForgetLearnedStateControllerFactory factory;
|
|
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
|
P2PTransportChannel ch("ping sufficiently", 1, &pa, nullptr, nullptr,
|
|
&factory);
|
|
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);
|
|
|
|
// 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_F(P2PTransportChannelTest, DisableDnsLookupsWithTransportPolicyRelay) {
|
|
ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
|
|
kDefaultPortAllocatorFlags);
|
|
auto* ep1 = GetEndpoint(0);
|
|
ep1->allocator_->SetCandidateFilter(CF_RELAY);
|
|
|
|
rtc::MockAsyncResolver mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
ON_CALL(mock_async_resolver_factory, Create())
|
|
.WillByDefault(Return(&mock_async_resolver));
|
|
ep1->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
|
|
bool lookup_started = false;
|
|
ON_CALL(mock_async_resolver, Start(_))
|
|
.WillByDefault(Assign(&lookup_started, true));
|
|
|
|
CreateChannels();
|
|
|
|
ep1_ch1()->AddRemoteCandidate(
|
|
CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100));
|
|
|
|
EXPECT_FALSE(lookup_started);
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
TEST_F(P2PTransportChannelTest, DisableDnsLookupsWithTransportPolicyNone) {
|
|
ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
|
|
kDefaultPortAllocatorFlags);
|
|
auto* ep1 = GetEndpoint(0);
|
|
ep1->allocator_->SetCandidateFilter(CF_NONE);
|
|
|
|
rtc::MockAsyncResolver mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
ON_CALL(mock_async_resolver_factory, Create())
|
|
.WillByDefault(Return(&mock_async_resolver));
|
|
ep1->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
|
|
bool lookup_started = false;
|
|
ON_CALL(mock_async_resolver, Start(_))
|
|
.WillByDefault(Assign(&lookup_started, true));
|
|
|
|
CreateChannels();
|
|
|
|
ep1_ch1()->AddRemoteCandidate(
|
|
CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100));
|
|
|
|
EXPECT_FALSE(lookup_started);
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
TEST_F(P2PTransportChannelTest, EnableDnsLookupsWithTransportPolicyNoHost) {
|
|
ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
|
|
kDefaultPortAllocatorFlags);
|
|
auto* ep1 = GetEndpoint(0);
|
|
ep1->allocator_->SetCandidateFilter(CF_ALL & ~CF_HOST);
|
|
|
|
rtc::MockAsyncResolver mock_async_resolver;
|
|
webrtc::MockAsyncResolverFactory mock_async_resolver_factory;
|
|
EXPECT_CALL(mock_async_resolver_factory, Create())
|
|
.WillOnce(Return(&mock_async_resolver));
|
|
EXPECT_CALL(mock_async_resolver, Destroy(_));
|
|
|
|
ep1->async_resolver_factory_ = &mock_async_resolver_factory;
|
|
|
|
bool lookup_started = false;
|
|
EXPECT_CALL(mock_async_resolver, Start(_))
|
|
.WillOnce(Assign(&lookup_started, true));
|
|
|
|
CreateChannels();
|
|
|
|
ep1_ch1()->AddRemoteCandidate(
|
|
CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100));
|
|
|
|
EXPECT_TRUE(lookup_started);
|
|
|
|
DestroyChannels();
|
|
}
|
|
|
|
} // namespace cricket
|