/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ // Original author: ekr@rtfm.com #include #include #include #include #include #include #include #include "sigslot.h" #include "logging.h" #include "ssl.h" #include "mozilla/Preferences.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" extern "C" { #include "r_types.h" #include "async_wait.h" #include "async_timer.h" #include "r_data.h" #include "util.h" #include "r_time.h" } #include "ice_ctx.h" #include "ice_peer_ctx.h" #include "ice_media_stream.h" #include "nricectx.h" #include "nricemediastream.h" #include "nriceresolverfake.h" #include "nriceresolver.h" #include "nrinterfaceprioritizer.h" #include "gtest_ringbuffer_dumper.h" #include "rlogconnector.h" #include "runnable_utils.h" #include "stunserver.h" #include "nr_socket_prsock.h" #include "test_nr_socket.h" #include "nsISocketFilter.h" #include "mozilla/net/DNS.h" #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" #include "gtest_utils.h" using namespace mozilla; static unsigned int kDefaultTimeout = 7000; // TODO(nils@mozilla.com): This should get replaced with some non-external // solution like discussed in bug 860775. const std::string kDefaultStunServerHostname((char*)"stun.l.google.com"); const std::string kBogusStunServerHostname( (char*)"stun-server-nonexistent.invalid"); const uint16_t kDefaultStunServerPort = 19305; const std::string kBogusIceCandidate( (char*)"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ"); const std::string kUnreachableHostIceCandidate( (char*)"candidate:0 1 UDP 2113601790 192.168.178.20 50769 typ host"); namespace { // DNS resolution helper code static std::string Resolve(const std::string& fqdn, int address_family) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = address_family; hints.ai_protocol = IPPROTO_UDP; struct addrinfo* res; int err = getaddrinfo(fqdn.c_str(), nullptr, &hints, &res); if (err) { std::cerr << "Error in getaddrinfo: " << err << std::endl; return ""; } char str_addr[64] = {0}; switch (res->ai_family) { case AF_INET: inet_ntop(AF_INET, &reinterpret_cast(res->ai_addr)->sin_addr, str_addr, sizeof(str_addr)); break; case AF_INET6: inet_ntop( AF_INET6, &reinterpret_cast(res->ai_addr)->sin6_addr, str_addr, sizeof(str_addr)); break; default: std::cerr << "Got unexpected address family in DNS lookup: " << res->ai_family << std::endl; freeaddrinfo(res); return ""; } if (!strlen(str_addr)) { std::cerr << "inet_ntop failed" << std::endl; } freeaddrinfo(res); return str_addr; } class StunTest : public MtransportTest { public: StunTest() : MtransportTest() {} void SetUp() override { MtransportTest::SetUp(); stun_server_hostname_ = kDefaultStunServerHostname; // If only a STUN server FQDN was provided, look up its IP address for the // address-only tests. if (stun_server_address_.empty() && !stun_server_hostname_.empty()) { stun_server_address_ = Resolve(stun_server_hostname_, AF_INET); ASSERT_TRUE(!stun_server_address_.empty()); } // Make sure NrIceCtx is in a testable state. test_utils_->SyncDispatchToSTS( WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal)); // NB: NrIceCtx::internal_DeinitializeGlobal destroys the RLogConnector // singleton. RLogConnector::CreateInstance(); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunServer::GetInstance, AF_INET)); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunServer::GetInstance, AF_INET6)); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET)); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6)); } void TearDown() override { test_utils_->SyncDispatchToSTS( WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal)); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunServer::ShutdownInstance)); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunTcpServer::ShutdownInstance)); RLogConnector::DestroyInstance(); MtransportTest::TearDown(); } }; enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL }; enum ConsentStatus { CONSENT_FRESH, CONSENT_STALE, CONSENT_EXPIRED }; typedef std::string (*CandidateFilter)(const std::string& candidate); std::vector split(const std::string& s, char delim) { std::vector elems; std::stringstream ss(s); std::string item; while (std::getline(ss, item, delim)) { elems.push_back(item); } return elems; } static std::string IsSrflxCandidate(const std::string& candidate) { std::vector tokens = split(candidate, ' '); if ((tokens.at(6) == "typ") && (tokens.at(7) == "srflx")) { return candidate; } return std::string(); } static std::string IsRelayCandidate(const std::string& candidate) { if (candidate.find("typ relay") != std::string::npos) { return candidate; } return std::string(); } static std::string IsTcpCandidate(const std::string& candidate) { if (candidate.find("TCP") != std::string::npos) { return candidate; } return std::string(); } static std::string IsTcpSoCandidate(const std::string& candidate) { if (candidate.find("tcptype so") != std::string::npos) { return candidate; } return std::string(); } static std::string IsLoopbackCandidate(const std::string& candidate) { if (candidate.find("127.0.0.") != std::string::npos) { return candidate; } return std::string(); } static std::string IsIpv4Candidate(const std::string& candidate) { std::vector tokens = split(candidate, ' '); if (tokens.at(4).find(':') == std::string::npos) { return candidate; } return std::string(); } static std::string SabotageHostCandidateAndDropReflexive( const std::string& candidate) { if (candidate.find("typ srflx") != std::string::npos) { return std::string(); } if (candidate.find("typ host") != std::string::npos) { return kUnreachableHostIceCandidate; } return candidate; } bool ContainsSucceededPair(const std::vector& pairs) { for (const auto& pair : pairs) { if (pair.state == NrIceCandidatePair::STATE_SUCCEEDED) { return true; } } return false; } // Note: Does not correspond to any notion of prioritization; this is just // so we can use stl containers/algorithms that need a comparator bool operator<(const NrIceCandidate& lhs, const NrIceCandidate& rhs) { if (lhs.cand_addr.host == rhs.cand_addr.host) { if (lhs.cand_addr.port == rhs.cand_addr.port) { if (lhs.cand_addr.transport == rhs.cand_addr.transport) { if (lhs.type == rhs.type) { return lhs.tcp_type < rhs.tcp_type; } return lhs.type < rhs.type; } return lhs.cand_addr.transport < rhs.cand_addr.transport; } return lhs.cand_addr.port < rhs.cand_addr.port; } return lhs.cand_addr.host < rhs.cand_addr.host; } bool operator==(const NrIceCandidate& lhs, const NrIceCandidate& rhs) { return !((lhs < rhs) || (rhs < lhs)); } class IceCandidatePairCompare { public: bool operator()(const NrIceCandidatePair& lhs, const NrIceCandidatePair& rhs) const { if (lhs.priority == rhs.priority) { if (lhs.local == rhs.local) { if (lhs.remote == rhs.remote) { return lhs.codeword < rhs.codeword; } return lhs.remote < rhs.remote; } return lhs.local < rhs.local; } return lhs.priority < rhs.priority; } }; class IceTestPeer; class SchedulableTrickleCandidate { public: SchedulableTrickleCandidate(IceTestPeer* peer, size_t stream, const std::string& candidate, const std::string& ufrag, MtransportTestUtils* utils) : peer_(peer), stream_(stream), candidate_(candidate), ufrag_(ufrag), timer_handle_(nullptr), test_utils_(utils) {} ~SchedulableTrickleCandidate() { if (timer_handle_) NR_async_timer_cancel(timer_handle_); } void Schedule(unsigned int ms) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &SchedulableTrickleCandidate::Schedule_s, ms)); } void Schedule_s(unsigned int ms) { MOZ_ASSERT(!timer_handle_); NR_ASYNC_TIMER_SET(ms, Trickle_cb, this, &timer_handle_); } static void Trickle_cb(NR_SOCKET s, int how, void* cb_arg) { static_cast(cb_arg)->Trickle(); } void Trickle(); std::string& Candidate() { return candidate_; } const std::string& Candidate() const { return candidate_; } bool IsHost() const { return candidate_.find("typ host") != std::string::npos; } bool IsReflexive() const { return candidate_.find("typ srflx") != std::string::npos; } bool IsRelay() const { return candidate_.find("typ relay") != std::string::npos; } private: IceTestPeer* peer_; size_t stream_; std::string candidate_; std::string ufrag_; void* timer_handle_; MtransportTestUtils* test_utils_; DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate); }; class IceTestPeer : public sigslot::has_slots<> { public: IceTestPeer(const std::string& name, MtransportTestUtils* utils, bool offerer, const NrIceCtx::Config& config) : name_(name), ice_ctx_(NrIceCtx::Create(name)), offerer_(offerer), candidates_(), stream_counter_(0), shutting_down_(false), gathering_complete_(false), ready_ct_(0), ice_connected_(false), ice_failed_(false), ice_reached_checking_(false), received_(0), sent_(0), fake_resolver_(), dns_resolver_(new NrIceResolver()), remote_(nullptr), candidate_filter_(nullptr), expected_local_type_(NrIceCandidate::ICE_HOST), expected_local_transport_(kNrIceTransportUdp), expected_remote_type_(NrIceCandidate::ICE_HOST), trickle_mode_(TRICKLE_NONE), simulate_ice_lite_(false), nat_(new TestNat), test_utils_(utils) { ice_ctx_->SignalGatheringStateChange.connect( this, &IceTestPeer::GatheringStateChange); ice_ctx_->SignalConnectionStateChange.connect( this, &IceTestPeer::ConnectionStateChange); ice_ctx_->SetIceConfig(config); consent_timestamp_.tv_sec = 0; consent_timestamp_.tv_usec = 0; int r = ice_ctx_->SetNat(nat_); (void)r; MOZ_ASSERT(!r); } ~IceTestPeer() { test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IceTestPeer::Shutdown)); // Give the ICE destruction callback time to fire before // we destroy the resolver. PR_Sleep(1000); } std::string MakeTransportId(size_t index) const { char id[100]; snprintf(id, sizeof(id), "%s:stream%d", name_.c_str(), (int)index); return id; } void SetIceCredentials_s(NrIceMediaStream& stream) { static size_t counter = 0; std::ostringstream prefix; prefix << name_ << "-" << counter++; std::string ufrag = prefix.str() + "-ufrag"; std::string pwd = prefix.str() + "-pwd"; if (mIceCredentials.count(stream.GetId())) { mOldIceCredentials[stream.GetId()] = mIceCredentials[stream.GetId()]; } mIceCredentials[stream.GetId()] = std::make_pair(ufrag, pwd); stream.SetIceCredentials(ufrag, pwd); } void AddStream_s(int components) { std::string id = MakeTransportId(stream_counter_++); RefPtr stream = ice_ctx_->CreateStream(id, id, components); ASSERT_TRUE(stream); SetIceCredentials_s(*stream); stream->SignalCandidate.connect(this, &IceTestPeer::CandidateInitialized); stream->SignalReady.connect(this, &IceTestPeer::StreamReady); stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed); stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived); } void AddStream(int components) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::AddStream_s, components)); } void RemoveStream_s(size_t index) { ice_ctx_->DestroyStream(MakeTransportId(index)); } void RemoveStream(size_t index) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::RemoveStream_s, index)); } RefPtr GetStream_s(size_t index) { std::string id = MakeTransportId(index); return ice_ctx_->GetStream(id); } void SetStunServer(const std::string addr, uint16_t port, const char* transport = kNrIceTransportUdp) { if (addr.empty()) { // Happens when MOZ_DISABLE_NONLOCAL_CONNECTIONS is set return; } std::vector stun_servers; UniquePtr server( NrIceStunServer::Create(addr, port, transport)); stun_servers.push_back(*server); SetStunServers(stun_servers); } void SetStunServers(const std::vector& servers) { ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(servers))); } void UseTestStunServer() { SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(), TestStunServer::GetInstance(AF_INET)->port()); } void SetTurnServer(const std::string addr, uint16_t port, const std::string username, const std::string password, const char* transport) { std::vector password_vec(password.begin(), password.end()); SetTurnServer(addr, port, username, password_vec, transport); } void SetTurnServer(const std::string addr, uint16_t port, const std::string username, const std::vector password, const char* transport) { std::vector turn_servers; UniquePtr server( NrIceTurnServer::Create(addr, port, username, password, transport)); turn_servers.push_back(*server); ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(turn_servers))); } void SetTurnServers(const std::vector servers) { ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(servers))); } void SetFakeResolver(const std::string& ip, const std::string& fqdn) { ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init())); if (!ip.empty() && !fqdn.empty()) { PRNetAddr addr; PRStatus status = PR_StringToNetAddr(ip.c_str(), &addr); addr.inet.port = kDefaultStunServerPort; ASSERT_EQ(PR_SUCCESS, status); fake_resolver_.SetAddr(fqdn, addr); } ASSERT_TRUE( NS_SUCCEEDED(ice_ctx_->SetResolver(fake_resolver_.AllocateResolver()))); } void SetDNSResolver() { ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init())); ASSERT_TRUE( NS_SUCCEEDED(ice_ctx_->SetResolver(dns_resolver_->AllocateResolver()))); } void Gather(bool default_route_only = false, bool obfuscate_host_addresses = false) { nsresult res; test_utils_->SyncDispatchToSTS( WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartGathering, default_route_only, obfuscate_host_addresses)); ASSERT_TRUE(NS_SUCCEEDED(res)); } void SetCtxFlags(bool default_route_only) { test_utils_->SyncDispatchToSTS( WrapRunnable(ice_ctx_, &NrIceCtx::SetCtxFlags, default_route_only)); } nsTArray GetStunAddrs() { return ice_ctx_->GetStunAddrs(); } void SetStunAddrs(const nsTArray& addrs) { ice_ctx_->SetStunAddrs(addrs); } void UseNat() { nat_->enabled_ = true; } void SetTimerDivider(int div) { ice_ctx_->internal_SetTimerAccelarator(div); } void SetStunResponseDelay(uint32_t delay) { nat_->delay_stun_resp_ms_ = delay; } void SetFilteringType(TestNat::NatBehavior type) { MOZ_ASSERT(!nat_->has_port_mappings()); nat_->filtering_type_ = type; } void SetMappingType(TestNat::NatBehavior type) { MOZ_ASSERT(!nat_->has_port_mappings()); nat_->mapping_type_ = type; } void SetBlockUdp(bool block) { MOZ_ASSERT(!nat_->has_port_mappings()); nat_->block_udp_ = block; } void SetBlockStun(bool block) { nat_->block_stun_ = block; } // Get various pieces of state std::vector GetGlobalAttributes() { std::vector attrs(ice_ctx_->GetGlobalAttributes()); if (simulate_ice_lite_) { attrs.push_back("ice-lite"); } return attrs; } std::vector GetAttributes(size_t stream) { std::vector v; RUN_ON_THREAD( test_utils_->sts_target(), WrapRunnableRet(&v, this, &IceTestPeer::GetAttributes_s, stream)); return v; } std::string FilterCandidate(const std::string& candidate) { if (candidate_filter_) { return candidate_filter_(candidate); } return candidate; } std::vector GetAttributes_s(size_t index) { std::vector attributes; auto stream = GetStream_s(index); if (!stream) { EXPECT_TRUE(false) << "No such stream " << index; return attributes; } std::vector attributes_in = stream->GetAttributes(); for (const auto& attribute : attributes_in) { if (attribute.find("candidate:") != std::string::npos) { std::string candidate(FilterCandidate(attribute)); if (!candidate.empty()) { std::cerr << name_ << " Returning candidate: " << candidate << std::endl; attributes.push_back(candidate); } } else { attributes.push_back(attribute); } } return attributes; } void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote, std::string local_transport = kNrIceTransportUdp) { expected_local_type_ = local; expected_local_transport_ = local_transport; expected_remote_type_ = remote; } void SetExpectedRemoteCandidateAddr(const std::string& addr) { expected_remote_addr_ = addr; } int GetCandidatesPrivateIpv4Range(size_t stream) { std::vector attributes = GetAttributes(stream); int host_net = 0; for (const auto& a : attributes) { if (a.find("typ host") != std::string::npos) { nr_transport_addr addr; std::vector tokens = split(a, ' '); int r = nr_str_port_to_transport_addr(tokens.at(4).c_str(), 0, IPPROTO_UDP, &addr); MOZ_ASSERT(!r); if (!r && (addr.ip_version == NR_IPV4)) { int n = nr_transport_addr_get_private_addr_range(&addr); if (n) { if (host_net) { // TODO: add support for multiple private interfaces std::cerr << "This test doesn't support multiple private interfaces"; return -1; } host_net = n; } } } } return host_net; } bool gathering_complete() { return gathering_complete_; } int ready_ct() { return ready_ct_; } bool is_ready_s(size_t index) { auto media_stream = GetStream_s(index); if (!media_stream) { EXPECT_TRUE(false) << "No such stream " << index; return false; } return media_stream->state() == NrIceMediaStream::ICE_OPEN; } bool is_ready(size_t stream) { bool result; test_utils_->SyncDispatchToSTS( WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream)); return result; } bool ice_connected() { return ice_connected_; } bool ice_failed() { return ice_failed_; } bool ice_reached_checking() { return ice_reached_checking_; } size_t received() { return received_; } size_t sent() { return sent_; } void RestartIce() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::RestartIce_s)); } void RestartIce_s() { for (auto& stream : ice_ctx_->GetStreams()) { SetIceCredentials_s(*stream); } // take care of some local bookkeeping ready_ct_ = 0; gathering_complete_ = false; ice_connected_ = false; ice_failed_ = false; ice_reached_checking_ = false; remote_ = nullptr; } void RollbackIceRestart() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::RollbackIceRestart_s)); } void RollbackIceRestart_s() { for (auto& stream : ice_ctx_->GetStreams()) { mIceCredentials[stream->GetId()] = mOldIceCredentials[stream->GetId()]; } } // Start connecting to another peer void Connect_s(IceTestPeer* remote, TrickleMode trickle_mode, bool start = true) { nsresult res; remote_ = remote; trickle_mode_ = trickle_mode; ice_connected_ = false; ice_failed_ = false; ice_reached_checking_ = false; res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes()); ASSERT_FALSE(remote->simulate_ice_lite_ && (ice_ctx_->GetControlling() == NrIceCtx::ICE_CONTROLLED)); ASSERT_TRUE(NS_SUCCEEDED(res)); for (size_t i = 0; i < stream_counter_; ++i) { auto aStream = GetStream_s(i); if (aStream) { std::vector attributes = remote->GetAttributes(i); for (auto it = attributes.begin(); it != attributes.end();) { if (trickle_mode == TRICKLE_SIMULATE && it->find("candidate:") != std::string::npos) { std::cerr << name_ << " Deferring remote candidate: " << *it << std::endl; attributes.erase(it); } else { std::cerr << name_ << " Adding remote attribute: " + *it << std::endl; ++it; } } auto credentials = mIceCredentials[aStream->GetId()]; res = aStream->ConnectToPeer(credentials.first, credentials.second, attributes); ASSERT_TRUE(NS_SUCCEEDED(res)); } } if (start) { ice_ctx_->SetControlling(offerer_ ? NrIceCtx::ICE_CONTROLLING : NrIceCtx::ICE_CONTROLLED); // Now start checks res = ice_ctx_->StartChecks(); ASSERT_TRUE(NS_SUCCEEDED(res)); } } void Connect(IceTestPeer* remote, TrickleMode trickle_mode, bool start = true) { test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IceTestPeer::Connect_s, remote, trickle_mode, start)); } void SimulateTrickle(size_t stream) { std::cerr << name_ << " Doing trickle for stream " << stream << std::endl; // If we are in trickle deferred mode, now trickle in the candidates // for |stream| std::vector& candidates = ControlTrickle(stream); for (auto& candidate : candidates) { candidate->Schedule(0); } } // Allows test case to completely control when/if candidates are trickled // (test could also do things like insert extra trickle candidates, or // change existing ones, or insert duplicates, really anything is fair game) std::vector& ControlTrickle(size_t stream) { std::cerr << "Doing controlled trickle for stream " << stream << std::endl; std::vector attributes = remote_->GetAttributes(stream); for (const auto& attribute : attributes) { if (attribute.find("candidate:") != std::string::npos) { controlled_trickle_candidates_[stream].push_back( new SchedulableTrickleCandidate(this, stream, attribute, "", test_utils_)); } } return controlled_trickle_candidates_[stream]; } nsresult TrickleCandidate_s(const std::string& candidate, const std::string& ufrag, size_t index) { auto stream = GetStream_s(index); if (!stream) { // stream might have gone away before the trickle timer popped return NS_OK; } return stream->ParseTrickleCandidate(candidate, ufrag, ""); } void DumpCandidate(std::string which, const NrIceCandidate& cand) { std::string type; std::string tcp_type; std::string addr; int port; if (which.find("Remote") != std::string::npos) { addr = cand.cand_addr.host; port = cand.cand_addr.port; } else { addr = cand.local_addr.host; port = cand.local_addr.port; } switch (cand.type) { case NrIceCandidate::ICE_HOST: type = "host"; break; case NrIceCandidate::ICE_SERVER_REFLEXIVE: type = "srflx"; break; case NrIceCandidate::ICE_PEER_REFLEXIVE: type = "prflx"; break; case NrIceCandidate::ICE_RELAYED: type = "relay"; if (which.find("Local") != std::string::npos) { type += "(" + cand.local_addr.transport + ")"; } break; default: FAIL(); }; switch (cand.tcp_type) { case NrIceCandidate::ICE_NONE: break; case NrIceCandidate::ICE_ACTIVE: tcp_type = " tcptype=active"; break; case NrIceCandidate::ICE_PASSIVE: tcp_type = " tcptype=passive"; break; case NrIceCandidate::ICE_SO: tcp_type = " tcptype=so"; break; default: FAIL(); }; std::cerr << which << " --> " << type << " " << addr << ":" << port << "/" << cand.cand_addr.transport << tcp_type << " codeword=" << cand.codeword << std::endl; } void DumpAndCheckActiveCandidates_s() { std::cerr << name_ << " Active candidates:" << std::endl; for (const auto& stream : ice_ctx_->GetStreams()) { for (size_t j = 0; j < stream->components(); ++j) { std::cerr << name_ << " Stream " << stream->GetId() << " component " << j + 1 << std::endl; UniquePtr local; UniquePtr remote; nsresult res = stream->GetActivePair(j + 1, &local, &remote); if (res == NS_ERROR_NOT_AVAILABLE) { std::cerr << "Component unpaired or disabled." << std::endl; } else { ASSERT_TRUE(NS_SUCCEEDED(res)); DumpCandidate("Local ", *local); /* Depending on timing, and the whims of the network * stack/configuration we're running on top of, prflx is always a * possibility. */ if (expected_local_type_ == NrIceCandidate::ICE_HOST) { ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, local->type); ASSERT_NE(NrIceCandidate::ICE_RELAYED, local->type); } else { ASSERT_EQ(expected_local_type_, local->type); } ASSERT_EQ(expected_local_transport_, local->local_addr.transport); DumpCandidate("Remote ", *remote); /* Depending on timing, and the whims of the network * stack/configuration we're running on top of, prflx is always a * possibility. */ if (expected_remote_type_ == NrIceCandidate::ICE_HOST) { ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, remote->type); ASSERT_NE(NrIceCandidate::ICE_RELAYED, remote->type); } else { ASSERT_EQ(expected_remote_type_, remote->type); } if (!expected_remote_addr_.empty()) { ASSERT_EQ(expected_remote_addr_, remote->cand_addr.host); } } } } } void DumpAndCheckActiveCandidates() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::DumpAndCheckActiveCandidates_s)); } void Close() { test_utils_->SyncDispatchToSTS( WrapRunnable(ice_ctx_, &NrIceCtx::destroy_peer_ctx)); } void Shutdown() { std::cerr << name_ << " Shutdown" << std::endl; shutting_down_ = true; for (auto& controlled_trickle_candidate : controlled_trickle_candidates_) { for (auto& cand : controlled_trickle_candidate.second) { delete cand; } } ice_ctx_->Destroy(); ice_ctx_ = nullptr; if (remote_) { remote_->UnsetRemote(); remote_ = nullptr; } } void UnsetRemote() { remote_ = nullptr; } void StartChecks() { nsresult res; test_utils_->SyncDispatchToSTS(WrapRunnableRet( &res, ice_ctx_, &NrIceCtx::SetControlling, offerer_ ? NrIceCtx::ICE_CONTROLLING : NrIceCtx::ICE_CONTROLLED)); // Now start checks test_utils_->SyncDispatchToSTS( WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartChecks)); ASSERT_TRUE(NS_SUCCEEDED(res)); } // Handle events void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) { if (shutting_down_) { return; } if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) { return; } std::cerr << name_ << " Gathering complete" << std::endl; gathering_complete_ = true; std::cerr << name_ << " ATTRIBUTES:" << std::endl; for (const auto& stream : ice_ctx_->GetStreams()) { std::cerr << "Stream " << stream->GetId() << std::endl; std::vector attributes = stream->GetAttributes(); for (const auto& attribute : attributes) { std::cerr << attribute << std::endl; } } std::cerr << std::endl; } void CandidateInitialized(NrIceMediaStream* stream, const std::string& raw_candidate, const std::string& ufrag, const std::string& mdns_addr, const std::string& actual_addr) { std::string candidate(FilterCandidate(raw_candidate)); if (candidate.empty()) { return; } std::cerr << "Candidate for stream " << stream->name() << " initialized: " << candidate << std::endl; candidates_[stream->name()].push_back(candidate); // If we are connected, then try to trickle to the other side. if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) { // first, find the index of the stream we've been given so // we can get the corresponding stream on the remote side for (size_t i = 0; i < stream_counter_; ++i) { if (GetStream_s(i) == stream) { ASSERT_GT(remote_->stream_counter_, i); nsresult res = remote_->GetStream_s(i)->ParseTrickleCandidate( candidate, ufrag, ""); ASSERT_TRUE(NS_SUCCEEDED(res)); return; } } ADD_FAILURE() << "No matching stream found for " << stream; } } nsresult GetCandidatePairs_s(size_t stream_index, std::vector* pairs) { MOZ_ASSERT(pairs); auto stream = GetStream_s(stream_index); if (!stream) { // Is there a better error for "no such index"? ADD_FAILURE() << "No such media stream index: " << stream_index; return NS_ERROR_INVALID_ARG; } return stream->GetCandidatePairs(pairs); } nsresult GetCandidatePairs(size_t stream_index, std::vector* pairs) { nsresult v; test_utils_->SyncDispatchToSTS(WrapRunnableRet( &v, this, &IceTestPeer::GetCandidatePairs_s, stream_index, pairs)); return v; } void DumpCandidatePair(const NrIceCandidatePair& pair) { std::cerr << std::endl; DumpCandidate("Local", pair.local); DumpCandidate("Remote", pair.remote); std::cerr << "state = " << pair.state << " priority = " << pair.priority << " nominated = " << pair.nominated << " selected = " << pair.selected << " codeword = " << pair.codeword << std::endl; } void DumpCandidatePairs_s(NrIceMediaStream* stream) { std::vector pairs; nsresult res = stream->GetCandidatePairs(&pairs); ASSERT_TRUE(NS_SUCCEEDED(res)); std::cerr << "Begin list of candidate pairs [" << std::endl; for (auto& pair : pairs) { DumpCandidatePair(pair); } std::cerr << "]" << std::endl; } void DumpCandidatePairs_s() { std::cerr << "Dumping candidate pairs for all streams [" << std::endl; for (const auto& stream : ice_ctx_->GetStreams()) { DumpCandidatePairs_s(stream.get()); } std::cerr << "]" << std::endl; } bool CandidatePairsPriorityDescending( const std::vector& pairs) { // Verify that priority is descending uint64_t priority = std::numeric_limits::max(); for (size_t p = 0; p < pairs.size(); ++p) { if (priority < pairs[p].priority) { std::cerr << "Priority increased in subsequent pairs:" << std::endl; DumpCandidatePair(pairs[p - 1]); DumpCandidatePair(pairs[p]); return false; } if (priority == pairs[p].priority) { if (!IceCandidatePairCompare()(pairs[p], pairs[p - 1]) && !IceCandidatePairCompare()(pairs[p - 1], pairs[p])) { std::cerr << "Ignoring identical pair from trigger check" << std::endl; } else { std::cerr << "Duplicate priority in subseqent pairs:" << std::endl; DumpCandidatePair(pairs[p - 1]); DumpCandidatePair(pairs[p]); return false; } } priority = pairs[p].priority; } return true; } void UpdateAndValidateCandidatePairs( size_t stream_index, std::vector* new_pairs) { std::vector old_pairs = *new_pairs; GetCandidatePairs(stream_index, new_pairs); ASSERT_TRUE(CandidatePairsPriorityDescending(*new_pairs)) << "New list of " "candidate pairs is either not sorted in priority order, or has " "duplicate priorities."; ASSERT_TRUE(CandidatePairsPriorityDescending(old_pairs)) << "Old list of " "candidate pairs is either not sorted in priority order, or has " "duplicate priorities. This indicates some bug in the test case."; std::vector added_pairs; std::vector removed_pairs; // set_difference computes the set of elements that are present in the // first set, but not the second // NrIceCandidatePair::operator< compares based on the priority, local // candidate, and remote candidate in that order. This means this will // catch cases where the priority has remained the same, but one of the // candidates has changed. std::set_difference((*new_pairs).begin(), (*new_pairs).end(), old_pairs.begin(), old_pairs.end(), std::inserter(added_pairs, added_pairs.begin()), IceCandidatePairCompare()); std::set_difference(old_pairs.begin(), old_pairs.end(), (*new_pairs).begin(), (*new_pairs).end(), std::inserter(removed_pairs, removed_pairs.begin()), IceCandidatePairCompare()); for (auto& added_pair : added_pairs) { std::cerr << "Found new candidate pair." << std::endl; DumpCandidatePair(added_pair); } for (auto& removed_pair : removed_pairs) { std::cerr << "Pre-existing candidate pair is now missing:" << std::endl; DumpCandidatePair(removed_pair); } ASSERT_TRUE(removed_pairs.empty()) << "At least one candidate pair has " "gone missing."; } void StreamReady(NrIceMediaStream* stream) { ++ready_ct_; std::cerr << name_ << " Stream ready for " << stream->name() << " ct=" << ready_ct_ << std::endl; DumpCandidatePairs_s(stream); } void StreamFailed(NrIceMediaStream* stream) { std::cerr << name_ << " Stream failed for " << stream->name() << " ct=" << ready_ct_ << std::endl; DumpCandidatePairs_s(stream); } void ConnectionStateChange(NrIceCtx* ctx, NrIceCtx::ConnectionState state) { (void)ctx; switch (state) { case NrIceCtx::ICE_CTX_INIT: break; case NrIceCtx::ICE_CTX_CHECKING: std::cerr << name_ << " ICE reached checking" << std::endl; ice_reached_checking_ = true; break; case NrIceCtx::ICE_CTX_CONNECTED: std::cerr << name_ << " ICE connected" << std::endl; ice_connected_ = true; break; case NrIceCtx::ICE_CTX_COMPLETED: std::cerr << name_ << " ICE completed" << std::endl; break; case NrIceCtx::ICE_CTX_FAILED: std::cerr << name_ << " ICE failed" << std::endl; ice_failed_ = true; break; case NrIceCtx::ICE_CTX_DISCONNECTED: std::cerr << name_ << " ICE disconnected" << std::endl; ice_connected_ = false; break; default: MOZ_CRASH(); } } void PacketReceived(NrIceMediaStream* stream, int component, const unsigned char* data, int len) { std::cerr << name_ << ": received " << len << " bytes" << std::endl; ++received_; } void SendPacket(int stream, int component, const unsigned char* data, int len) { auto media_stream = GetStream_s(stream); if (!media_stream) { ADD_FAILURE() << "No such stream " << stream; return; } ASSERT_TRUE(NS_SUCCEEDED(media_stream->SendPacket(component, data, len))); ++sent_; std::cerr << name_ << ": sent " << len << " bytes" << std::endl; } void SendFailure(int stream, int component) { auto media_stream = GetStream_s(stream); if (!media_stream) { ADD_FAILURE() << "No such stream " << stream; return; } const std::string d("FAIL"); ASSERT_TRUE(NS_FAILED(media_stream->SendPacket( component, reinterpret_cast(d.c_str()), d.length()))); std::cerr << name_ << ": send failed as expected" << std::endl; } void SetCandidateFilter(CandidateFilter filter) { candidate_filter_ = filter; } void ParseCandidate_s(size_t i, const std::string& candidate, const std::string& mdns_addr) { auto media_stream = GetStream_s(i); ASSERT_TRUE(media_stream.get()) << "No such stream " << i; media_stream->ParseTrickleCandidate(candidate, "", mdns_addr); } void ParseCandidate(size_t i, const std::string& candidate, const std::string& mdns_addr) { test_utils_->SyncDispatchToSTS(WrapRunnable( this, &IceTestPeer::ParseCandidate_s, i, candidate, mdns_addr)); } void DisableComponent_s(size_t index, int component_id) { ASSERT_LT(index, stream_counter_); auto stream = GetStream_s(index); ASSERT_TRUE(stream.get()) << "No such stream " << index; nsresult res = stream->DisableComponent(component_id); ASSERT_TRUE(NS_SUCCEEDED(res)); } void DisableComponent(size_t stream, int component_id) { test_utils_->SyncDispatchToSTS(WrapRunnable( this, &IceTestPeer::DisableComponent_s, stream, component_id)); } void AssertConsentRefresh_s(size_t index, int component_id, ConsentStatus status) { ASSERT_LT(index, stream_counter_); auto stream = GetStream_s(index); ASSERT_TRUE(stream.get()) << "No such stream " << index; bool can_send; struct timeval timestamp; nsresult res = stream->GetConsentStatus(component_id, &can_send, ×tamp); ASSERT_TRUE(NS_SUCCEEDED(res)); if (status == CONSENT_EXPIRED) { ASSERT_EQ(can_send, 0); } else { ASSERT_EQ(can_send, 1); } if (consent_timestamp_.tv_sec) { if (status == CONSENT_FRESH) { ASSERT_EQ(r_timeval_cmp(×tamp, &consent_timestamp_), 1); } else { ASSERT_EQ(r_timeval_cmp(×tamp, &consent_timestamp_), 0); } } consent_timestamp_.tv_sec = timestamp.tv_sec; consent_timestamp_.tv_usec = timestamp.tv_usec; std::cerr << name_ << ": new consent timestamp = " << consent_timestamp_.tv_sec << "." << consent_timestamp_.tv_usec << std::endl; } void AssertConsentRefresh(ConsentStatus status) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::AssertConsentRefresh_s, 0, 1, status)); } void ChangeNetworkState_s(bool online) { ice_ctx_->UpdateNetworkState(online); } void ChangeNetworkStateToOffline() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::ChangeNetworkState_s, false)); } void ChangeNetworkStateToOnline() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::ChangeNetworkState_s, true)); } void SetControlling(NrIceCtx::Controlling controlling) { nsresult res; test_utils_->SyncDispatchToSTS(WrapRunnableRet( &res, ice_ctx_, &NrIceCtx::SetControlling, controlling)); ASSERT_TRUE(NS_SUCCEEDED(res)); } NrIceCtx::Controlling GetControlling() { return ice_ctx_->GetControlling(); } void SetTiebreaker(uint64_t tiebreaker) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &IceTestPeer::SetTiebreaker_s, tiebreaker)); } void SetTiebreaker_s(uint64_t tiebreaker) { ice_ctx_->peer()->tiebreaker = tiebreaker; } void SimulateIceLite() { simulate_ice_lite_ = true; SetControlling(NrIceCtx::ICE_CONTROLLED); } nsresult GetDefaultCandidate(unsigned int stream, NrIceCandidate* cand) { nsresult rv; test_utils_->SyncDispatchToSTS(WrapRunnableRet( &rv, this, &IceTestPeer::GetDefaultCandidate_s, stream, cand)); return rv; } nsresult GetDefaultCandidate_s(unsigned int index, NrIceCandidate* cand) { return GetStream_s(index)->GetDefaultCandidate(1, cand); } private: std::string name_; RefPtr ice_ctx_; bool offerer_; std::map> candidates_; // Maps from stream id to list of remote trickle candidates std::map> controlled_trickle_candidates_; std::map> mIceCredentials; std::map> mOldIceCredentials; size_t stream_counter_; bool shutting_down_; bool gathering_complete_; int ready_ct_; bool ice_connected_; bool ice_failed_; bool ice_reached_checking_; size_t received_; size_t sent_; struct timeval consent_timestamp_; NrIceResolverFake fake_resolver_; RefPtr dns_resolver_; IceTestPeer* remote_; CandidateFilter candidate_filter_; NrIceCandidate::Type expected_local_type_; std::string expected_local_transport_; NrIceCandidate::Type expected_remote_type_; std::string expected_remote_addr_; TrickleMode trickle_mode_; bool simulate_ice_lite_; RefPtr nat_; MtransportTestUtils* test_utils_; }; void SchedulableTrickleCandidate::Trickle() { timer_handle_ = nullptr; nsresult res = peer_->TrickleCandidate_s(candidate_, ufrag_, stream_); ASSERT_TRUE(NS_SUCCEEDED(res)); } class WebRtcIceGatherTest : public StunTest { public: void SetUp() override { StunTest::SetUp(); Preferences::SetInt("media.peerconnection.ice.tcp_so_sock_count", 3); test_utils_->SyncDispatchToSTS(WrapRunnable( TestStunServer::GetInstance(AF_INET), &TestStunServer::Reset)); if (TestStunServer::GetInstance(AF_INET6)) { test_utils_->SyncDispatchToSTS(WrapRunnable( TestStunServer::GetInstance(AF_INET6), &TestStunServer::Reset)); } } void TearDown() override { peer_ = nullptr; StunTest::TearDown(); } void EnsurePeer() { if (!peer_) { peer_ = MakeUnique("P1", test_utils_, true, NrIceCtx::Config()); } } void Gather(unsigned int waitTime = kDefaultTimeout, bool default_route_only = false, bool obfuscate_host_addresses = false) { EnsurePeer(); peer_->Gather(default_route_only, obfuscate_host_addresses); if (waitTime) { WaitForGather(waitTime); } } void WaitForGather(unsigned int waitTime = kDefaultTimeout) { ASSERT_TRUE_WAIT(peer_->gathering_complete(), waitTime); } void AddStunServerWithResponse(const std::string& fake_addr, uint16_t fake_port, const std::string& fqdn, const std::string& proto, std::vector* stun_servers) { int family; if (fake_addr.find(':') != std::string::npos) { family = AF_INET6; } else { family = AF_INET; } std::string stun_addr; uint16_t stun_port; if (proto == kNrIceTransportUdp) { TestStunServer::GetInstance(family)->SetResponseAddr(fake_addr, fake_port); stun_addr = TestStunServer::GetInstance(family)->addr(); stun_port = TestStunServer::GetInstance(family)->port(); } else if (proto == kNrIceTransportTcp) { TestStunTcpServer::GetInstance(family)->SetResponseAddr(fake_addr, fake_port); stun_addr = TestStunTcpServer::GetInstance(family)->addr(); stun_port = TestStunTcpServer::GetInstance(family)->port(); } else { MOZ_CRASH(); } if (!fqdn.empty()) { peer_->SetFakeResolver(stun_addr, fqdn); stun_addr = fqdn; } stun_servers->push_back( *NrIceStunServer::Create(stun_addr, stun_port, proto.c_str())); if (family == AF_INET6 && !fqdn.empty()) { stun_servers->back().SetUseIPv6IfFqdn(); } } void UseFakeStunUdpServerWithResponse( const std::string& fake_addr, uint16_t fake_port, const std::string& fqdn = std::string()) { EnsurePeer(); std::vector stun_servers; AddStunServerWithResponse(fake_addr, fake_port, fqdn, "udp", &stun_servers); peer_->SetStunServers(stun_servers); } void UseFakeStunTcpServerWithResponse( const std::string& fake_addr, uint16_t fake_port, const std::string& fqdn = std::string()) { EnsurePeer(); std::vector stun_servers; AddStunServerWithResponse(fake_addr, fake_port, fqdn, "tcp", &stun_servers); peer_->SetStunServers(stun_servers); } void UseFakeStunUdpTcpServersWithResponse(const std::string& fake_udp_addr, uint16_t fake_udp_port, const std::string& fake_tcp_addr, uint16_t fake_tcp_port) { EnsurePeer(); std::vector stun_servers; AddStunServerWithResponse(fake_udp_addr, fake_udp_port, "", // no fqdn "udp", &stun_servers); AddStunServerWithResponse(fake_tcp_addr, fake_tcp_port, "", // no fqdn "tcp", &stun_servers); peer_->SetStunServers(stun_servers); } void UseTestStunServer() { TestStunServer::GetInstance(AF_INET)->Reset(); peer_->SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(), TestStunServer::GetInstance(AF_INET)->port()); } // NB: Only does substring matching, watch out for stuff like "1.2.3.4" // matching "21.2.3.47". " 1.2.3.4 " should not have false positives. bool StreamHasMatchingCandidate(unsigned int stream, const std::string& match, const std::string& match2 = "") { std::vector attributes = peer_->GetAttributes(stream); for (auto& attribute : attributes) { if (std::string::npos != attribute.find(match)) { if (!match2.length() || std::string::npos != attribute.find(match2)) { return true; } } } return false; } void DumpAttributes(unsigned int stream) { std::vector attributes = peer_->GetAttributes(stream); std::cerr << "Attributes for stream " << stream << "->" << attributes.size() << std::endl; for (const auto& a : attributes) { std::cerr << "Attribute: " << a << std::endl; } } protected: mozilla::UniquePtr peer_; }; class WebRtcIceConnectTest : public StunTest { public: WebRtcIceConnectTest() : initted_(false), test_stun_server_inited_(false), use_nat_(false), filtering_type_(TestNat::ENDPOINT_INDEPENDENT), mapping_type_(TestNat::ENDPOINT_INDEPENDENT), block_udp_(false) {} void SetUp() override { StunTest::SetUp(); nsresult rv; target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); ASSERT_TRUE(NS_SUCCEEDED(rv)); } void TearDown() override { p1_ = nullptr; p2_ = nullptr; StunTest::TearDown(); } void AddStream(int components) { Init(); p1_->AddStream(components); p2_->AddStream(components); } void RemoveStream(size_t index) { p1_->RemoveStream(index); p2_->RemoveStream(index); } void Init(bool setup_stun_servers = true, NrIceCtx::Policy ice_policy = NrIceCtx::ICE_POLICY_ALL) { if (initted_) { return; } NrIceCtx::Config config; config.mPolicy = ice_policy; p1_ = MakeUnique("P1", test_utils_, true, config); p2_ = MakeUnique("P2", test_utils_, false, config); InitPeer(p1_.get(), setup_stun_servers); InitPeer(p2_.get(), setup_stun_servers); initted_ = true; } void InitPeer(IceTestPeer* peer, bool setup_stun_servers = true) { if (use_nat_) { // If we enable nat simulation, but still use a real STUN server somewhere // on the internet, we will see failures if there is a real NAT in // addition to our simulated one, particularly if it disallows // hairpinning. if (setup_stun_servers) { InitTestStunServer(); peer->UseTestStunServer(); } peer->UseNat(); peer->SetFilteringType(filtering_type_); peer->SetMappingType(mapping_type_); peer->SetBlockUdp(block_udp_); } else if (setup_stun_servers) { std::vector stun_servers; stun_servers.push_back(*NrIceStunServer::Create( stun_server_address_, kDefaultStunServerPort, kNrIceTransportUdp)); peer->SetStunServers(stun_servers); } } bool Gather(unsigned int waitTime = kDefaultTimeout, bool default_route_only = false) { Init(); return GatherCallerAndCallee(p1_.get(), p2_.get(), waitTime, default_route_only); } bool GatherCallerAndCallee(IceTestPeer* caller, IceTestPeer* callee, unsigned int waitTime = kDefaultTimeout, bool default_route_only = false) { caller->Gather(default_route_only); callee->Gather(default_route_only); if (waitTime) { EXPECT_TRUE_WAIT(caller->gathering_complete(), waitTime); if (!caller->gathering_complete()) return false; EXPECT_TRUE_WAIT(callee->gathering_complete(), waitTime); if (!callee->gathering_complete()) return false; } return true; } void UseNat() { // to be useful, this method should be called before Init ASSERT_FALSE(initted_); use_nat_ = true; } void SetFilteringType(TestNat::NatBehavior type) { // to be useful, this method should be called before Init ASSERT_FALSE(initted_); filtering_type_ = type; } void SetMappingType(TestNat::NatBehavior type) { // to be useful, this method should be called before Init ASSERT_FALSE(initted_); mapping_type_ = type; } void BlockUdp() { // note: |block_udp_| is used only in InitPeer. // Use IceTestPeer::SetBlockUdp to act on the peer directly. block_udp_ = true; } void SetupAndCheckConsent() { p1_->SetTimerDivider(10); p2_->SetTimerDivider(10); ASSERT_TRUE(Gather()); Connect(); p1_->AssertConsentRefresh(CONSENT_FRESH); p2_->AssertConsentRefresh(CONSENT_FRESH); SendReceive(); } void AssertConsentRefresh(ConsentStatus status = CONSENT_FRESH) { p1_->AssertConsentRefresh(status); p2_->AssertConsentRefresh(status); } void InitTestStunServer() { if (test_stun_server_inited_) { return; } std::cerr << "Resetting TestStunServer" << std::endl; TestStunServer::GetInstance(AF_INET)->Reset(); test_stun_server_inited_ = true; } void UseTestStunServer() { InitTestStunServer(); p1_->UseTestStunServer(); p2_->UseTestStunServer(); } void SetTurnServer(const std::string addr, uint16_t port, const std::string username, const std::string password, const char* transport = kNrIceTransportUdp) { p1_->SetTurnServer(addr, port, username, password, transport); p2_->SetTurnServer(addr, port, username, password, transport); } void SetTurnServers(const std::vector& servers) { p1_->SetTurnServers(servers); p2_->SetTurnServers(servers); } void SetCandidateFilter(CandidateFilter filter, bool both = true) { p1_->SetCandidateFilter(filter); if (both) { p2_->SetCandidateFilter(filter); } } void Connect() { ConnectCallerAndCallee(p1_.get(), p2_.get()); } void ConnectCallerAndCallee(IceTestPeer* caller, IceTestPeer* callee, TrickleMode mode = TRICKLE_NONE) { ASSERT_TRUE(caller->ready_ct() == 0); ASSERT_TRUE(caller->ice_connected() == 0); ASSERT_TRUE(caller->ice_reached_checking() == 0); ASSERT_TRUE(callee->ready_ct() == 0); ASSERT_TRUE(callee->ice_connected() == 0); ASSERT_TRUE(callee->ice_reached_checking() == 0); // IceTestPeer::Connect grabs attributes from the first arg, and // gives them to |this|, meaning that callee->Connect(caller, ...) // simulates caller sending an offer to callee. Order matters here // because it determines which peer is controlling. callee->Connect(caller, mode); caller->Connect(callee, mode); if (mode != TRICKLE_SIMULATE) { ASSERT_TRUE_WAIT(caller->ice_connected() && callee->ice_connected(), kDefaultTimeout); ASSERT_TRUE(caller->ready_ct() >= 1 && callee->ready_ct() >= 1); ASSERT_TRUE(caller->ice_reached_checking()); ASSERT_TRUE(callee->ice_reached_checking()); caller->DumpAndCheckActiveCandidates(); callee->DumpAndCheckActiveCandidates(); } } void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote, std::string transport = kNrIceTransportUdp) { p1_->SetExpectedTypes(local, remote, transport); p2_->SetExpectedTypes(local, remote, transport); } void SetExpectedRemoteCandidateAddr(const std::string& addr) { p1_->SetExpectedRemoteCandidateAddr(addr); p2_->SetExpectedRemoteCandidateAddr(addr); } void ConnectP1(TrickleMode mode = TRICKLE_NONE) { p1_->Connect(p2_.get(), mode); } void ConnectP2(TrickleMode mode = TRICKLE_NONE) { p2_->Connect(p1_.get(), mode); } void WaitForConnectedStreams(int expected_streams = 1) { ASSERT_TRUE_WAIT(p1_->ready_ct() == expected_streams && p2_->ready_ct() == expected_streams, kDefaultTimeout); ASSERT_TRUE_WAIT(p1_->ice_connected() && p2_->ice_connected(), kDefaultTimeout); } void AssertCheckingReached() { ASSERT_TRUE(p1_->ice_reached_checking()); ASSERT_TRUE(p2_->ice_reached_checking()); } void WaitForConnected(unsigned int timeout = kDefaultTimeout) { ASSERT_TRUE_WAIT(p1_->ice_connected(), timeout); ASSERT_TRUE_WAIT(p2_->ice_connected(), timeout); } void WaitForGather() { ASSERT_TRUE_WAIT(p1_->gathering_complete(), kDefaultTimeout); ASSERT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout); } void WaitForDisconnected(unsigned int timeout = kDefaultTimeout) { ASSERT_TRUE(p1_->ice_connected()); ASSERT_TRUE(p2_->ice_connected()); ASSERT_TRUE_WAIT(p1_->ice_connected() == 0 && p2_->ice_connected() == 0, timeout); } void WaitForFailed(unsigned int timeout = kDefaultTimeout) { ASSERT_TRUE_WAIT(p1_->ice_failed() && p2_->ice_failed(), timeout); } void ConnectTrickle(TrickleMode trickle = TRICKLE_SIMULATE) { p2_->Connect(p1_.get(), trickle); p1_->Connect(p2_.get(), trickle); } void SimulateTrickle(size_t stream) { p1_->SimulateTrickle(stream); p2_->SimulateTrickle(stream); ASSERT_TRUE_WAIT(p1_->is_ready(stream), kDefaultTimeout); ASSERT_TRUE_WAIT(p2_->is_ready(stream), kDefaultTimeout); } void SimulateTrickleP1(size_t stream) { p1_->SimulateTrickle(stream); } void SimulateTrickleP2(size_t stream) { p2_->SimulateTrickle(stream); } void CloseP1() { p1_->Close(); } void ConnectThenDelete() { p2_->Connect(p1_.get(), TRICKLE_NONE, false); p1_->Connect(p2_.get(), TRICKLE_NONE, true); test_utils_->SyncDispatchToSTS( WrapRunnable(this, &WebRtcIceConnectTest::CloseP1)); p2_->StartChecks(); // Wait to see if we crash PR_Sleep(PR_MillisecondsToInterval(kDefaultTimeout)); } // default is p1_ sending to p2_ void SendReceive() { SendReceive(p1_.get(), p2_.get()); } void SendReceive(IceTestPeer* p1, IceTestPeer* p2, bool expect_tx_failure = false, bool expect_rx_failure = false) { size_t previousSent = p1->sent(); size_t previousReceived = p2->received(); if (expect_tx_failure) { test_utils_->SyncDispatchToSTS( WrapRunnable(p1, &IceTestPeer::SendFailure, 0, 1)); ASSERT_EQ(previousSent, p1->sent()); } else { test_utils_->SyncDispatchToSTS( WrapRunnable(p1, &IceTestPeer::SendPacket, 0, 1, reinterpret_cast("TEST"), 4)); ASSERT_EQ(previousSent + 1, p1->sent()); } if (expect_rx_failure) { usleep(1000); ASSERT_EQ(previousReceived, p2->received()); } else { ASSERT_TRUE_WAIT(p2->received() == previousReceived + 1, 1000); } } void SendFailure() { test_utils_->SyncDispatchToSTS( WrapRunnable(p1_.get(), &IceTestPeer::SendFailure, 0, 1)); } protected: bool initted_; bool test_stun_server_inited_; nsCOMPtr target_; mozilla::UniquePtr p1_; mozilla::UniquePtr p2_; bool use_nat_; TestNat::NatBehavior filtering_type_; TestNat::NatBehavior mapping_type_; bool block_udp_; }; class WebRtcIcePrioritizerTest : public StunTest { public: WebRtcIcePrioritizerTest() : prioritizer_(nullptr) {} ~WebRtcIcePrioritizerTest() { if (prioritizer_) { nr_interface_prioritizer_destroy(&prioritizer_); } } void SetPriorizer(nr_interface_prioritizer* prioritizer) { prioritizer_ = prioritizer; } void AddInterface(const std::string& num, int type, int estimated_speed) { std::string str_addr = "10.0.0." + num; std::string ifname = "eth" + num; nr_local_addr local_addr; local_addr.interface.type = type; local_addr.interface.estimated_speed = estimated_speed; int r = nr_str_port_to_transport_addr(str_addr.c_str(), 0, IPPROTO_UDP, &(local_addr.addr)); ASSERT_EQ(0, r); strncpy(local_addr.addr.ifname, ifname.c_str(), MAXIFNAME - 1); local_addr.addr.ifname[MAXIFNAME - 1] = '\0'; r = nr_interface_prioritizer_add_interface(prioritizer_, &local_addr); ASSERT_EQ(0, r); r = nr_interface_prioritizer_sort_preference(prioritizer_); ASSERT_EQ(0, r); } void HasLowerPreference(const std::string& num1, const std::string& num2) { std::string key1 = "eth" + num1 + ":10.0.0." + num1; std::string key2 = "eth" + num2 + ":10.0.0." + num2; UCHAR pref1, pref2; int r = nr_interface_prioritizer_get_priority(prioritizer_, key1.c_str(), &pref1); ASSERT_EQ(0, r); r = nr_interface_prioritizer_get_priority(prioritizer_, key2.c_str(), &pref2); ASSERT_EQ(0, r); ASSERT_LE(pref1, pref2); } private: nr_interface_prioritizer* prioritizer_; }; class WebRtcIcePacketFilterTest : public StunTest { public: WebRtcIcePacketFilterTest() : udp_filter_(nullptr), tcp_filter_(nullptr) {} void SetUp() { StunTest::SetUp(); NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig()); // Set up enough of the ICE ctx to allow the packet filter to work ice_ctx_ = NrIceCtx::Create("test"); nsCOMPtr udp_handler = do_GetService(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID); ASSERT_TRUE(udp_handler); udp_handler->NewFilter(getter_AddRefs(udp_filter_)); nsCOMPtr tcp_handler = do_GetService(NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID); ASSERT_TRUE(tcp_handler); tcp_handler->NewFilter(getter_AddRefs(tcp_filter_)); } void TearDown() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &WebRtcIcePacketFilterTest::TearDown_s)); StunTest::TearDown(); } void TearDown_s() { ice_ctx_ = nullptr; } void TestIncoming(const uint8_t* data, uint32_t len, uint8_t from_addr, int from_port, bool expected_result) { mozilla::net::NetAddr addr; MakeNetAddr(&addr, from_addr, from_port); bool result; nsresult rv = udp_filter_->FilterPacket( &addr, data, len, nsISocketFilter::SF_INCOMING, &result); ASSERT_EQ(NS_OK, rv); ASSERT_EQ(expected_result, result); } void TestIncomingTcp(const uint8_t* data, uint32_t len, bool expected_result) { mozilla::net::NetAddr addr; bool result; nsresult rv = tcp_filter_->FilterPacket( &addr, data, len, nsISocketFilter::SF_INCOMING, &result); ASSERT_EQ(NS_OK, rv); ASSERT_EQ(expected_result, result); } void TestIncomingTcpFramed(const uint8_t* data, uint32_t len, bool expected_result) { mozilla::net::NetAddr addr; bool result; uint8_t* framed_data = new uint8_t[len + 2]; framed_data[0] = htons(len); memcpy(&framed_data[2], data, len); nsresult rv = tcp_filter_->FilterPacket( &addr, framed_data, len + 2, nsISocketFilter::SF_INCOMING, &result); ASSERT_EQ(NS_OK, rv); ASSERT_EQ(expected_result, result); delete[] framed_data; } void TestOutgoing(const uint8_t* data, uint32_t len, uint8_t to_addr, int to_port, bool expected_result) { mozilla::net::NetAddr addr; MakeNetAddr(&addr, to_addr, to_port); bool result; nsresult rv = udp_filter_->FilterPacket( &addr, data, len, nsISocketFilter::SF_OUTGOING, &result); ASSERT_EQ(NS_OK, rv); ASSERT_EQ(expected_result, result); } void TestOutgoingTcp(const uint8_t* data, uint32_t len, bool expected_result) { mozilla::net::NetAddr addr; bool result; nsresult rv = tcp_filter_->FilterPacket( &addr, data, len, nsISocketFilter::SF_OUTGOING, &result); ASSERT_EQ(NS_OK, rv); ASSERT_EQ(expected_result, result); } void TestOutgoingTcpFramed(const uint8_t* data, uint32_t len, bool expected_result) { mozilla::net::NetAddr addr; bool result; uint8_t* framed_data = new uint8_t[len + 2]; framed_data[0] = htons(len); memcpy(&framed_data[2], data, len); nsresult rv = tcp_filter_->FilterPacket( &addr, framed_data, len + 2, nsISocketFilter::SF_OUTGOING, &result); ASSERT_EQ(NS_OK, rv); ASSERT_EQ(expected_result, result); delete[] framed_data; } private: void MakeNetAddr(mozilla::net::NetAddr* net_addr, uint8_t last_digit, uint16_t port) { net_addr->inet.family = AF_INET; net_addr->inet.ip = 192 << 24 | 168 << 16 | 1 << 8 | last_digit; net_addr->inet.port = port; } nsCOMPtr udp_filter_; nsCOMPtr tcp_filter_; RefPtr ice_ctx_; }; } // end namespace TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostnameNoResolver) { if (stun_server_hostname_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort); peer_->AddStream(1); Gather(); } // Disabled because google isn't running any TCP stun servers right now TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherFakeStunServerTcpHostnameNoResolver) { if (stun_server_hostname_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort, kNrIceTransportTcp); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerIpAddress) { if (stun_server_address_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort); peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_); peer_->AddStream(1); Gather(); } TEST_F(WebRtcIceGatherTest, TestGatherStunServerIpAddressNoHost) { if (stun_server_address_.empty()) { return; } { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } NrIceCtx::Config config; config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; peer_ = MakeUnique("P1", test_utils_, true, config); peer_->AddStream(1); peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort); peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_); Gather(); ASSERT_FALSE(StreamHasMatchingCandidate(0, " host ")); } TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostname) { if (stun_server_hostname_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort); peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_); peer_->AddStream(1); Gather(); } TEST_F(WebRtcIceGatherTest, TestGatherFakeStunBogusHostname) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort); peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_); peer_->AddStream(1); Gather(); } TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddress) { if (stun_server_address_.empty()) { return; } { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } // A srflx candidate is considered redundant and discarded if its address // equals that of a host candidate. (Frequently, a srflx candidate and a host // candidate have equal addresses when the agent is not behind a NAT.) So set // ICE_POLICY_NO_HOST here to ensure that a srflx candidate is not falsely // discarded in this test. NrIceCtx::Config config; config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; peer_ = MakeUnique("P1", test_utils_, true, config); peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr")); } // Disabled because google isn't running any TCP stun servers right now TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerIpAddressTcp) { if (stun_server_address_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort, kNrIceTransportTcp); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive")); ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so")); ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 ")); } TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostname) { if (stun_server_hostname_.empty()) { return; } { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } // A srflx candidate is considered redundant and discarded if its address // equals that of a host candidate. (Frequently, a srflx candidate and a host // candidate have equal addresses when the agent is not behind a NAT.) So set // ICE_POLICY_NO_HOST here to ensure that a srflx candidate is not falsely // discarded in this test. NrIceCtx::Config config; config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; peer_ = MakeUnique("P1", test_utils_, true, config); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr")); } // Disabled because google isn't running any TCP stun servers right now TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerHostnameTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort, kNrIceTransportTcp); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive")); ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so")); ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 ")); } // Disabled because google isn't running any TCP stun servers right now TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerHostnameBothUdpTcp) { if (stun_server_hostname_.empty()) { return; } std::vector stun_servers; NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); EnsurePeer(); stun_servers.push_back(*NrIceStunServer::Create( stun_server_hostname_, kDefaultStunServerPort, kNrIceTransportUdp)); stun_servers.push_back(*NrIceStunServer::Create( stun_server_hostname_, kDefaultStunServerPort, kNrIceTransportTcp)); peer_->SetStunServers(stun_servers); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } // Disabled because google isn't running any TCP stun servers right now TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerIpAddressBothUdpTcp) { if (stun_server_address_.empty()) { return; } std::vector stun_servers; NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); EnsurePeer(); stun_servers.push_back(*NrIceStunServer::Create( stun_server_address_, kDefaultStunServerPort, kNrIceTransportUdp)); stun_servers.push_back(*NrIceStunServer::Create( stun_server_address_, kDefaultStunServerPort, kNrIceTransportTcp)); peer_->SetStunServers(stun_servers); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } TEST_F(WebRtcIceGatherTest, TestGatherDNSStunBogusHostname) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); } // Disabled because google isn't running any TCP stun servers right now TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunBogusHostnameTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort, kNrIceTransportTcp); peer_->SetDNSResolver(); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } TEST_F(WebRtcIceGatherTest, TestDefaultCandidate) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort); peer_->AddStream(1); Gather(); NrIceCandidate default_candidate; ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate))); } TEST_F(WebRtcIceGatherTest, TestGatherTurn) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); if (turn_server_.empty()) return; peer_->SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_, kNrIceTransportUdp); peer_->AddStream(1); Gather(); } TEST_F(WebRtcIceGatherTest, TestGatherTurnTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); if (turn_server_.empty()) return; peer_->SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_, kNrIceTransportTcp); peer_->AddStream(1); Gather(); } TEST_F(WebRtcIceGatherTest, TestGatherDisableComponent) { if (stun_server_hostname_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort); peer_->AddStream(1); peer_->AddStream(2); peer_->DisableComponent(1, 2); Gather(); std::vector attributes = peer_->GetAttributes(1); for (auto& attribute : attributes) { if (attribute.find("candidate:") != std::string::npos) { size_t sp1 = attribute.find(' '); ASSERT_EQ(0, attribute.compare(sp1 + 1, 1, "1", 1)); } } } TEST_F(WebRtcIceGatherTest, TestGatherVerifyNoLoopback) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->AddStream(1); Gather(); ASSERT_FALSE(StreamHasMatchingCandidate(0, "127.0.0.1")); } TEST_F(WebRtcIceGatherTest, TestGatherAllowLoopback) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; config.mAllowLoopback = true; NrIceCtx::InitializeGlobals(config); // Set up peer with loopback allowed. peer_ = MakeUnique("P1", test_utils_, true, NrIceCtx::Config()); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "127.0.0.1")); } TEST_F(WebRtcIceGatherTest, TestGatherTcpDisabledNoStun) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->AddStream(1); Gather(); ASSERT_FALSE(StreamHasMatchingCandidate(0, " TCP ")); ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); } TEST_F(WebRtcIceGatherTest, VerifyTestStunServer) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("192.0.2.133", 3333); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 ")); } TEST_F(WebRtcIceGatherTest, VerifyTestStunTcpServer) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); UseFakeStunTcpServerWithResponse("192.0.2.233", 3333); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.233 3333 typ srflx", " tcptype ")); } TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6) { if (!TestStunServer::GetInstance(AF_INET6)) { // No V6 addresses return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("beef::", 3333); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 ")); } TEST_F(WebRtcIceGatherTest, VerifyTestStunServerFQDN) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("192.0.2.133", 3333, "stun.example.com"); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 ")); } TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6FQDN) { if (!TestStunServer::GetInstance(AF_INET6)) { // No V6 addresses return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("beef::", 3333, "stun.example.com"); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 ")); } TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddr) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("0.0.0.0", 3333); peer_->AddStream(1); Gather(kDefaultTimeout * 3); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 0.0.0.0 ")); } TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddrV6) { if (!TestStunServer::GetInstance(AF_INET6)) { // No V6 addresses return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("::", 3333); peer_->AddStream(1); Gather(kDefaultTimeout * 3); ASSERT_FALSE(StreamHasMatchingCandidate(0, " :: ")); } TEST_F(WebRtcIceGatherTest, TestStunServerReturnsPort0) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("192.0.2.133", 0); peer_->AddStream(1); Gather(kDefaultTimeout * 3); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.2.133 0 ")); } TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddr) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("127.0.0.133", 3333); peer_->AddStream(1); Gather(kDefaultTimeout * 3); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 ")); } TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddrV6) { if (!TestStunServer::GetInstance(AF_INET6)) { // No V6 addresses return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("::1", 3333); peer_->AddStream(1); Gather(kDefaultTimeout * 3); ASSERT_FALSE(StreamHasMatchingCandidate(0, " ::1 ")); } TEST_F(WebRtcIceGatherTest, TestStunServerTrickle) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpServerWithResponse("192.0.2.1", 3333); peer_->AddStream(1); TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3); Gather(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1")); WaitForGather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1")); } // Test no host with our fake STUN server and apparently NATted. TEST_F(WebRtcIceGatherTest, TestFakeStunServerNatedNoHost) { { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } NrIceCtx::Config config; config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; peer_ = MakeUnique("P1", test_utils_, true, config); UseFakeStunUdpServerWithResponse("192.0.2.1", 3333); peer_->AddStream(1); Gather(0); WaitForGather(); DumpAttributes(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, "host")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx")); NrIceCandidate default_candidate; nsresult rv = peer_->GetDefaultCandidate(0, &default_candidate); if (NS_SUCCEEDED(rv)) { ASSERT_NE(NrIceCandidate::ICE_HOST, default_candidate.type); } } // Test no host with our fake STUN server and apparently non-NATted. TEST_F(WebRtcIceGatherTest, TestFakeStunServerNoNatNoHost) { { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } NrIceCtx::Config config; config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; peer_ = MakeUnique("P1", test_utils_, true, config); UseTestStunServer(); peer_->AddStream(1); Gather(0); WaitForGather(); DumpAttributes(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, "host")); ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx")); } // Test that srflx candidate is discarded in non-NATted environment if host // address obfuscation is not enabled. TEST_F(WebRtcIceGatherTest, TestSrflxCandidateDiscardedWithObfuscateHostAddressesNotEnabled) { { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } NrIceCtx::Config config; peer_ = MakeUnique("P1", test_utils_, true, config); UseTestStunServer(); peer_->AddStream(1); Gather(0, false, false); WaitForGather(); DumpAttributes(0); EXPECT_TRUE(StreamHasMatchingCandidate(0, "host")); EXPECT_FALSE(StreamHasMatchingCandidate(0, "srflx")); } // Test that srflx candidate is generated in non-NATted environment if host // address obfuscation is enabled. TEST_F(WebRtcIceGatherTest, TestSrflxCandidateGeneratedWithObfuscateHostAddressesEnabled) { { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); } NrIceCtx::Config config; peer_ = MakeUnique("P1", test_utils_, true, config); UseTestStunServer(); peer_->AddStream(1); Gather(0, false, true); WaitForGather(); DumpAttributes(0); EXPECT_TRUE(StreamHasMatchingCandidate(0, "host")); EXPECT_TRUE(StreamHasMatchingCandidate(0, "srflx")); } TEST_F(WebRtcIceGatherTest, TestStunTcpServerTrickle) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); UseFakeStunTcpServerWithResponse("192.0.3.1", 3333); TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500); peer_->AddStream(1); Gather(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); WaitForGather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); } TEST_F(WebRtcIceGatherTest, TestStunTcpAndUdpServerTrickle) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); UseFakeStunUdpTcpServersWithResponse("192.0.2.1", 3333, "192.0.3.1", 3333); TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3); TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500); peer_->AddStream(1); Gather(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP")); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); WaitForGather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP")); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); } TEST_F(WebRtcIceGatherTest, TestSetIceControlling) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->AddStream(1); peer_->SetControlling(NrIceCtx::ICE_CONTROLLING); NrIceCtx::Controlling controlling = peer_->GetControlling(); ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, controlling); // SetControlling should only allow setting this once peer_->SetControlling(NrIceCtx::ICE_CONTROLLED); controlling = peer_->GetControlling(); ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, controlling); } TEST_F(WebRtcIceGatherTest, TestSetIceControlled) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); EnsurePeer(); peer_->AddStream(1); peer_->SetControlling(NrIceCtx::ICE_CONTROLLED); NrIceCtx::Controlling controlling = peer_->GetControlling(); ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, controlling); // SetControlling should only allow setting this once peer_->SetControlling(NrIceCtx::ICE_CONTROLLING); controlling = peer_->GetControlling(); ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, controlling); } TEST_F(WebRtcIceConnectTest, TestGather) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestGatherTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestGatherAutoPrioritize) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestConnect) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectRestartIce) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); Connect(); SendReceive(p1_.get(), p2_.get()); p2_->RestartIce(); ASSERT_FALSE(p2_->gathering_complete()); // verify p1 and p2 streams are still connected after restarting ice on p2 SendReceive(p1_.get(), p2_.get()); mozilla::UniquePtr p3_; p3_ = MakeUnique("P3", test_utils_, true, NrIceCtx::Config()); InitPeer(p3_.get()); p3_->AddStream(1); ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get())); std::cout << "-------------------------------------------------" << std::endl; ConnectCallerAndCallee(p3_.get(), p2_.get(), TRICKLE_SIMULATE); SendReceive(p1_.get(), p2_.get()); // p1 and p2 are still connected SendReceive(p3_.get(), p2_.get(), true, true); // p3 and p2 not yet connected p2_->SimulateTrickle(0); p3_->SimulateTrickle(0); ASSERT_TRUE_WAIT(p3_->is_ready(0), kDefaultTimeout); ASSERT_TRUE_WAIT(p2_->is_ready(0), kDefaultTimeout); SendReceive(p1_.get(), p2_.get(), false, true); // p1 and p2 not connected SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected p3_ = nullptr; } TEST_F(WebRtcIceConnectTest, TestConnectRestartIceThenAbort) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); Connect(); SendReceive(p1_.get(), p2_.get()); p2_->RestartIce(); ASSERT_FALSE(p2_->gathering_complete()); // verify p1 and p2 streams are still connected after restarting ice on p2 SendReceive(p1_.get(), p2_.get()); mozilla::UniquePtr p3_; p3_ = MakeUnique("P3", test_utils_, true, NrIceCtx::Config()); InitPeer(p3_.get()); p3_->AddStream(1); ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get())); std::cout << "-------------------------------------------------" << std::endl; p2_->RollbackIceRestart(); p2_->Connect(p1_.get(), TRICKLE_NONE); SendReceive(p1_.get(), p2_.get()); p3_ = nullptr; } TEST_F(WebRtcIceConnectTest, TestConnectIceRestartRoleConflict) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); // Just for fun lets do this with switched rolls p1_->SetControlling(NrIceCtx::ICE_CONTROLLED); p2_->SetControlling(NrIceCtx::ICE_CONTROLLING); Connect(); SendReceive(p1_.get(), p2_.get()); // Set rolls should not switch by connecting ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling()); ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p2_->GetControlling()); p2_->RestartIce(); ASSERT_FALSE(p2_->gathering_complete()); p2_->SetControlling(NrIceCtx::ICE_CONTROLLED); ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p2_->GetControlling()) << "ICE restart should not allow role to change, unless ice-lite happens"; mozilla::UniquePtr p3_; p3_ = MakeUnique("P3", test_utils_, true, NrIceCtx::Config()); InitPeer(p3_.get()); p3_->AddStream(1); // Set control role for p3 accordingly (with role conflict) p3_->SetControlling(NrIceCtx::ICE_CONTROLLING); ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p3_->GetControlling()); ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get())); std::cout << "-------------------------------------------------" << std::endl; ConnectCallerAndCallee(p3_.get(), p2_.get()); auto p2role = p2_->GetControlling(); ASSERT_NE(p2role, p3_->GetControlling()) << "Conflict should be resolved"; ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling()) << "P1 should be unaffected by role conflict"; // And again we are not allowed to switch roles at this point any more p1_->SetControlling(NrIceCtx::ICE_CONTROLLING); ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling()); p3_->SetControlling(p2role); ASSERT_NE(p2role, p3_->GetControlling()); p3_ = nullptr; } TEST_F(WebRtcIceConnectTest, TestIceRestartWithMultipleInterfacesAndUserStartingScreenSharing) { const char* FAKE_WIFI_ADDR = "10.0.0.1"; const char* FAKE_WIFI_IF_NAME = "wlan9"; // prepare a fake wifi interface nr_local_addr wifi_addr; wifi_addr.interface.type = NR_INTERFACE_TYPE_WIFI; wifi_addr.interface.estimated_speed = 1000; int r = nr_str_port_to_transport_addr(FAKE_WIFI_ADDR, 0, IPPROTO_UDP, &(wifi_addr.addr)); ASSERT_EQ(0, r); strncpy(wifi_addr.addr.ifname, FAKE_WIFI_IF_NAME, MAXIFNAME); NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); // setup initial ICE connection between p1_ and p2_ UseNat(); AddStream(1); SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); ASSERT_TRUE(Gather(kDefaultTimeout, true)); Connect(); // verify the connection is working SendReceive(p1_.get(), p2_.get()); // simulate user accepting permissions for screen sharing p2_->SetCtxFlags(false); // and having an additional non-default interface nsTArray stunAddr = p2_->GetStunAddrs(); stunAddr.InsertElementAt(0, NrIceStunAddr(&wifi_addr)); p2_->SetStunAddrs(stunAddr); std::cout << "-------------------------------------------------" << std::endl; // now restart ICE p2_->RestartIce(); ASSERT_FALSE(p2_->gathering_complete()); // verify that we can successfully gather candidates p2_->Gather(); EXPECT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout); } TEST_F(WebRtcIceConnectTest, TestConnectTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp); Connect(); } // TCP SO tests works on localhost only with delay applied: // tc qdisc add dev lo root netem delay 10ms TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectTcpSo) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpSoCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp); Connect(); } // Disabled because this breaks with hairpinning. TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectNoHost) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); Init(false, NrIceCtx::ICE_POLICY_NO_HOST); AddStream(1); ASSERT_TRUE(Gather()); SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, kNrIceTransportTcp); Connect(); } TEST_F(WebRtcIceConnectTest, TestLoopbackOnlySortOf) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; config.mAllowLoopback = true; NrIceCtx::InitializeGlobals(config); Init(false); AddStream(1); SetCandidateFilter(IsLoopbackCandidate); ASSERT_TRUE(Gather()); SetExpectedRemoteCandidateAddr("127.0.0.1"); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP1Wins) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); p1_->SetTiebreaker(1); p2_->SetTiebreaker(0); ASSERT_TRUE(Gather()); p1_->SetControlling(NrIceCtx::ICE_CONTROLLING); p2_->SetControlling(NrIceCtx::ICE_CONTROLLING); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP2Wins) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); p1_->SetTiebreaker(0); p2_->SetTiebreaker(1); ASSERT_TRUE(Gather()); p1_->SetControlling(NrIceCtx::ICE_CONTROLLING); p2_->SetControlling(NrIceCtx::ICE_CONTROLLING); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectIceLiteOfferer) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); p1_->SimulateIceLite(); Connect(); } TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP1Wins) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); p1_->SetTiebreaker(1); p2_->SetTiebreaker(0); ASSERT_TRUE(Gather()); p1_->SetControlling(NrIceCtx::ICE_CONTROLLING); p2_->SetControlling(NrIceCtx::ICE_CONTROLLING); ConnectTrickle(); SimulateTrickle(0); WaitForConnected(1000); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP2Wins) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); p1_->SetTiebreaker(0); p2_->SetTiebreaker(1); ASSERT_TRUE(Gather()); p1_->SetControlling(NrIceCtx::ICE_CONTROLLING); p2_->SetControlling(NrIceCtx::ICE_CONTROLLING); ConnectTrickle(); SimulateTrickle(0); WaitForConnected(1000); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestTrickleIceLiteOfferer) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); p1_->SimulateIceLite(); ConnectTrickle(); SimulateTrickle(0); WaitForConnected(1000); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestGatherFullCone) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestGatherFullConeAutoPrioritize) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); Init(); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestConnectFullCone) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); AddStream(1); SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectNoNatNoHost) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); Init(false, NrIceCtx::ICE_POLICY_NO_HOST); UseTestStunServer(); // Because we are connecting from our host candidate to the // other side's apparent srflx (which is also their host) // we see a host/srflx pair. SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectFullConeNoHost) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); Init(false, NrIceCtx::ICE_POLICY_NO_HOST); UseTestStunServer(); SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestGatherAddressRestrictedCone) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); SetFilteringType(TestNat::ADDRESS_DEPENDENT); SetMappingType(TestNat::ENDPOINT_INDEPENDENT); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestConnectAddressRestrictedCone) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); SetFilteringType(TestNat::ADDRESS_DEPENDENT); SetMappingType(TestNat::ENDPOINT_INDEPENDENT); AddStream(1); SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestGatherPortRestrictedCone) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); SetFilteringType(TestNat::PORT_DEPENDENT); SetMappingType(TestNat::ENDPOINT_INDEPENDENT); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestConnectPortRestrictedCone) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); SetFilteringType(TestNat::PORT_DEPENDENT); SetMappingType(TestNat::ENDPOINT_INDEPENDENT); AddStream(1); SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestGatherSymmetricNat) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); SetFilteringType(TestNat::PORT_DEPENDENT); SetMappingType(TestNat::PORT_DEPENDENT); AddStream(1); ASSERT_TRUE(Gather()); } TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); SetFilteringType(TestNat::PORT_DEPENDENT); SetMappingType(TestNat::PORT_DEPENDENT); p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED); p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNatAndNoNat) { { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); } NrIceCtx::Config config; p1_ = MakeUnique("P1", test_utils_, true, config); p1_->UseNat(); p1_->SetFilteringType(TestNat::PORT_DEPENDENT); p1_->SetMappingType(TestNat::PORT_DEPENDENT); p2_ = MakeUnique("P2", test_utils_, false, config); initted_ = true; AddStream(1); p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_PEER_REFLEXIVE, NrIceCandidate::Type::ICE_HOST); p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_PEER_REFLEXIVE); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestGatherNatBlocksUDP) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); BlockUdp(); std::vector turn_servers; std::vector password_vec(turn_password_.begin(), turn_password_.end()); turn_servers.push_back( *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_, password_vec, kNrIceTransportTcp)); turn_servers.push_back( *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_, password_vec, kNrIceTransportUdp)); SetTurnServers(turn_servers); AddStream(1); // We have to wait for the UDP-based stuff to time out. ASSERT_TRUE(Gather(kDefaultTimeout * 3)); } TEST_F(WebRtcIceConnectTest, TestConnectNatBlocksUDP) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); UseNat(); BlockUdp(); std::vector turn_servers; std::vector password_vec(turn_password_.begin(), turn_password_.end()); turn_servers.push_back( *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_, password_vec, kNrIceTransportTcp)); turn_servers.push_back( *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_, password_vec, kNrIceTransportUdp)); SetTurnServers(turn_servers); p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp); p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp); AddStream(1); ASSERT_TRUE(Gather(kDefaultTimeout * 3)); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectTwoComponents) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(2); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectTwoComponentsDisableSecond) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(2); ASSERT_TRUE(Gather()); p1_->DisableComponent(0, 2); p2_->DisableComponent(0, 2); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectP2(); PR_Sleep(1000); ConnectP1(); WaitForConnectedStreams(); } TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1Trickle) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectP2(); PR_Sleep(1000); ConnectP1(TRICKLE_SIMULATE); SimulateTrickleP1(0); WaitForConnectedStreams(); } TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(2); ASSERT_TRUE(Gather()); ConnectP2(); PR_Sleep(1000); ConnectP1(TRICKLE_SIMULATE); SimulateTrickleP1(0); std::cerr << "Sleeping between trickle streams" << std::endl; PR_Sleep(1000); // Give this some time to settle but not complete // all of ICE. SimulateTrickleP1(1); WaitForConnectedStreams(2); } TEST_F(WebRtcIceConnectTest, TestConnectAutoPrioritize) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectTrickleOneStreamOneComponent) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); SimulateTrickle(0); WaitForConnected(1000); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestConnectTrickleTwoStreamsOneComponent) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); SimulateTrickle(0); SimulateTrickle(1); WaitForConnected(1000); AssertCheckingReached(); } void RealisticTrickleDelay( std::vector& candidates) { for (size_t i = 0; i < candidates.size(); ++i) { SchedulableTrickleCandidate* cand = candidates[i]; if (cand->IsHost()) { cand->Schedule(i * 10); } else if (cand->IsReflexive()) { cand->Schedule(i * 10 + 100); } else if (cand->IsRelay()) { cand->Schedule(i * 10 + 200); } } } void DelayRelayCandidates(std::vector& candidates, unsigned int ms) { for (auto& candidate : candidates) { if (candidate->IsRelay()) { candidate->Schedule(ms); } else { candidate->Schedule(0); } } } void AddNonPairableCandidates( std::vector& candidates, IceTestPeer* peer, size_t stream, int net_type, MtransportTestUtils* test_utils_) { for (int i = 1; i < 5; i++) { if (net_type == i) continue; switch (i) { case 1: candidates.push_back(new SchedulableTrickleCandidate( peer, stream, "candidate:0 1 UDP 2113601790 10.0.0.1 12345 typ host", "", test_utils_)); break; case 2: candidates.push_back(new SchedulableTrickleCandidate( peer, stream, "candidate:0 1 UDP 2113601791 172.16.1.1 12345 typ host", "", test_utils_)); break; case 3: candidates.push_back(new SchedulableTrickleCandidate( peer, stream, "candidate:0 1 UDP 2113601792 192.168.0.1 12345 typ host", "", test_utils_)); break; case 4: candidates.push_back(new SchedulableTrickleCandidate( peer, stream, "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host", "", test_utils_)); break; default: NR_UNIMPLEMENTED; } } for (auto i = candidates.rbegin(); i != candidates.rend(); ++i) { std::cerr << "Scheduling candidate: " << (*i)->Candidate().c_str() << std::endl; (*i)->Schedule(0); } } void DropTrickleCandidates( std::vector& candidates) {} TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamDuringICE) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); AddStream(1); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamAfterICE) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); WaitForConnected(1000); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, RemoveStream) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); RemoveStream(0); ASSERT_TRUE(Gather()); ConnectTrickle(); } TEST_F(WebRtcIceConnectTest, P1NoTrickle) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); DropTrickleCandidates(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); WaitForConnected(1000); } TEST_F(WebRtcIceConnectTest, P2NoTrickle) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); DropTrickleCandidates(p2_->ControlTrickle(0)); WaitForConnected(1000); } TEST_F(WebRtcIceConnectTest, RemoveAndAddStream) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); RemoveStream(0); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(2)); RealisticTrickleDelay(p2_->ControlTrickle(2)); WaitForConnected(1000); } TEST_F(WebRtcIceConnectTest, RemoveStreamBeforeGather) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); ASSERT_TRUE(Gather(0)); RemoveStream(0); WaitForGather(); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); } TEST_F(WebRtcIceConnectTest, RemoveStreamDuringGather) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); RemoveStream(0); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); } TEST_F(WebRtcIceConnectTest, RemoveStreamDuringConnect) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); RemoveStream(0); WaitForConnected(1000); } TEST_F(WebRtcIceConnectTest, TestConnectRealTrickleOneStreamOneComponent) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); AddStream(1); ASSERT_TRUE(Gather(0)); ConnectTrickle(TRICKLE_REAL); WaitForConnected(); WaitForGather(); // ICE can complete before we finish gathering. AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestSendReceive) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); Connect(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestSendReceiveTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp); Connect(); SendReceive(); } // TCP SO tests works on localhost only with delay applied: // tc qdisc add dev lo root netem delay 10ms TEST_F(WebRtcIceConnectTest, DISABLED_TestSendReceiveTcpSo) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpSoCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp); Connect(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestConsent) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); PR_Sleep(1500); AssertConsentRefresh(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestConsentTcp) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = true; NrIceCtx::InitializeGlobals(config); Init(); AddStream(1); SetCandidateFilter(IsTcpCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp); SetupAndCheckConsent(); PR_Sleep(1500); AssertConsentRefresh(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestConsentIntermittent) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); p1_->SetBlockStun(true); p2_->SetBlockStun(true); WaitForDisconnected(); AssertConsentRefresh(CONSENT_STALE); SendReceive(); p1_->SetBlockStun(false); p2_->SetBlockStun(false); WaitForConnected(); AssertConsentRefresh(); SendReceive(); p1_->SetBlockStun(true); p2_->SetBlockStun(true); WaitForDisconnected(); AssertConsentRefresh(CONSENT_STALE); SendReceive(); p1_->SetBlockStun(false); p2_->SetBlockStun(false); WaitForConnected(); AssertConsentRefresh(); } TEST_F(WebRtcIceConnectTest, TestConsentTimeout) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); p1_->SetBlockStun(true); p2_->SetBlockStun(true); WaitForDisconnected(); AssertConsentRefresh(CONSENT_STALE); SendReceive(); WaitForFailed(); AssertConsentRefresh(CONSENT_EXPIRED); SendFailure(); } TEST_F(WebRtcIceConnectTest, TestConsentDelayed) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); /* Note: We don't have a list of STUN transaction IDs of the previously timed out consent requests. Thus responses after sending the next consent request are ignored. */ p1_->SetStunResponseDelay(200); p2_->SetStunResponseDelay(200); PR_Sleep(1000); AssertConsentRefresh(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineAndRecovery) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); p1_->ChangeNetworkStateToOffline(); ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout); // Next round of consent check should switch it back to online ASSERT_TRUE_WAIT(p1_->ice_connected(), kDefaultTimeout); } TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineTwice) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); p2_->ChangeNetworkStateToOffline(); ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout); p2_->ChangeNetworkStateToOffline(); ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout); } TEST_F(WebRtcIceConnectTest, TestNetworkOnlineDoesntChangeState) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); SetupAndCheckConsent(); p2_->ChangeNetworkStateToOnline(); ASSERT_TRUE(p2_->ice_connected()); PR_Sleep(1500); p2_->ChangeNetworkStateToOnline(); ASSERT_TRUE(p2_->ice_connected()); } TEST_F(WebRtcIceConnectTest, TestNetworkOnlineTriggersConsent) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); // Let's emulate audio + video w/o rtcp-mux AddStream(2); AddStream(2); SetupAndCheckConsent(); p1_->ChangeNetworkStateToOffline(); p1_->SetBlockStun(true); ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout); PR_Sleep(1500); ASSERT_TRUE(p1_->ice_connected() == 0); p1_->SetBlockStun(false); p1_->ChangeNetworkStateToOnline(); ASSERT_TRUE_WAIT(p1_->ice_connected(), 500); } TEST_F(WebRtcIceConnectTest, TestConnectTurn) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnWithDelay) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); SetCandidateFilter(SabotageHostCandidateAndDropReflexive); AddStream(1); p1_->Gather(); PR_Sleep(500); p2_->Gather(); ConnectTrickle(TRICKLE_REAL); WaitForGather(); WaitForConnectedStreams(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelay) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); WaitForConnected(); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelayOneSided) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(0)); p2_->SimulateTrickle(0); WaitForConnected(); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnWithLargeTrickleDelay) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); SetCandidateFilter(SabotageHostCandidateAndDropReflexive); AddStream(1); ASSERT_TRUE(Gather()); ConnectTrickle(); // Trickle host candidates immediately, but delay relay candidates DelayRelayCandidates(p1_->ControlTrickle(0), 3700); DelayRelayCandidates(p2_->ControlTrickle(0), 3700); WaitForConnected(); AssertCheckingReached(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnTcp) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_, kNrIceTransportTcp); AddStream(1); ASSERT_TRUE(Gather()); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnOnly) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsRelayCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED); Connect(); } TEST_F(WebRtcIceConnectTest, TestConnectTurnTcpOnly) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_, kNrIceTransportTcp); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsRelayCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp); Connect(); } TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnOnly) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsRelayCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED); Connect(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnTcpOnly) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_, turn_password_, kNrIceTransportTcp); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsRelayCandidate); SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp); Connect(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnBothOnly) { if (turn_server_.empty()) return; NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); std::vector turn_servers; std::vector password_vec(turn_password_.begin(), turn_password_.end()); turn_servers.push_back( *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_, password_vec, kNrIceTransportTcp)); turn_servers.push_back( *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_, password_vec, kNrIceTransportUdp)); SetTurnServers(turn_servers); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsRelayCandidate); // UDP is preferred. SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportUdp); Connect(); SendReceive(); } TEST_F(WebRtcIceConnectTest, TestConnectShutdownOneSide) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); ConnectThenDelete(); } TEST_F(WebRtcIceConnectTest, TestPollCandPairsBeforeConnect) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); std::vector pairs; nsresult res = p1_->GetCandidatePairs(0, &pairs); // There should be no candidate pairs prior to calling Connect() ASSERT_EQ(NS_OK, res); ASSERT_EQ(0U, pairs.size()); res = p2_->GetCandidatePairs(0, &pairs); ASSERT_EQ(NS_OK, res); ASSERT_EQ(0U, pairs.size()); } TEST_F(WebRtcIceConnectTest, TestPollCandPairsAfterConnect) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); Connect(); std::vector pairs; nsresult r = p1_->GetCandidatePairs(0, &pairs); ASSERT_EQ(NS_OK, r); // How detailed of a check do we want to do here? If the turn server is // functioning, we'll get at least two pairs, but this is probably not // something we should assume. ASSERT_NE(0U, pairs.size()); ASSERT_TRUE(p1_->CandidatePairsPriorityDescending(pairs)); ASSERT_TRUE(ContainsSucceededPair(pairs)); pairs.clear(); r = p2_->GetCandidatePairs(0, &pairs); ASSERT_EQ(NS_OK, r); ASSERT_NE(0U, pairs.size()); ASSERT_TRUE(p2_->CandidatePairsPriorityDescending(pairs)); ASSERT_TRUE(ContainsSucceededPair(pairs)); } // TODO Bug 1259842 - disabled until we find a better way to handle two // candidates from different RFC1918 ranges TEST_F(WebRtcIceConnectTest, DISABLED_TestHostCandPairingFilter) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); Init(false); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsIpv4Candidate); int host_net = p1_->GetCandidatesPrivateIpv4Range(0); if (host_net <= 0) { // TODO bug 1226838: make this work with multiple private IPs FAIL() << "This test needs exactly one private IPv4 host candidate to work" << std::endl; } ConnectTrickle(); AddNonPairableCandidates(p1_->ControlTrickle(0), p1_.get(), 0, host_net, test_utils_); AddNonPairableCandidates(p2_->ControlTrickle(0), p2_.get(), 0, host_net, test_utils_); std::vector pairs; p1_->GetCandidatePairs(0, &pairs); for (auto p : pairs) { std::cerr << "Verifying pair:" << std::endl; p1_->DumpCandidatePair(p); nr_transport_addr addr; nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr); ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net); nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr); ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net); } } // TODO Bug 1226838 - See Comment 2 - this test can't work as written TEST_F(WebRtcIceConnectTest, DISABLED_TestSrflxCandPairingFilter) { if (stun_server_address_.empty()) { return; } NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); Init(false); AddStream(1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsSrflxCandidate); if (p1_->GetCandidatesPrivateIpv4Range(0) <= 0) { // TODO bug 1226838: make this work with public IP addresses std::cerr << "Don't run this test at IETF meetings!" << std::endl; FAIL() << "This test needs one private IPv4 host candidate to work" << std::endl; } ConnectTrickle(); SimulateTrickleP1(0); SimulateTrickleP2(0); std::vector pairs; p1_->GetCandidatePairs(0, &pairs); for (auto p : pairs) { std::cerr << "Verifying P1 pair:" << std::endl; p1_->DumpCandidatePair(p); nr_transport_addr addr; nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr); ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0); nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr); ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0); } p2_->GetCandidatePairs(0, &pairs); for (auto p : pairs) { std::cerr << "Verifying P2 pair:" << std::endl; p2_->DumpCandidatePair(p); nr_transport_addr addr; nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr); ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0); nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr); ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0); } } TEST_F(WebRtcIceConnectTest, TestPollCandPairsDuringConnect) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); p2_->Connect(p1_.get(), TRICKLE_NONE, false); p1_->Connect(p2_.get(), TRICKLE_NONE, false); std::vector pairs1; std::vector pairs2; p1_->StartChecks(); p1_->UpdateAndValidateCandidatePairs(0, &pairs1); p2_->UpdateAndValidateCandidatePairs(0, &pairs2); p2_->StartChecks(); p1_->UpdateAndValidateCandidatePairs(0, &pairs1); p2_->UpdateAndValidateCandidatePairs(0, &pairs2); WaitForConnectedStreams(); p1_->UpdateAndValidateCandidatePairs(0, &pairs1); p2_->UpdateAndValidateCandidatePairs(0, &pairs2); ASSERT_TRUE(ContainsSucceededPair(pairs1)); ASSERT_TRUE(ContainsSucceededPair(pairs2)); } TEST_F(WebRtcIceConnectTest, TestRLogConnector) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); ASSERT_TRUE(Gather()); p2_->Connect(p1_.get(), TRICKLE_NONE, false); p1_->Connect(p2_.get(), TRICKLE_NONE, false); std::vector pairs1; std::vector pairs2; p1_->StartChecks(); p1_->UpdateAndValidateCandidatePairs(0, &pairs1); p2_->UpdateAndValidateCandidatePairs(0, &pairs2); p2_->StartChecks(); p1_->UpdateAndValidateCandidatePairs(0, &pairs1); p2_->UpdateAndValidateCandidatePairs(0, &pairs2); WaitForConnectedStreams(); p1_->UpdateAndValidateCandidatePairs(0, &pairs1); p2_->UpdateAndValidateCandidatePairs(0, &pairs2); ASSERT_TRUE(ContainsSucceededPair(pairs1)); ASSERT_TRUE(ContainsSucceededPair(pairs2)); for (auto& p : pairs1) { std::deque logs; std::string substring("CAND-PAIR("); substring += p.codeword; RLogConnector::GetInstance()->Filter(substring, 0, &logs); ASSERT_NE(0U, logs.size()); } for (auto& p : pairs2) { std::deque logs; std::string substring("CAND-PAIR("); substring += p.codeword; RLogConnector::GetInstance()->Filter(substring, 0, &logs); ASSERT_NE(0U, logs.size()); } } // Verify that a bogus candidate doesn't cause crashes on the // main thread. See bug 856433. TEST_F(WebRtcIceConnectTest, TestBogusCandidate) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); Gather(); ConnectTrickle(); p1_->ParseCandidate(0, kBogusIceCandidate, ""); std::vector pairs; nsresult res = p1_->GetCandidatePairs(0, &pairs); ASSERT_EQ(NS_OK, res); ASSERT_EQ(0U, pairs.size()); } TEST_F(WebRtcIceConnectTest, TestNonMDNSCandidate) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); Gather(); ConnectTrickle(); p1_->ParseCandidate(0, kUnreachableHostIceCandidate, ""); std::vector pairs; nsresult res = p1_->GetCandidatePairs(0, &pairs); ASSERT_EQ(NS_OK, res); ASSERT_EQ(1U, pairs.size()); ASSERT_EQ(pairs[0].remote.mdns_addr, ""); } TEST_F(WebRtcIceConnectTest, TestMDNSCandidate) { NrIceCtx::GlobalConfig config; config.mTcpEnabled = false; NrIceCtx::InitializeGlobals(config); AddStream(1); Gather(); ConnectTrickle(); p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "host.local"); std::vector pairs; nsresult res = p1_->GetCandidatePairs(0, &pairs); ASSERT_EQ(NS_OK, res); ASSERT_EQ(1U, pairs.size()); ASSERT_EQ(pairs[0].remote.mdns_addr, "host.local"); } TEST_F(WebRtcIcePrioritizerTest, TestPrioritizer) { SetPriorizer(::mozilla::CreateInterfacePrioritizer()); AddInterface("0", NR_INTERFACE_TYPE_VPN, 100); // unknown vpn AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED, 100); // wired vpn AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI, 100); // wifi vpn AddInterface("3", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_MOBILE, 100); // wifi vpn AddInterface("4", NR_INTERFACE_TYPE_WIRED, 1000); // wired, high speed AddInterface("5", NR_INTERFACE_TYPE_WIRED, 10); // wired, low speed AddInterface("6", NR_INTERFACE_TYPE_WIFI, 10); // wifi, low speed AddInterface("7", NR_INTERFACE_TYPE_WIFI, 1000); // wifi, high speed AddInterface("8", NR_INTERFACE_TYPE_MOBILE, 10); // mobile, low speed AddInterface("9", NR_INTERFACE_TYPE_MOBILE, 1000); // mobile, high speed AddInterface("10", NR_INTERFACE_TYPE_UNKNOWN, 10); // unknown, low speed AddInterface("11", NR_INTERFACE_TYPE_UNKNOWN, 1000); // unknown, high speed // expected preference "4" > "5" > "1" > "7" > "6" > "2" > "9" > "8" > "3" > // "11" > "10" > "0" HasLowerPreference("0", "10"); HasLowerPreference("10", "11"); HasLowerPreference("11", "3"); HasLowerPreference("3", "8"); HasLowerPreference("8", "9"); HasLowerPreference("9", "2"); HasLowerPreference("2", "6"); HasLowerPreference("6", "7"); HasLowerPreference("7", "1"); HasLowerPreference("1", "5"); HasLowerPreference("5", "4"); } TEST_F(WebRtcIcePacketFilterTest, TestSendNonStunPacket) { const unsigned char data[] = "12345abcde"; TestOutgoing(data, sizeof(data), 123, 45, false); TestOutgoingTcp(data, sizeof(data), false); } TEST_F(WebRtcIcePacketFilterTest, TestRecvNonStunPacket) { const unsigned char data[] = "12345abcde"; TestIncoming(data, sizeof(data), 123, 45, false); TestIncomingTcp(data, sizeof(data), true); } TEST_F(WebRtcIcePacketFilterTest, TestSendStunPacket) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); TestOutgoingTcpFramed(msg->buffer, msg->length, true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingId) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.id.octet[0] = 1; msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); msg->header.id.octet[0] = 0; msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncoming(msg->buffer, msg->length, 123, 45, true); TestIncomingTcp(msg->buffer, msg->length, true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvStunBindingRequestWithoutAPendingId) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.id.octet[0] = 1; msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncoming(msg->buffer, msg->length, 123, 45, true); TestIncomingTcp(msg->buffer, msg->length, true); msg->header.id.octet[0] = 1; msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingIdTcpFramed) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.id.octet[0] = 1; msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoingTcpFramed(msg->buffer, msg->length, true); msg->header.id.octet[0] = 0; msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncomingTcpFramed(msg->buffer, msg->length, true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingAddress) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); // nothing to test here for the TCP filter msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncoming(msg->buffer, msg->length, 123, 46, false); TestIncoming(msg->buffer, msg->length, 124, 45, false); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithPendingIdAndAddress) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncoming(msg->buffer, msg->length, 123, 45, true); TestIncomingTcp(msg->buffer, msg->length, true); // Test whitelist by filtering non-stun packets. const unsigned char data[] = "12345abcde"; // 123:45 is white-listed. TestOutgoing(data, sizeof(data), 123, 45, true); TestOutgoingTcp(data, sizeof(data), true); TestIncoming(data, sizeof(data), 123, 45, true); TestIncomingTcp(data, sizeof(data), true); // Indications pass as well. msg->header.type = NR_STUN_MSG_BINDING_INDICATION; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); TestIncoming(msg->buffer, msg->length, 123, 45, true); TestIncomingTcp(msg->buffer, msg->length, true); // Packets from and to other address are still disallowed. // Note: this doesn't apply for TCP connections TestOutgoing(data, sizeof(data), 123, 46, false); TestIncoming(data, sizeof(data), 123, 46, false); TestOutgoing(data, sizeof(data), 124, 45, false); TestIncoming(data, sizeof(data), 124, 45, false); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithPendingIdTcpFramed) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoingTcpFramed(msg->buffer, msg->length, true); msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncomingTcpFramed(msg->buffer, msg->length, true); // Test whitelist by filtering non-stun packets. const unsigned char data[] = "12345abcde"; TestOutgoingTcpFramed(data, sizeof(data), true); TestIncomingTcpFramed(data, sizeof(data), true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestSendNonRequestStunPacket) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, false); TestOutgoingTcp(msg->buffer, msg->length, false); // Send a packet so we allow the incoming request. msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); // This packet makes us able to send a response. msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestIncoming(msg->buffer, msg->length, 123, 45, true); TestIncomingTcp(msg->buffer, msg->length, true); msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST_F(WebRtcIcePacketFilterTest, TestRecvDataPacketWithAPendingAddress) { nr_stun_message* msg; ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg)); msg->header.type = NR_STUN_MSG_BINDING_REQUEST; ASSERT_EQ(0, nr_stun_encode_message(msg)); TestOutgoing(msg->buffer, msg->length, 123, 45, true); TestOutgoingTcp(msg->buffer, msg->length, true); const unsigned char data[] = "12345abcde"; TestIncoming(data, sizeof(data), 123, 45, true); TestIncomingTcp(data, sizeof(data), true); ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } TEST(WebRtcIceInternalsTest, TestAddBogusAttribute) { nr_stun_message* req; ASSERT_EQ(0, nr_stun_message_create(&req)); Data* data; ASSERT_EQ(0, r_data_alloc(&data, 3000)); memset(data->data, 'A', data->len); ASSERT_TRUE(nr_stun_message_add_message_integrity_attribute(req, data)); ASSERT_EQ(0, r_data_destroy(&data)); ASSERT_EQ(0, nr_stun_message_destroy(&req)); }