/* -*- 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 <algorithm>
#include <deque>
#include <iostream>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include "sigslot.h"
#include "logging.h"
#include "ssl.h"
#include "mozilla/DataMutex.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: It would be nice to have a test STUN/TURN server that can run with
// gtest.
MOZ_RUNINIT
const std::string kDefaultStunServerHostname((
char *)
"" );
MOZ_RUNINIT
const std::string kBogusStunServerHostname(
(
char *)
"stun-server-nonexistent.invalid" );
const uint16_t kDefaultStunServerPort = 19305;
MOZ_RUNINIT
const std::string kBogusIceCandidate(
(
char *)
"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ" );
MOZ_RUNINIT
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 <
struct sockaddr_in*>(res->ai_addr)->sin_addr,
str_addr,
sizeof (str_addr));
break ;
case AF_INET6:
inet_ntop(
AF_INET6,
&
reinterpret_cast <
struct sockaddr_in6*>(res->ai_addr)->sin6_add
r,
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() = default ;
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());
}
test_utils_->SyncDispatchToSTS(WrapRunnable(this , &StunTest::SetUp_s));
}
void SetUp_s() {
// Make sure NrIceCtx is in a testable state.
NrIceCtx::internal_DeinitializeGlobal();
RLogConnector::CreateInstance();
TestStunServer::GetInstance(AF_INET);
TestStunServer::GetInstance(AF_INET6);
TestStunTcpServer::GetInstance(AF_INET);
TestStunTcpServer::GetInstance(AF_INET6);
}
void TearDown() override {
test_utils_->SyncDispatchToSTS(WrapRunnable(this , &StunTest::TearDown_s));
MtransportTest::TearDown();
}
void TearDown_s() {
NrIceCtx::internal_DeinitializeGlobal();
TestStunServer::ShutdownInstance();
TestStunTcpServer::ShutdownInstance();
}
};
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<std::string> split(const std::string& s, char delim) {
std::vector<std::string> 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<std::string> 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<std::string> 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<NrIceCandidatePair>& 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) {
std::cerr << "Scheduling " << Candidate() << " in " << ms << "ms"
<< std::endl;
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 <SchedulableTrickleCandidate*>(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),
stream_counter_(0),
shutting_down_(false ),
mConnectionStates("IceTestPeer::mConnectionStates" ),
mGatheringStates("IceTestPeer::mGatheringStates" ),
ready_ct_(0),
ice_reached_checking_(false ),
received_(0),
sent_(0),
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_->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<NrIceMediaStream> 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);
stream->SignalGatheringStateChange.connect(
this , &IceTestPeer::GatheringStateChange);
{
auto lock = mConnectionStates.Lock();
lock.ref()[id] = NrIceCtx::ICE_CTX_INIT;
}
{
auto lock = mGatheringStates.Lock();
lock.ref()[id] = NrIceMediaStream::ICE_STREAM_GATHER_INIT;
}
}
void AddStream(int components) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::AddStream_s, components));
}
void RemoveStream_s(size_t index) {
const std::string id = MakeTransportId(index);
ice_ctx_->DestroyStream(id);
{
auto lock = mConnectionStates.Lock();
lock->erase(id);
}
{
auto lock = mGatheringStates.Lock();
lock->erase(id);
}
}
void RemoveStream(size_t index) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::RemoveStream_s, index));
}
RefPtr<NrIceMediaStream> 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<NrIceStunServer> stun_servers;
UniquePtr<NrIceStunServer> server(
NrIceStunServer::Create(addr, port, transport));
stun_servers.push_back(*server);
SetStunServers(stun_servers);
}
void SetStunServers(const std::vector<NrIceStunServer>& 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<unsigned char > 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<unsigned char > password,
const char * transport) {
std::vector<NrIceTurnServer> turn_servers;
UniquePtr<NrIceTurnServer> 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<NrIceTurnServer> 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<NrIceStunAddr> GetStunAddrs() { return ice_ctx_->GetStunAddrs(); }
void SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs) {
ice_ctx_->SetStunAddrs(addrs);
}
void UseNat() {
test_utils_->SyncDispatchToSTS(WrapRunnable(this , &IceTestPeer::UseNat_s));
}
void UseNat_s() { nat_->enabled_ = true ; }
void SetTimerDivider(int div) { ice_ctx_->internal_SetTimerAccelarator(div); }
void SetStunResponseDelay(uint32_t delay) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::SetStunResponseDelay_s, delay));
}
void SetStunResponseDelay_s(uint32_t delay) {
nat_->delay_stun_resp_ms_ = delay;
}
void SetFilteringType(TestNat::NatBehavior type) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::SetFilteringType_s, type));
}
void SetFilteringType_s(TestNat::NatBehavior type) {
MOZ_ASSERT(!nat_->has_port_mappings());
nat_->filtering_type_ = type;
}
void SetMappingType(TestNat::NatBehavior type) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::SetMappingType_s, type));
}
void SetMappingType_s(TestNat::NatBehavior type) {
MOZ_ASSERT(!nat_->has_port_mappings());
nat_->mapping_type_ = type;
}
void SetBlockUdp(bool block) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::SetBlockUdp_s, block));
}
void SetBlockUdp_s(bool block) {
MOZ_ASSERT(!nat_->has_port_mappings());
nat_->block_udp_ = block;
}
void SetBlockStun(bool block) {
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &IceTestPeer::SetBlockStun_s, block));
}
void SetBlockStun_s(bool block) { nat_->block_stun_ = block; }
// Get various pieces of state
std::vector<std::string> GetGlobalAttributes() {
std::vector<std::string> attrs(ice_ctx_->GetGlobalAttributes());
if (simulate_ice_lite_) {
attrs.push_back("ice-lite" );
}
return attrs;
}
std::vector<std::string> GetAttributes(size_t stream) {
std::vector<std::string> 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<std::string> GetAttributes_s(size_t index) {
std::vector<std::string> attributes;
auto stream = GetStream_s(index);
if (!stream) {
EXPECT_TRUE(false ) << "No such stream " << index;
return attributes;
}
std::vector<std::string> 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<std::string> 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<std::string> 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() {
auto lock = mGatheringStates.Lock();
for (const auto & [id, state] : lock.ref()) {
Unused << id;
if (state != NrIceMediaStream::ICE_STREAM_GATHER_COMPLETE) {
return false ;
}
}
return true ;
}
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() {
auto lock = mConnectionStates.Lock();
for (const auto & [id, state] : lock.ref()) {
if (state != NrIceCtx::ICE_CTX_CONNECTED) {
return false ;
}
}
return true ;
}
bool ice_failed() {
auto lock = mConnectionStates.Lock();
for (const auto & [id, state] : lock.ref()) {
if (state == NrIceCtx::ICE_CTX_FAILED) {
return true ;
}
}
return false ;
}
bool ice_checking() {
if (ice_failed() || ice_connected()) {
return false ;
}
auto lock = mConnectionStates.Lock();
for (const auto & [id, state] : lock.ref()) {
if (state == NrIceCtx::ICE_CTX_CHECKING) {
return true ;
}
}
return false ;
}
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);
{
auto lock = mConnectionStates.Lock();
lock.ref()[stream->GetId()] = NrIceCtx::ICE_CTX_INIT;
}
{
auto lock = mGatheringStates.Lock();
lock.ref()[stream->GetId()] = NrIceMediaStream::ICE_STREAM_GATHER_INIT;
}
}
// take care of some local bookkeeping
ready_ct_ = 0;
// We do not unset ice_reached_checking_ here, since we do not expect
// ICE to return to checking in an ICE restart, because the ICE stack
// continues using the old streams (which are probably connected) until the
// new ones are connected.
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;
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<std::string> 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<SchedulableTrickleCandidate*>& 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<SchedulableTrickleCandidate*>& ControlTrickle(size_t stream) {
std::cerr << "Doing controlled trickle for stream " << stream << std::endl;
std::vector<std::string> 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
std::cerr << "Trickle candidate has no stream: " << index << std::endl;
return NS_OK;
}
std::cerr << "Trickle candidate for " << index << " (" << stream->GetId()
<< "):" << candidate << std::endl;
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<NrIceCandidate> local;
UniquePtr<NrIceCandidate> 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(const std::string& aTransportId,
NrIceMediaStream::GatheringState state) {
if (shutting_down_) {
return ;
}
{
auto lock = mGatheringStates.Lock();
lock.ref()[aTransportId] = state;
}
if (!gathering_complete()) {
return ;
}
std::cerr << name_ << " Gathering complete" << std::endl;
std::cerr << name_ << " ATTRIBUTES:" << std::endl;
for (const auto & stream : ice_ctx_->GetStreams()) {
std::cerr << "Stream " << stream->GetId() << std::endl;
std::vector<std::string> 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->GetId()
<< " initialized: " << candidate << std::endl;
candidates_[stream->GetId()].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->GetId();
}
}
nsresult GetCandidatePairs_s(size_t stream_index,
std::vector<NrIceCandidatePair>* 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<NrIceCandidatePair>* 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<NrIceCandidatePair> 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<NrIceCandidatePair>& pairs) {
// Verify that priority is descending
uint64_t priority = std::numeric_limits<uint64_t>::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<NrIceCandidatePair>* new_pairs) {
std::vector<NrIceCandidatePair> 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<NrIceCandidatePair> added_pairs;
std::vector<NrIceCandidatePair> 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(NrIceMediaStream* stream,
NrIceCtx::ConnectionState state) {
{
auto lock = mConnectionStates.Lock();
lock.ref()[stream->GetId()] = state;
}
if (ice_checking()) {
ice_reached_checking_ = true ;
}
switch (state) {
case NrIceCtx::ICE_CTX_INIT:
break ;
case NrIceCtx::ICE_CTX_CHECKING:
std::cerr << name_ << " ICE reached checking (" << stream->GetId()
<< ")" << std::endl;
MOZ_ASSERT(ice_reached_checking_);
break ;
case NrIceCtx::ICE_CTX_CONNECTED:
std::cerr << name_ << " ICE reached connected (" << stream->GetId()
<< ")" << std::endl;
MOZ_ASSERT(ice_reached_checking_);
break ;
case NrIceCtx::ICE_CTX_COMPLETED:
std::cerr << name_ << " ICE reached completed (" << stream->GetId()
<< ")" << std::endl;
MOZ_ASSERT(ice_reached_checking_);
break ;
case NrIceCtx::ICE_CTX_FAILED:
std::cerr << name_ << " ICE reached failed (" << stream->GetId() << ")"
<< std::endl;
MOZ_ASSERT(ice_reached_checking_);
break ;
case NrIceCtx::ICE_CTX_DISCONNECTED:
std::cerr << name_ << " ICE reached disconnected (" << stream->GetId()
<< ")" << std::endl;
MOZ_ASSERT(ice_reached_checking_);
break ;
case NrIceCtx::ICE_CTX_CLOSED:
std::cerr << name_ << " ICE reached closed (" << stream->GetId() << ")"
<< std::endl;
break ;
}
}
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 <const unsigned char *>(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<NrIceCtx> ice_ctx_;
bool offerer_;
std::map<std::string, std::vector<std::string>> candidates_;
// Maps from stream id to list of remote trickle candidates
std::map<size_t, std::vector<SchedulableTrickleCandidate*>>
controlled_trickle_candidates_;
std::map<std::string, std::pair<std::string, std::string>> mIceCredentials;
std::map<std::string, std::pair<std::string, std::string>> mOldIceCredentials;
size_t stream_counter_;
bool shutting_down_;
DataMutex<std::map<std::string, NrIceCtx::ConnectionState>> mConnectionStates;
DataMutex<std::map<std::string, NrIceMediaStream::GatheringState>>
mGatheringStates;
std::atomic<int > ready_ct_;
bool ice_reached_checking_;
std::atomic<size_t> received_;
std::atomic<size_t> sent_;
struct timeval consent_timestamp_;
NrIceResolverFake fake_resolver_;
RefPtr<NrIceResolver> 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<mozilla::TestNat> 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<IceTestPeer>("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<NrIceStunServer>* 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<NrIceStunServer> 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<NrIceStunServer> 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<NrIceStunServer> 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<std::string> 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<std::string> 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<IceTestPeer> 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<IceTestPeer>("P1" , test_utils_, true , config);
p2_ = MakeUnique<IceTestPeer>("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) {
if (stun_server_address_.empty()) {
InitTestStunServer();
peer->UseTestStunServer();
} else {
std::vector<NrIceStunServer> 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<NrIceTurnServer>& 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(callee->ready_ct() == 0);
ASSERT_TRUE(callee->ice_connected() == 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 <const unsigned char *>("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<nsIEventTarget> target_;
mozilla::UniquePtr<IceTestPeer> p1_;
mozilla::UniquePtr<IceTestPeer> 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();
test_utils_->SyncDispatchToSTS(
WrapRunnable(this , &WebRtcIcePacketFilterTest::SetUp_s));
nsCOMPtr<nsISocketFilterHandler> udp_handler =
do_GetService(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID);
ASSERT_TRUE(udp_handler);
udp_handler->NewFilter(getter_AddRefs(udp_filter_));
nsCOMPtr<nsISocketFilterHandler> tcp_handler =
do_GetService(NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID);
ASSERT_TRUE(tcp_handler);
tcp_handler->NewFilter(getter_AddRefs(tcp_filter_));
}
void SetUp_s() {
NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
// Set up enough of the ICE ctx to allow the packet filter to work
ice_ctx_ = NrIceCtx::Create("test" );
}
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);
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=97 H=94 G=95
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland