diff --git a/media/mtransport/common.build b/media/mtransport/common.build index 01b0e9682ead..0d441f79c882 100644 --- a/media/mtransport/common.build +++ b/media/mtransport/common.build @@ -16,6 +16,7 @@ mtransport_lcppsrcs = [ 'rlogringbuffer.cpp', 'simpletokenbucket.cpp', 'stun_udp_socket_filter.cpp', + 'test_nr_socket.cpp', 'transportflow.cpp', 'transportlayer.cpp', 'transportlayerdtls.cpp', diff --git a/media/mtransport/nr_socket_prsock.cpp b/media/mtransport/nr_socket_prsock.cpp index 93be754baefa..998d2d7dbc02 100644 --- a/media/mtransport/nr_socket_prsock.cpp +++ b/media/mtransport/nr_socket_prsock.cpp @@ -653,7 +653,7 @@ int NrSocket::sendto(const void *msg, size_t len, if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); - r_log(LOG_GENERIC, LOG_INFO, "Error in sendto %s", to->as_string); + r_log(LOG_GENERIC, LOG_INFO, "Error in sendto: %s", to->as_string); ABORT(R_IO_ERROR); } @@ -674,7 +674,7 @@ int NrSocket::recvfrom(void * buf, size_t maxlen, if (status <= 0) { if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); - r_log(LOG_GENERIC, LOG_INFO, "Error in recvfrom"); + r_log(LOG_GENERIC, LOG_INFO, "Error in recvfrom: %d", (int)PR_GetError()); ABORT(R_IO_ERROR); } *len=status; @@ -1309,7 +1309,7 @@ static nr_socket_vtbl nr_socket_local_vtbl={ nr_socket_local_close }; -int nr_socket_local_create(nr_transport_addr *addr, nr_socket **sockp) { +int nr_socket_local_create(void *obj, nr_transport_addr *addr, nr_socket **sockp) { RefPtr sock; // create IPC bridge for content process diff --git a/media/mtransport/nr_socket_prsock.h b/media/mtransport/nr_socket_prsock.h index d892cd5ad7d5..1c2623ee621f 100644 --- a/media/mtransport/nr_socket_prsock.h +++ b/media/mtransport/nr_socket_prsock.h @@ -116,6 +116,9 @@ public: static TimeStamp short_term_violation_time(); static TimeStamp long_term_violation_time(); + const nr_transport_addr& my_addr() const { + return my_addr_; + } protected: void fire_callback(int how); @@ -163,7 +166,7 @@ public: virtual int write(const void *msg, size_t len, size_t *written) override; virtual int read(void* buf, size_t maxlen, size_t *len) override; -private: +protected: virtual ~NrSocket() { if (fd_) PR_Close(fd_); diff --git a/media/mtransport/test/ice_unittest.cpp b/media/mtransport/test/ice_unittest.cpp index c7124229186c..54602be1bc1d 100644 --- a/media/mtransport/test/ice_unittest.cpp +++ b/media/mtransport/test/ice_unittest.cpp @@ -35,6 +35,9 @@ #include "rlogringbuffer.h" #include "runnable_utils.h" #include "stunserver.h" +#include "nr_socket_prsock.h" +#include "test_nr_socket.h" +#include "ice_ctx.h" // TODO(bcampen@mozilla.com): Big fat hack since the build system doesn't give // us a clean way to add object files to a single executable. #include "stunserver.cpp" @@ -250,13 +253,21 @@ class IceTestPeer : public sigslot::has_slots<> { expected_remote_type_(NrIceCandidate::ICE_HOST), trickle_mode_(TRICKLE_NONE), trickled_(0), - simulate_ice_lite_(false) { + simulate_ice_lite_(false), + nat_(new TestNat) { ice_ctx_->SignalGatheringStateChange.connect( this, &IceTestPeer::GatheringStateChange); ice_ctx_->SignalConnectionStateChange.connect( this, &IceTestPeer::ConnectionStateChange); + + nr_socket_factory *fac; + int r = nat_->create_socket_factory(&fac); + MOZ_ASSERT(!r); + if (!r) { + nr_ice_ctx_set_socket_factory(ice_ctx_->ctx(), fac); + } } ~IceTestPeer() { @@ -317,6 +328,11 @@ class IceTestPeer : public sigslot::has_slots<> { ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers))); } + void UseTestStunServer() { + SetStunServer(TestStunServer::GetInstance()->addr(), + TestStunServer::GetInstance()->port()); + } + void SetTurnServer(const std::string addr, uint16_t port, const std::string username, const std::string password, @@ -371,6 +387,25 @@ class IceTestPeer : public sigslot::has_slots<> { ASSERT_TRUE(NS_SUCCEEDED(res)); } + void UseNat() { + nat_->enabled_ = true; + } + + 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; + } + // Get various pieces of state std::vector GetGlobalAttributes() { std::vector attrs(ice_ctx_->GetGlobalAttributes()); @@ -1008,6 +1043,7 @@ class IceTestPeer : public sigslot::has_slots<> { TrickleMode trickle_mode_; int trickled_; bool simulate_ice_lite_; + nsRefPtr nat_; }; void SchedulableTrickleCandidate::Trickle() { @@ -1065,6 +1101,12 @@ class IceGatherTest : public ::testing::Test { TestStunServer::GetInstance()->port()); } + void UseTestStunServer() { + TestStunServer::GetInstance()->Reset(); + peer_->SetStunServer(TestStunServer::GetInstance()->addr(), + TestStunServer::GetInstance()->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, @@ -1084,7 +1126,12 @@ class IceGatherTest : public ::testing::Test { class IceConnectTest : public ::testing::Test { public: - IceConnectTest() : initted_(false) {} + IceConnectTest() : + initted_(false), + use_nat_(false), + filtering_type_(TestNat::ENDPOINT_INDEPENDENT), + mapping_type_(TestNat::ENDPOINT_INDEPENDENT), + block_udp_(false) {} void SetUp() { nsresult rv; @@ -1126,8 +1173,25 @@ class IceConnectTest : public ::testing::Test { bool Gather(unsigned int waitTime = kDefaultTimeout) { Init(false, false); - p1_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); - p2_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); + 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. + UseTestStunServer(); + p1_->UseNat(); + p2_->UseNat(); + p1_->SetFilteringType(filtering_type_); + p2_->SetFilteringType(filtering_type_); + p1_->SetMappingType(mapping_type_); + p2_->SetMappingType(mapping_type_); + p1_->SetBlockUdp(block_udp_); + p2_->SetBlockUdp(block_udp_); + } else { + p1_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); + p2_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); + } + p1_->Gather(); p2_->Gather(); @@ -1142,6 +1206,28 @@ class IceConnectTest : public ::testing::Test { return true; } + void UseNat() { + use_nat_ = true; + } + + void SetFilteringType(TestNat::NatBehavior type) { + filtering_type_ = type; + } + + void SetMappingType(TestNat::NatBehavior type) { + mapping_type_ = type; + } + + void BlockUdp() { + block_udp_ = true; + } + + void UseTestStunServer() { + TestStunServer::GetInstance()->Reset(); + p1_->UseTestStunServer(); + p2_->UseTestStunServer(); + } + void SetTurnServer(const std::string addr, uint16_t port, const std::string username, const std::string password, @@ -1277,6 +1363,10 @@ class IceConnectTest : public ::testing::Test { nsCOMPtr target_; mozilla::ScopedDeletePtr p1_; mozilla::ScopedDeletePtr p2_; + bool use_nat_; + TestNat::NatBehavior filtering_type_; + TestNat::NatBehavior mapping_type_; + bool block_udp_; }; class PrioritizerTest : public ::testing::Test { @@ -1643,6 +1733,147 @@ TEST_F(IceConnectTest, TestTrickleIceLiteOfferer) { AssertCheckingReached(); } +TEST_F(IceConnectTest, TestGatherFullCone) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::ENDPOINT_INDEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + ASSERT_TRUE(Gather()); +} + +TEST_F(IceConnectTest, TestGatherFullConeAutoPrioritize) { + Init(false, true); + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::ENDPOINT_INDEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + ASSERT_TRUE(Gather()); +} + + +TEST_F(IceConnectTest, TestConnectFullCone) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::ENDPOINT_INDEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, + NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); + ASSERT_TRUE(Gather()); + Connect(); +} + +TEST_F(IceConnectTest, TestGatherAddressRestrictedCone) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::ADDRESS_DEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + ASSERT_TRUE(Gather()); +} + +TEST_F(IceConnectTest, TestConnectAddressRestrictedCone) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::ADDRESS_DEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, + NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); + ASSERT_TRUE(Gather()); + Connect(); +} + +TEST_F(IceConnectTest, TestGatherPortRestrictedCone) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::PORT_DEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + ASSERT_TRUE(Gather()); +} + +TEST_F(IceConnectTest, TestConnectPortRestrictedCone) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::PORT_DEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, + NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); + ASSERT_TRUE(Gather()); + Connect(); +} + +TEST_F(IceConnectTest, TestGatherSymmetricNat) { + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::PORT_DEPENDENT); + SetMappingType(TestNat::PORT_DEPENDENT); + ASSERT_TRUE(Gather()); +} + +TEST_F(IceConnectTest, TestConnectSymmetricNat) { + if (g_turn_server.empty()) + return; + + AddStream("first", 1); + 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(g_turn_server, kDefaultStunServerPort, + g_turn_user, g_turn_password); + ASSERT_TRUE(Gather()); + Connect(); +} + +TEST_F(IceConnectTest, TestGatherNatBlocksUDP) { + if (g_turn_server.empty()) + return; + + AddStream("first", 1); + UseNat(); + BlockUdp(); + std::vector turn_servers; + std::vector password_vec(g_turn_password.begin(), + g_turn_password.end()); + turn_servers.push_back( + *NrIceTurnServer::Create(g_turn_server, kDefaultStunServerPort, + g_turn_user, password_vec, kNrIceTransportTcp)); + turn_servers.push_back( + *NrIceTurnServer::Create(g_turn_server, kDefaultStunServerPort, + g_turn_user, password_vec, kNrIceTransportUdp)); + SetTurnServers(turn_servers); + // We have to wait for the UDP-based stuff to time out. + ASSERT_TRUE(Gather(kDefaultTimeout * 3)); +} + +TEST_F(IceConnectTest, TestConnectNatBlocksUDP) { + if (g_turn_server.empty()) + return; + + AddStream("first", 1); + UseNat(); + BlockUdp(); + std::vector turn_servers; + std::vector password_vec(g_turn_password.begin(), + g_turn_password.end()); + turn_servers.push_back( + *NrIceTurnServer::Create(g_turn_server, kDefaultStunServerPort, + g_turn_user, password_vec, kNrIceTransportTcp)); + turn_servers.push_back( + *NrIceTurnServer::Create(g_turn_server, kDefaultStunServerPort, + g_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); + ASSERT_TRUE(Gather(kDefaultTimeout * 3)); + Connect(); +} + TEST_F(IceConnectTest, TestConnectTwoComponents) { AddStream("first", 2); ASSERT_TRUE(Gather()); diff --git a/media/mtransport/test/moz.build b/media/mtransport/test/moz.build index 3f74840ac1fb..71f8abf68aa6 100644 --- a/media/mtransport/test/moz.build +++ b/media/mtransport/test/moz.build @@ -14,6 +14,7 @@ if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk': 'runnable_utils_unittest', 'simpletokenbucket_unittest', 'sockettransportservice_unittest', + 'test_nr_socket_unittest', 'TestSyncRunnable', 'transport_unittests', 'turn_unittest', @@ -30,6 +31,8 @@ for var in ('MOZILLA_INTERNAL_API', 'MOZILLA_XPCOMRT_API', 'MOZILLA_EXTERNAL_LIN DEFINES[var] = True if CONFIG['OS_TARGET'] == 'Android': + DEFINES['LINUX'] = True + DEFINES['ANDROID'] = True LOCAL_INCLUDES += [ '/media/mtransport/third_party/nrappkit/src/port/android/include', ] @@ -37,6 +40,8 @@ else: DEFINES['INET6'] = True if CONFIG['OS_TARGET'] == 'Linux': + DEFINES['LINUX'] = True + DEFINES['USE_INTERFACE_PRIORITIZER'] = True LOCAL_INCLUDES += [ '/media/mtransport/third_party/nrappkit/src/port/linux/include', ] @@ -53,12 +58,20 @@ if CONFIG['OS_TARGET'] == 'Darwin': ] if CONFIG['OS_TARGET'] in ('DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD'): + if CONFIG['OS_TARGET'] == 'Darwin': + DEFINES['DARWIN'] = True + else: + DEFINES['BSD'] = True LOCAL_INCLUDES += [ '/media/mtransport/third_party/nrappkit/src/port/darwin/include', ] # SCTP DEFINES if CONFIG['OS_TARGET'] == 'WINNT': + DEFINES['WIN'] = True + # for stun.h + DEFINES['WIN32'] = True + DEFINES['NOMINMAX'] = True DEFINES['__Userspace_os_Windows'] = 1 else: # Works for Darwin, Linux, Android. Probably doesn't work for others. diff --git a/media/mtransport/test/stunserver.cpp b/media/mtransport/test/stunserver.cpp index 9f71f646a311..f0a4bb51cb6e 100644 --- a/media/mtransport/test/stunserver.cpp +++ b/media/mtransport/test/stunserver.cpp @@ -221,7 +221,7 @@ int TestStunServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) { return R_INTERNAL; } - if (nr_socket_local_create(&addr->addr, &listen_sock_)) { + if (nr_socket_local_create(nullptr, &addr->addr, &listen_sock_)) { MOZ_MTLOG(ML_ERROR, "Couldn't create listen socket"); return R_ALREADY; } diff --git a/media/mtransport/test/test_nr_socket_unittest.cpp b/media/mtransport/test/test_nr_socket_unittest.cpp new file mode 100644 index 000000000000..bbe6ff54b2a1 --- /dev/null +++ b/media/mtransport/test/test_nr_socket_unittest.cpp @@ -0,0 +1,660 @@ +/* -*- 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: bcampen@mozilla.com + +extern "C" { +#include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE +#include "stun_util.h" +#include "nr_api.h" +#include "async_wait.h" +#include "nr_socket.h" +#include "nr_socket_local.h" +#include "stun_hint.h" +#include "local_addr.h" +#include "registry.h" +} + +#include "test_nr_socket.h" + +#include "nsCOMPtr.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsAutoPtr.h" +#include "runnable_utils.h" +#include "mtransport_test_utils.h" + +#include + +#define GTEST_HAS_RTTI 0 +#include "gtest/gtest.h" +#include "gtest_utils.h" + +namespace mozilla { + +class TestNrSocketTest : public ::testing::Test { + public: + TestNrSocketTest() : + wait_done_for_main_(false), + sts_(), + public_addrs_(), + private_addrs_(), + nats_() { + // Get the transport service as a dispatch target + nsresult rv; + sts_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + EXPECT_TRUE(NS_SUCCEEDED(rv)) << "Failed to get STS: " << (int)rv; + } + + ~TestNrSocketTest() { + sts_->Dispatch(WrapRunnable(this, &TestNrSocketTest::TearDown_s), + NS_DISPATCH_SYNC); + } + + void TearDown_s() { + public_addrs_.clear(); + private_addrs_.clear(); + nats_.clear(); + sts_ = nullptr; + } + + nsRefPtr CreateTestNrSocket_s(const char *ip_str, + TestNat *nat) { + // If no nat is supplied, we create a default NAT which is disabled. This + // is how we simulate a non-natted socket. + nsRefPtr sock(new TestNrSocket(nat ? nat : new TestNat)); + nr_transport_addr address; + nr_ip4_str_port_to_transport_addr(ip_str, 0, IPPROTO_UDP, &address); + int r = sock->create(&address); + if (r) { + return nullptr; + } + return sock; + } + + void CreatePublicAddrs(size_t count, const char *ip_str = "127.0.0.1") { + sts_->Dispatch( + WrapRunnable(this, + &TestNrSocketTest::CreatePublicAddrs_s, + count, + ip_str), + NS_DISPATCH_SYNC); + } + + void CreatePublicAddrs_s(size_t count, const char* ip_str) { + while (count--) { + auto sock = CreateTestNrSocket_s(ip_str, nullptr); + ASSERT_TRUE(sock) << "Failed to create socket"; + public_addrs_.push_back(sock); + } + } + + nsRefPtr CreatePrivateAddrs(size_t size, + const char* ip_str = "127.0.0.1") { + nsRefPtr result; + sts_->Dispatch( + WrapRunnableRet(this, + &TestNrSocketTest::CreatePrivateAddrs_s, + size, + ip_str, + &result), + NS_DISPATCH_SYNC); + return result; + } + + nsRefPtr CreatePrivateAddrs_s(size_t count, const char* ip_str) { + nsRefPtr nat(new TestNat); + while (count--) { + auto sock = CreateTestNrSocket_s(ip_str, nat); + if (!sock) { + EXPECT_TRUE(false) << "Failed to create socket"; + break; + } + private_addrs_.push_back(sock); + } + nat->enabled_ = true; + nats_.push_back(nat); + return nat; + } + + bool CheckConnectivityVia( + TestNrSocket *from, + TestNrSocket *to, + const nr_transport_addr &via, + nr_transport_addr *sender_external_address = nullptr) { + MOZ_ASSERT(from); + + if (!WaitForWriteable(from)) { + return false; + } + + int result = 0; + sts_->Dispatch(WrapRunnableRet(this, + &TestNrSocketTest::SendData_s, + from, + via, + &result), + NS_DISPATCH_SYNC); + if (result) { + return false; + } + + if (!WaitForReadable(to)) { + return false; + } + + nr_transport_addr dummy_outparam; + if (!sender_external_address) { + sender_external_address = &dummy_outparam; + } + + MOZ_ASSERT(to); + sts_->Dispatch(WrapRunnableRet(this, + &TestNrSocketTest::RecvData_s, + to, + sender_external_address, + &result), + NS_DISPATCH_SYNC); + + return !result; + } + + bool CheckConnectivity( + TestNrSocket *from, + TestNrSocket *to, + nr_transport_addr *sender_external_address = nullptr) { + nr_transport_addr destination_address; + int r = GetAddress(to, &destination_address); + if (r) { + return false; + } + + return CheckConnectivityVia(from, + to, + destination_address, + sender_external_address); + } + + int GetAddress(TestNrSocket *sock, nr_transport_addr_ *address) { + MOZ_ASSERT(sock); + MOZ_ASSERT(address); + int r; + sts_->Dispatch(WrapRunnableRet(this, + &TestNrSocketTest::GetAddress_s, + sock, + address, + &r), + NS_DISPATCH_SYNC); + return r; + } + + int GetAddress_s(TestNrSocket *sock, nr_transport_addr *address) { + return sock->getaddr(address); + } + + int SendData_s(TestNrSocket *from, const nr_transport_addr &to) { + // It is up to caller to ensure that |from| is writeable. + const char buf[] = "foobajooba"; + return from->sendto(buf, sizeof(buf), 0, + // TODO(bug 1170299): Remove const_cast when no longer necessary + const_cast(&to)); + } + + int RecvData_s(TestNrSocket *to, nr_transport_addr *from) { + // It is up to caller to ensure that |to| is readable + const size_t bufSize = 1024; + char buf[bufSize]; + size_t len; + // Maybe check that data matches? + int r = to->recvfrom(buf, sizeof(buf), &len, 0, from); + if (!r && (len == 0)) { + r = R_INTERNAL; + } + return r; + } + + bool WaitForSocketState(TestNrSocket *sock, int state) { + MOZ_ASSERT(sock); + sts_->Dispatch(WrapRunnable(this, + &TestNrSocketTest::WaitForSocketState_s, + sock, + state), + NS_DISPATCH_SYNC); + + bool res; + WAIT_(wait_done_for_main_, 100, res); + wait_done_for_main_ = false; + + if (!res) { + sts_->Dispatch(WrapRunnable(this, + &TestNrSocketTest::CancelWait_s, + sock, + state), + NS_DISPATCH_SYNC); + } + + return res; + } + + void WaitForSocketState_s(TestNrSocket *sock, int state) { + NR_ASYNC_WAIT(sock, state, &WaitDone, this); + } + + void CancelWait_s(TestNrSocket *sock, int state) { + sock->cancel(state); + } + + bool WaitForReadable(TestNrSocket *sock) { + return WaitForSocketState(sock, NR_ASYNC_WAIT_READ); + } + + bool WaitForWriteable(TestNrSocket *sock) { + return WaitForSocketState(sock, NR_ASYNC_WAIT_WRITE); + } + + static void WaitDone(void *sock, int how, void *test_fixture) { + TestNrSocketTest *test = static_cast(test_fixture); + test->wait_done_for_main_ = true; + } + + // Simple busywait boolean for the test cases to spin on. + Atomic wait_done_for_main_; + + nsCOMPtr sts_; + std::vector> public_addrs_; + std::vector> private_addrs_; + std::vector> nats_; +}; + +} // namespace mozilla + +using mozilla::TestNrSocketTest; +using mozilla::TestNat; + +TEST_F(TestNrSocketTest, PublicConnectivity) { + CreatePublicAddrs(2); + + ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[1])); + ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[0])); + ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[0])); + ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[1])); +} + +TEST_F(TestNrSocketTest, PrivateConnectivity) { + nsRefPtr nat(CreatePrivateAddrs(2)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[1])); + ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[0])); + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[0])); + ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[1])); +} + +TEST_F(TestNrSocketTest, NoConnectivityWithoutPinhole) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + CreatePublicAddrs(1); + + ASSERT_FALSE(CheckConnectivity(public_addrs_[0], private_addrs_[0])); +} + +TEST_F(TestNrSocketTest, NoConnectivityBetweenSubnets) { + nsRefPtr nat1(CreatePrivateAddrs(1)); + nat1->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat1->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + nsRefPtr nat2(CreatePrivateAddrs(1)); + nat2->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat2->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + + ASSERT_FALSE(CheckConnectivity(private_addrs_[0], private_addrs_[1])); + ASSERT_FALSE(CheckConnectivity(private_addrs_[1], private_addrs_[0])); + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[0])); + ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[1])); +} + +TEST_F(TestNrSocketTest, FullConeAcceptIngress) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + CreatePublicAddrs(2); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); + + // Verify that other public IP can use the pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address)); +} + +TEST_F(TestNrSocketTest, FullConeOnePinhole) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + CreatePublicAddrs(2); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); + + // Send traffic to other public IP, verify that it uses the same pinhole + nr_transport_addr sender_external_address2; + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[1], + &sender_external_address2)); + ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address, + &sender_external_address2, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + << "addr1: " << sender_external_address.as_string << " addr2: " + << sender_external_address2.as_string; +} + +// OS 10.6 doesn't seem to allow us to open ports on 127.0.0.2, and while linux +// does allow this, it has other behavior (see below) that prevents this test +// from working. +TEST_F(TestNrSocketTest, DISABLED_AddressRestrictedCone) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::ADDRESS_DEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + CreatePublicAddrs(2, "127.0.0.1"); + CreatePublicAddrs(1, "127.0.0.2"); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); + + // Verify that another address on the same host can use the pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address)); + + // Linux has a tendency to monkey around with source addresses, doing + // stuff like substituting 127.0.0.1 for packets sent by 127.0.0.2, and even + // going as far as substituting localhost for a packet sent from a real IP + // address when the destination is localhost. The only way to make this test + // work on linux is to have two real IP addresses. +#ifndef __linux__ + // Verify that an address on a different host can't use the pinhole + ASSERT_FALSE(CheckConnectivityVia(public_addrs_[2], + private_addrs_[0], + sender_external_address)); +#endif + + // Send traffic to other public IP, verify that it uses the same pinhole + nr_transport_addr sender_external_address2; + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[1], + &sender_external_address2)); + ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address, + &sender_external_address2, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + << "addr1: " << sender_external_address.as_string << " addr2: " + << sender_external_address2.as_string; + + // Verify that the other public IP can now use the pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address2)); + + // Send traffic to other public IP, verify that it uses the same pinhole + nr_transport_addr sender_external_address3; + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[2], + &sender_external_address3)); + ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address, + &sender_external_address3, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + << "addr1: " << sender_external_address.as_string << " addr2: " + << sender_external_address3.as_string; + + // Verify that the other public IP can now use the pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[2], + private_addrs_[0], + sender_external_address3)); +} + +TEST_F(TestNrSocketTest, RestrictedCone) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::PORT_DEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + CreatePublicAddrs(2); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); + + // Verify that other public IP cannot use the pinhole + ASSERT_FALSE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address)); + + // Send traffic to other public IP, verify that it uses the same pinhole + nr_transport_addr sender_external_address2; + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[1], + &sender_external_address2)); + ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address, + &sender_external_address2, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + << "addr1: " << sender_external_address.as_string << " addr2: " + << sender_external_address2.as_string; + + // Verify that the other public IP can now use the pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address2)); +} + +TEST_F(TestNrSocketTest, PortDependentMappingFullCone) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::PORT_DEPENDENT; + CreatePublicAddrs(2); + + nr_transport_addr sender_external_address0; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address0)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address0)); + + // Verify that other public IP can use the pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address0)); + + // Send traffic to other public IP, verify that it uses a different pinhole + nr_transport_addr sender_external_address1; + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[1], + &sender_external_address1)); + ASSERT_TRUE(nr_transport_addr_cmp(&sender_external_address0, + &sender_external_address1, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + << "addr1: " << sender_external_address0.as_string << " addr2: " + << sender_external_address1.as_string; + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address1)); + + // Verify that other public IP can use the original pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address1)); +} + +TEST_F(TestNrSocketTest, Symmetric) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::PORT_DEPENDENT; + nat->mapping_type_ = TestNat::PORT_DEPENDENT; + CreatePublicAddrs(2); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); + + // Verify that other public IP cannot use the pinhole + ASSERT_FALSE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address)); + + // Send traffic to other public IP, verify that it uses a new pinhole + nr_transport_addr sender_external_address2; + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[1], + &sender_external_address2)); + ASSERT_TRUE(nr_transport_addr_cmp(&sender_external_address, + &sender_external_address2, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)); + + // Verify that the other public IP can use the new pinhole + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], + private_addrs_[0], + sender_external_address2)); +} + +TEST_F(TestNrSocketTest, BlockUdp) { + nsRefPtr nat(CreatePrivateAddrs(2)); + nat->block_udp_ = true; + CreatePublicAddrs(1); + + nr_transport_addr sender_external_address; + ASSERT_FALSE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Make sure UDP behind the NAT still works + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + private_addrs_[1])); + ASSERT_TRUE(CheckConnectivity(private_addrs_[1], + private_addrs_[0])); +} + +TEST_F(TestNrSocketTest, DenyHairpinning) { + nsRefPtr nat(CreatePrivateAddrs(2)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + CreatePublicAddrs(1); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that hairpinning is disallowed + ASSERT_FALSE(CheckConnectivityVia(private_addrs_[1], + private_addrs_[0], + sender_external_address)); +} + +TEST_F(TestNrSocketTest, AllowHairpinning) { + nsRefPtr nat(CreatePrivateAddrs(2)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_timeout_ = 30000; + nat->allow_hairpinning_ = true; + CreatePublicAddrs(1); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0, obtain external address + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that hairpinning is allowed + ASSERT_TRUE(CheckConnectivityVia(private_addrs_[1], + private_addrs_[0], + sender_external_address)); +} + +TEST_F(TestNrSocketTest, FullConeTimeout) { + nsRefPtr nat(CreatePrivateAddrs(1)); + nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT; + nat->mapping_timeout_ = 200; + CreatePublicAddrs(2); + + nr_transport_addr sender_external_address; + // Open pinhole to public IP 0 + ASSERT_TRUE(CheckConnectivity(private_addrs_[0], + public_addrs_[0], + &sender_external_address)); + + // Verify that return traffic works + ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); + + PR_Sleep(201); + + // Verify that return traffic does not work + ASSERT_FALSE(CheckConnectivityVia(public_addrs_[0], + private_addrs_[0], + sender_external_address)); +} + +// TODO(): We need TCP tests, but first we will need ICE TCP to land (this +// adds listen/accept support to NrSocket) + +int main(int argc, char **argv) +{ + // Inits STS and some other stuff. + MtransportTestUtils test_utils; + + NR_reg_init(NR_REG_MODE_LOCAL); + + // Start the tests + ::testing::InitGoogleTest(&argc, argv); + + int rv = RUN_ALL_TESTS(); + + return rv; +} diff --git a/media/mtransport/test/turn_unittest.cpp b/media/mtransport/test/turn_unittest.cpp index 898d0439d540..b1e53c7a17da 100644 --- a/media/mtransport/test/turn_unittest.cpp +++ b/media/mtransport/test/turn_unittest.cpp @@ -119,7 +119,7 @@ class TurnClient : public ::testing::Test { r = nr_ip4_port_to_transport_addr(0, 0, protocol_, &addr); ASSERT_EQ(0, r); - r = nr_socket_local_create(&addr, &real_socket_); + r = nr_socket_local_create(nullptr, &addr, &real_socket_); ASSERT_EQ(0, r); if (protocol_ == IPPROTO_TCP) { diff --git a/media/mtransport/test_nr_socket.cpp b/media/mtransport/test_nr_socket.cpp new file mode 100644 index 000000000000..1d6c8ca195dd --- /dev/null +++ b/media/mtransport/test_nr_socket.cpp @@ -0,0 +1,763 @@ +/* -*- 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/. */ +/* +*/ + +/* +Based partially on original code from nICEr and nrappkit. + +nICEr copyright: + +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +nrappkit copyright: + + Copyright (C) 2001-2003, Network Resonance, Inc. + Copyright (C) 2006, Network Resonance, Inc. + All Rights Reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Network Resonance, Inc. nor the name of any + contributors to this software may be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + ekr@rtfm.com Thu Dec 20 20:14:49 2001 +*/ + +// Original author: bcampen@mozilla.com [:bwc] + +extern "C" { +#include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE +#include "nr_api.h" +#include "async_wait.h" +#include "nr_socket.h" +#include "nr_socket_local.h" +#include "stun_hint.h" +#include "transport_addr.h" +} + +#include "mozilla/RefPtr.h" +#include "test_nr_socket.h" +#include "runnable_utils.h" + +namespace mozilla { + +static int test_nat_socket_create(void *obj, + nr_transport_addr *addr, + nr_socket **sockp) { + RefPtr sock = new TestNrSocket(static_cast(obj)); + + int r, _status; + + r = sock->create(addr); + if (r) + ABORT(r); + + r = nr_socket_create_int(static_cast(sock), + sock->vtbl(), sockp); + if (r) + ABORT(r); + + _status = 0; + + { + // We will release this reference in destroy(), not exactly the normal + // ownership model, but it is what it is. + NrSocketBase *dummy = sock.forget().take(); + (void)dummy; + } + +abort: + return _status; +} + +static int test_nat_socket_factory_destroy(void **obj) { + TestNat *nat = static_cast(*obj); + *obj = nullptr; + nat->Release(); + return 0; +} + +static nr_socket_factory_vtbl test_nat_socket_factory_vtbl = { + test_nat_socket_create, + test_nat_socket_factory_destroy +}; + +bool TestNat::has_port_mappings() const { + for (TestNrSocket *sock : sockets_) { + if (sock->has_port_mappings()) { + return true; + } + } + return false; +} + +bool TestNat::is_my_external_tuple(const nr_transport_addr &addr) const { + for (TestNrSocket *sock : sockets_) { + if (sock->is_my_external_tuple(addr)) { + return true; + } + } + + return false; +} + +bool TestNat::is_an_internal_tuple(const nr_transport_addr &addr) const { + for (TestNrSocket *sock : sockets_) { + nr_transport_addr addr_behind_nat; + if (sock->getaddr(&addr_behind_nat)) { + MOZ_CRASH("TestNrSocket::getaddr failed!"); + } + + // TODO(bug 1170299): Remove const_cast when no longer necessary + if (!nr_transport_addr_cmp(const_cast(&addr), + &addr_behind_nat, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) { + return true; + } + } + return false; +} + +int TestNat::create_socket_factory(nr_socket_factory **factorypp) { + int r = nr_socket_factory_create_int(this, + &test_nat_socket_factory_vtbl, + factorypp); + if (!r) { + AddRef(); + } + return r; +} + +TestNrSocket::TestNrSocket(TestNat *nat) + : nat_(nat) { + nat_->insert_socket(this); +} + +TestNrSocket::~TestNrSocket() { + nat_->erase_socket(this); +} + +nsRefPtr TestNrSocket::create_external_socket( + const nr_transport_addr &dest_addr) const { + MOZ_ASSERT(nat_->enabled_); + MOZ_ASSERT(!nat_->is_an_internal_tuple(dest_addr)); + + int r; + nr_transport_addr nat_external_addr; + + // Open the socket on an arbitrary port, on the same address. + // TODO(bug 1170299): Remove const_cast when no longer necessary + if ((r = nr_transport_addr_copy(&nat_external_addr, + const_cast(&my_addr_)))) { + r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_copy: %d", + __FUNCTION__, r); + return nullptr; + } + + if ((r = nr_transport_addr_set_port(&nat_external_addr, 0))) { + r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_set_port: %d", + __FUNCTION__, r); + return nullptr; + } + + nsRefPtr external_socket = new NrSocket; + + if ((r = external_socket->create(&nat_external_addr))) { + r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in NrSocket::create: %d", + __FUNCTION__, r); + return nullptr; + } + + return external_socket; +} + +int TestNrSocket::sendto(const void *msg, size_t len, + int flags, nr_transport_addr *to) { + MOZ_ASSERT(my_addr_.protocol != IPPROTO_TCP); + ASSERT_ON_THREAD(ststhread_); + + if (!nat_->enabled_ || nat_->is_an_internal_tuple(*to)) { + return NrSocket::sendto(msg, len, flags, to); + } + + destroy_stale_port_mappings(); + + if (to->protocol == IPPROTO_UDP && nat_->block_udp_) { + // Silently eat the packet + return 0; + } + + // Choose our port mapping based on our most selective criteria + PortMapping *port_mapping = get_port_mapping(*to, + std::max(nat_->filtering_type_, + nat_->mapping_type_)); + + if (!port_mapping) { + // See if we have already made the external socket we need to use. + PortMapping *similar_port_mapping = + get_port_mapping(*to, nat_->mapping_type_); + nsRefPtr external_socket; + + if (similar_port_mapping) { + external_socket = similar_port_mapping->external_socket_; + } else { + external_socket = create_external_socket(*to); + if (!external_socket) { + MOZ_ASSERT(false); + return R_INTERNAL; + } + } + + port_mapping = create_port_mapping(*to, external_socket); + port_mappings_.push_back(port_mapping); + + if (poll_flags() & PR_POLL_READ) { + // Make sure the new port mapping is ready to receive traffic if the + // TestNrSocket is already waiting. + port_mapping->async_wait(NR_ASYNC_WAIT_READ, + port_mapping_readable_callback, + this, + (char*)__FUNCTION__, + __LINE__); + } + } + + // We probably don't want to propagate the flags, since this is a simulated + // external IP address. + return port_mapping->sendto(msg, len, *to); +} + +int TestNrSocket::recvfrom(void *buf, size_t maxlen, + size_t *len, int flags, + nr_transport_addr *from) { + MOZ_ASSERT(my_addr_.protocol != IPPROTO_TCP); + ASSERT_ON_THREAD(ststhread_); + + int r; + bool ingress_allowed = false; + + if (readable_socket_) { + // If any of the external sockets got data, see if it will be passed through + r = readable_socket_->recvfrom(buf, maxlen, len, 0, from); + readable_socket_ = nullptr; + if (!r) { + PortMapping *port_mapping_used; + ingress_allowed = allow_ingress(*from, &port_mapping_used); + if (ingress_allowed && nat_->refresh_on_ingress_ && port_mapping_used) { + port_mapping_used->last_used_ = PR_IntervalNow(); + } + } + } else { + // If no external socket has data, see if there's any data that was sent + // directly to the TestNrSocket, and eat it if it isn't supposed to get + // through. + r = NrSocket::recvfrom(buf, maxlen, len, flags, from); + if (!r) { + // We do not use allow_ingress() here because that only handles traffic + // landing on an external port. + ingress_allowed = (!nat_->enabled_ || + nat_->is_an_internal_tuple(*from)); + if (!ingress_allowed) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Not behind the same NAT", + my_addr_.as_string, + from->as_string); + } + } + } + + // Kinda lame that we are forced to give the app a readable callback and then + // say "Oh, never mind...", but the alternative is to totally decouple the + // callbacks from STS and the callbacks the app sets. On the bright side, this + // speeds up unit tests where we are verifying that ingress is forbidden, + // since they'll get a readable callback and then an error, instead of having + // to wait for a timeout. + if (!ingress_allowed) { + *len = 0; + r = R_WOULDBLOCK; + } + + return r; +} + +bool TestNrSocket::allow_ingress(const nr_transport_addr &from, + PortMapping **port_mapping_used) const { + *port_mapping_used = nullptr; + if (!nat_->enabled_) + return true; + + if (nat_->is_an_internal_tuple(from)) + return true; + + *port_mapping_used = get_port_mapping(from, nat_->filtering_type_); + if (!(*port_mapping_used)) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Filtered", + my_addr_.as_string, + from.as_string); + return false; + } + + if (is_port_mapping_stale(**port_mapping_used)) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Stale port mapping", + my_addr_.as_string, + from.as_string); + return false; + } + + if (!nat_->allow_hairpinning_ && nat_->is_my_external_tuple(from)) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Hairpinning disallowed", + my_addr_.as_string, + from.as_string); + return false; + } + + return true; +} + +int TestNrSocket::connect(nr_transport_addr *addr) { + ASSERT_ON_THREAD(ststhread_); + + if (connect_invoked_ || !port_mappings_.empty()) { + MOZ_CRASH("TestNrSocket::connect() called more than once!"); + return R_INTERNAL; + } + + if (!nat_->enabled_ || nat_->is_an_internal_tuple(*addr)) { + // This will set connect_invoked_ + return NrSocket::connect(addr); + } + + nsRefPtr external_socket(create_external_socket(*addr)); + if (!external_socket) { + return R_INTERNAL; + } + + PortMapping *port_mapping = create_port_mapping(*addr, external_socket); + port_mappings_.push_back(port_mapping); + port_mapping->external_socket_->connect(addr); + port_mapping->last_used_ = PR_IntervalNow(); + + if (poll_flags() & PR_POLL_READ) { + port_mapping->async_wait(NR_ASYNC_WAIT_READ, + port_mapping_tcp_passthrough_callback, + this, + (char*)__FUNCTION__, + __LINE__); + } + + return 0; +} + +int TestNrSocket::write(const void *msg, size_t len, size_t *written) { + ASSERT_ON_THREAD(ststhread_); + + if (port_mappings_.empty()) { + // The no-nat case, just pass call through. + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s writing", + my_addr().as_string); + + return NrSocket::write(msg, len, written); + } else { + // This is TCP only + MOZ_ASSERT(port_mappings_.size() == 1); + r_log(LOG_GENERIC, LOG_INFO, + "PortMapping %s -> %s writing", + port_mappings_.front()->external_socket_->my_addr().as_string, + port_mappings_.front()->remote_address_.as_string); + + return port_mappings_.front()->external_socket_->write(msg, len, written); + } +} + +int TestNrSocket::read(void *buf, size_t maxlen, size_t *len) { + ASSERT_ON_THREAD(ststhread_); + + if (port_mappings_.empty()) { + return NrSocket::read(buf, maxlen, len); + } else { + MOZ_ASSERT(port_mappings_.size() == 1); + return port_mappings_.front()->external_socket_->read(buf, maxlen, len); + } +} + +int TestNrSocket::async_wait(int how, NR_async_cb cb, void *cb_arg, + char *function, int line) { + ASSERT_ON_THREAD(ststhread_); + + // Make sure we're waiting on the socket for the internal address + int r = NrSocket::async_wait(how, cb, cb_arg, function, line); + + if (r) { + return r; + } + + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s waiting for %s", + my_addr_.as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + if (is_tcp_connection_behind_nat()) { + // Bypass all port-mapping related logic + return 0; + } + + if (my_addr_.protocol == IPPROTO_TCP) { + // For a TCP connection through a simulated NAT, these signals are + // just passed through. + MOZ_ASSERT(port_mappings_.size() == 1); + + return port_mappings_.front()->async_wait( + how, + port_mapping_tcp_passthrough_callback, + this, + function, + line); + } else if (how == NR_ASYNC_WAIT_READ) { + // For UDP port mappings, we decouple the writeable callbacks + for (PortMapping *port_mapping : port_mappings_) { + // Be ready to receive traffic on our port mappings + r = port_mapping->async_wait(how, + port_mapping_readable_callback, + this, + function, + line); + if (r) { + return r; + } + } + } + + return 0; +} + +void TestNrSocket::cancel_port_mapping_async_wait(int how) { + for (PortMapping *port_mapping : port_mappings_) { + port_mapping->cancel(how); + } +} + +int TestNrSocket::cancel(int how) { + ASSERT_ON_THREAD(ststhread_); + + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s stop waiting for %s", + my_addr_.as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + // Writable callbacks are decoupled except for the TCP case + if (how == NR_ASYNC_WAIT_READ || my_addr_.protocol == IPPROTO_TCP) { + cancel_port_mapping_async_wait(how); + } + + return NrSocket::cancel(how); +} + +bool TestNrSocket::has_port_mappings() const { + return !port_mappings_.empty(); +} + +bool TestNrSocket::is_my_external_tuple(const nr_transport_addr &addr) const { + for (PortMapping *port_mapping : port_mappings_) { + nr_transport_addr port_mapping_addr; + if (port_mapping->external_socket_->getaddr(&port_mapping_addr)) { + MOZ_CRASH("NrSocket::getaddr failed!"); + } + + // TODO(bug 1170299): Remove const_cast when no longer necessary + if (!nr_transport_addr_cmp(const_cast(&addr), + &port_mapping_addr, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) { + return true; + } + } + return false; +} + +bool TestNrSocket::is_port_mapping_stale( + const PortMapping &port_mapping) const { + PRIntervalTime now = PR_IntervalNow(); + PRIntervalTime elapsed_ticks = now - port_mapping.last_used_; + uint32_t idle_duration = PR_IntervalToMilliseconds(elapsed_ticks); + return idle_duration > nat_->mapping_timeout_; +} + +void TestNrSocket::destroy_stale_port_mappings() { + for (auto i = port_mappings_.begin(); i != port_mappings_.end();) { + auto temp = i; + ++i; + if (is_port_mapping_stale(**temp)) { + r_log(LOG_GENERIC, LOG_INFO, + "TestNrSocket %s destroying port mapping %s -> %s", + my_addr_.as_string, + (*temp)->external_socket_->my_addr().as_string, + (*temp)->remote_address_.as_string); + + port_mappings_.erase(temp); + } + } +} + +void TestNrSocket::port_mapping_readable_callback(void *ext_sock_v, + int how, + void *test_sock_v) { + TestNrSocket *test_socket = static_cast(test_sock_v); + NrSocket *external_socket = static_cast(ext_sock_v); + + test_socket->on_port_mapping_readable(external_socket); +} + +void TestNrSocket::on_port_mapping_readable(NrSocket *external_socket) { + if (!readable_socket_) { + readable_socket_ = external_socket; + } + + // None of our port mappings should be waiting for readable callbacks + // if nobody is waiting for readable callbacks from us. + MOZ_ASSERT(poll_flags() & PR_POLL_READ); + + fire_readable_callback(); +} + +void TestNrSocket::fire_readable_callback() { + MOZ_ASSERT(poll_flags() & PR_POLL_READ); + // Stop listening on all mapped sockets; we will start listening again + // if the app starts listening to us again. + cancel_port_mapping_async_wait(NR_ASYNC_WAIT_READ); + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s ready for read", + my_addr_.as_string); + fire_callback(NR_ASYNC_WAIT_READ); +} + +void TestNrSocket::port_mapping_writeable_callback(void *ext_sock_v, + int how, + void *test_sock_v) { + TestNrSocket *test_socket = static_cast(test_sock_v); + NrSocket *external_socket = static_cast(ext_sock_v); + + test_socket->write_to_port_mapping(external_socket); +} + +void TestNrSocket::write_to_port_mapping(NrSocket *external_socket) { + MOZ_ASSERT(my_addr_.protocol != IPPROTO_TCP); + + int r = 0; + for (PortMapping *port_mapping : port_mappings_) { + if (port_mapping->external_socket_ == external_socket) { + // If the send succeeds, or if there was nothing to send, we keep going + r = port_mapping->send_from_queue(); + if (r) { + break; + } + } + } + + if (r == R_WOULDBLOCK) { + // Re-register for writeable callbacks, since we still have stuff to send + NR_ASYNC_WAIT(external_socket, + NR_ASYNC_WAIT_WRITE, + &TestNrSocket::port_mapping_writeable_callback, + this); + } +} + +void TestNrSocket::port_mapping_tcp_passthrough_callback(void *ext_sock_v, + int how, + void *test_sock_v) { + TestNrSocket *test_socket = static_cast(test_sock_v); + r_log(LOG_GENERIC, LOG_INFO, + "TestNrSocket %s firing %s callback", + test_socket->my_addr().as_string, + how == NR_ASYNC_WAIT_READ ? "readable" : "writeable"); + + + test_socket->fire_callback(how); +} + +bool TestNrSocket::is_tcp_connection_behind_nat() const { + return my_addr_.protocol == IPPROTO_TCP && port_mappings_.empty(); +} + +TestNrSocket::PortMapping* TestNrSocket::get_port_mapping( + const nr_transport_addr &remote_address, + TestNat::NatBehavior filter) const { + int compare_flags; + switch (filter) { + case TestNat::ENDPOINT_INDEPENDENT: + compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL; + break; + case TestNat::ADDRESS_DEPENDENT: + compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ADDR; + break; + case TestNat::PORT_DEPENDENT: + compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ALL; + break; + } + + for (PortMapping *port_mapping : port_mappings_) { + // TODO(bug 1170299): Remove const_cast when no longer necessary + if (!nr_transport_addr_cmp(const_cast(&remote_address), + &port_mapping->remote_address_, + compare_flags)) + return port_mapping; + } + return nullptr; +} + +TestNrSocket::PortMapping* TestNrSocket::create_port_mapping( + const nr_transport_addr &remote_address, + const nsRefPtr &external_socket) const { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s creating port mapping %s -> %s", + my_addr_.as_string, + external_socket->my_addr().as_string, + remote_address.as_string); + + return new PortMapping(remote_address, external_socket); +} + +TestNrSocket::PortMapping::PortMapping( + const nr_transport_addr &remote_address, + const nsRefPtr &external_socket) : + external_socket_(external_socket) { + // TODO(bug 1170299): Remove const_cast when no longer necessary + nr_transport_addr_copy(&remote_address_, + const_cast(&remote_address)); +} + +int TestNrSocket::PortMapping::send_from_queue() { + MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP); + int r = 0; + + while (!send_queue_.empty()) { + UdpPacket &packet = *send_queue_.front(); + r_log(LOG_GENERIC, LOG_INFO, + "PortMapping %s -> %s sending from queue to %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + packet.remote_address_.as_string); + + r = external_socket_->sendto(packet.buffer_->data(), + packet.buffer_->len(), + 0, + &packet.remote_address_); + + if (r) { + if (r != R_WOULDBLOCK) { + r_log(LOG_GENERIC, LOG_ERR, "%s: Fatal error %d, stop trying", + __FUNCTION__, r); + send_queue_.clear(); + } else { + r_log(LOG_GENERIC, LOG_INFO, "Would block, will retry later"); + } + break; + } + + send_queue_.pop_front(); + } + + return r; +} + +int TestNrSocket::PortMapping::sendto(const void *msg, + size_t len, + const nr_transport_addr &to) { + MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP); + r_log(LOG_GENERIC, LOG_INFO, + "PortMapping %s -> %s sending to %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + to.as_string); + + last_used_ = PR_IntervalNow(); + int r = external_socket_->sendto(msg, len, 0, + // TODO(bug 1170299): Remove const_cast when no longer necessary + const_cast(&to)); + + if (r == R_WOULDBLOCK) { + r_log(LOG_GENERIC, LOG_INFO, "Enqueueing UDP packet to %s", to.as_string); + send_queue_.push_back(nsRefPtr(new UdpPacket(msg, len, to))); + return 0; + } else if (r) { + r_log(LOG_GENERIC,LOG_ERR, "Error: %d", r); + } + + return r; +} + +int TestNrSocket::PortMapping::async_wait(int how, NR_async_cb cb, void *cb_arg, + char *function, int line) { + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s waiting for %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + return external_socket_->async_wait(how, cb, cb_arg, function, line); +} + +int TestNrSocket::PortMapping::cancel(int how) { + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s stop waiting for %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + return external_socket_->cancel(how); +} +} // namespace mozilla + diff --git a/media/mtransport/test_nr_socket.h b/media/mtransport/test_nr_socket.h new file mode 100644 index 000000000000..8fd2f148aa07 --- /dev/null +++ b/media/mtransport/test_nr_socket.h @@ -0,0 +1,283 @@ +/* -*- 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/. */ +/* +*/ + +/* +Based partially on original code from nICEr and nrappkit. + +nICEr copyright: + +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +nrappkit copyright: + + Copyright (C) 2001-2003, Network Resonance, Inc. + Copyright (C) 2006, Network Resonance, Inc. + All Rights Reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Network Resonance, Inc. nor the name of any + contributors to this software may be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + ekr@rtfm.com Thu Dec 20 20:14:49 2001 +*/ + +// Original author: bcampen@mozilla.com [:bwc] + +#ifndef test_nr_socket__ +#define test_nr_socket__ + +#include "nr_socket_prsock.h" + +extern "C" { +#include "nr_socket.h" +} + +#include +#include +#include +#include + +#include "mozilla/UniquePtr.h" +#include "prinrval.h" + +namespace mozilla { + +class TestNrSocket; + +/** + * A group of TestNrSockets that behave as if they were behind the same NAT. + * @note We deliberately avoid addref/release of TestNrSocket here to avoid + * masking lifetime errors elsewhere. +*/ +class TestNat { + public: + typedef enum { + /** For mapping, one port is used for all destinations. + * For filtering, allow any external address/port. */ + ENDPOINT_INDEPENDENT, + + /** For mapping, one port for each destination address (for any port). + * For filtering, allow incoming traffic from addresses that outgoing + * traffic has been sent to. */ + ADDRESS_DEPENDENT, + + /** For mapping, one port for each destination address/port. + * For filtering, allow incoming traffic only from addresses/ports that + * outgoing traffic has been sent to. */ + PORT_DEPENDENT, + } NatBehavior; + + TestNat() : + enabled_(false), + filtering_type_(ENDPOINT_INDEPENDENT), + mapping_type_(ENDPOINT_INDEPENDENT), + mapping_timeout_(30000), + allow_hairpinning_(false), + refresh_on_ingress_(false), + block_udp_(false), + sockets_() {} + + bool has_port_mappings() const; + + // Helps determine whether we're hairpinning + bool is_my_external_tuple(const nr_transport_addr &addr) const; + bool is_an_internal_tuple(const nr_transport_addr &addr) const; + + int create_socket_factory(nr_socket_factory **factorypp); + + void insert_socket(TestNrSocket *socket) { + sockets_.insert(socket); + } + + void erase_socket(TestNrSocket *socket) { + sockets_.erase(socket); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestNat); + + bool enabled_; + TestNat::NatBehavior filtering_type_; + TestNat::NatBehavior mapping_type_; + uint32_t mapping_timeout_; + bool allow_hairpinning_; + bool refresh_on_ingress_; + bool block_udp_; + + private: + std::set sockets_; + + ~TestNat(){} +}; + +/** + * Subclass of NrSocket that can simulate things like being behind a NAT, packet + * loss, latency, packet rewriting, etc. Also exposes some stuff that assists in + * diagnostics. + */ +class TestNrSocket : public NrSocket { + public: + explicit TestNrSocket(TestNat *nat); + + virtual ~TestNrSocket(); + + bool has_port_mappings() const; + bool is_my_external_tuple(const nr_transport_addr &addr) const; + + // Overrides of NrSocket + int sendto(const void *msg, size_t len, + int flags, nr_transport_addr *to) override; + int recvfrom(void * buf, size_t maxlen, + size_t *len, int flags, + nr_transport_addr *from) override; + int connect(nr_transport_addr *addr) override; + int write(const void *msg, size_t len, size_t *written) override; + int read(void *buf, size_t maxlen, size_t *len) override; + + int async_wait(int how, NR_async_cb cb, void *cb_arg, + char *function, int line) override; + int cancel(int how) override; + + private: + class UdpPacket { + public: + UdpPacket(const void *msg, size_t len, const nr_transport_addr &addr) : + buffer_(new DataBuffer(static_cast(msg), len)) { + // TODO(bug 1170299): Remove const_cast when no longer necessary + nr_transport_addr_copy(&remote_address_, + const_cast(&addr)); + } + + nr_transport_addr remote_address_; + UniquePtr buffer_; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UdpPacket); + private: + ~UdpPacket(){} + }; + + class PortMapping { + public: + PortMapping(const nr_transport_addr &remote_address, + const nsRefPtr &external_socket); + + int sendto(const void *msg, size_t len, const nr_transport_addr &to); + int async_wait(int how, NR_async_cb cb, void *cb_arg, + char *function, int line); + int cancel(int how); + int send_from_queue(); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PortMapping); + + PRIntervalTime last_used_; + nsRefPtr external_socket_; + // For non-symmetric, most of the data here doesn't matter + nr_transport_addr remote_address_; + + private: + ~PortMapping(){} + + // If external_socket_ returns E_WOULDBLOCK, we don't want to propagate + // that to the code using the TestNrSocket. We can also perhaps use this + // to help simulate things like latency. + std::list> send_queue_; + }; + + bool is_port_mapping_stale(const PortMapping &port_mapping) const; + bool allow_ingress(const nr_transport_addr &from, + PortMapping **port_mapping_used) const; + void destroy_stale_port_mappings(); + + static void port_mapping_readable_callback(void *ext_sock_v, + int how, + void *test_sock_v); + void on_port_mapping_readable(NrSocket *external_socket); + void fire_readable_callback(); + + static void port_mapping_tcp_passthrough_callback(void *ext_sock_v, + int how, + void *test_sock_v); + void cancel_port_mapping_async_wait(int how); + + static void port_mapping_writeable_callback(void *ext_sock_v, + int how, + void *test_sock_v); + void write_to_port_mapping(NrSocket *external_socket); + bool is_tcp_connection_behind_nat() const; + + PortMapping* get_port_mapping(const nr_transport_addr &remote_addr, + TestNat::NatBehavior filter) const; + PortMapping* create_port_mapping( + const nr_transport_addr &remote_addr, + const nsRefPtr &external_socket) const; + nsRefPtr create_external_socket( + const nr_transport_addr &remote_addr) const; + + nsRefPtr readable_socket_; + nsRefPtr nat_; + // Since our comparison logic is different depending on what kind of NAT + // we simulate, and the STL does not make it very easy to switch out the + // comparison function at runtime, and these lists are going to be very + // small anyway, we just brute-force it. + std::list> port_mappings_; +}; + +} // namespace mozilla + +#endif // test_nr_socket__ + diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_component.c b/media/mtransport/third_party/nICEr/src/ice/ice_component.c index e144793f4439..62a6ab33cc0c 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c @@ -196,7 +196,7 @@ static int nr_ice_component_initialize_udp(struct nr_ice_ctx_ *ctx,nr_ice_compon continue; } r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): host address %s",ctx->label,addrs[i].addr.as_string); - if(r=nr_socket_local_create(&addrs[i].addr,&sock)){ + if((r=nr_socket_factory_create_socket(ctx->socket_factory,&addrs[i].addr,&sock))){ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): couldn't create socket for address %s",ctx->label,addrs[i].addr.as_string); continue; } @@ -323,7 +323,7 @@ static int nr_ice_component_initialize_tcp(struct nr_ice_ctx_ *ctx,nr_ice_compon addr.protocol = IPPROTO_TCP; if ((r=nr_transport_addr_fmt_addr_string(&addr))) ABORT(r); - if((r=nr_socket_local_create(&addr, &sock))){ + if((r=nr_socket_factory_create_socket(ctx->socket_factory,&addr,&sock))){ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): couldn't create socket for address %s",ctx->label,addr.as_string); continue; } diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c index 8eb7da6f814a..d866fb1231f9 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c @@ -54,6 +54,7 @@ static char *RCSSTRING __UNUSED__="$Id: ice_ctx.c,v 1.2 2008/04/28 17:59:01 ekr #include "nr_crypto.h" #include "async_timer.h" #include "util.h" +#include "nr_socket_local.h" int LOG_ICE = 0; @@ -65,6 +66,14 @@ static int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out); #endif /* USE_TURN */ static void nr_ice_ctx_destroy_cb(NR_SOCKET s, int how, void *cb_arg); static int nr_ice_ctx_pair_new_trickle_candidates(nr_ice_ctx *ctx, nr_ice_candidate *cand); +static int no_op(void **obj) { + return 0; +} + +static nr_socket_factory_vtbl default_socket_factory_vtbl = { + nr_socket_local_create, + no_op +}; int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out) { @@ -229,6 +238,12 @@ int nr_ice_ctx_set_turn_tcp_socket_wrapper(nr_ice_ctx *ctx, nr_socket_wrapper_fa return(_status); } +void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory) + { + nr_socket_factory_destroy(&ctx->socket_factory); + ctx->socket_factory = factory; + } + #ifdef USE_TURN int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out) { @@ -381,6 +396,9 @@ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) ctx->Ta = 20; + if (r=nr_socket_factory_create_int(NULL, &default_socket_factory_vtbl, &ctx->socket_factory)) + ABORT(r); + STAILQ_INIT(&ctx->streams); STAILQ_INIT(&ctx->sockets); STAILQ_INIT(&ctx->foundations); @@ -439,6 +457,7 @@ static void nr_ice_ctx_destroy_cb(NR_SOCKET s, int how, void *cb_arg) nr_resolver_destroy(&ctx->resolver); nr_interface_prioritizer_destroy(&ctx->interface_prioritizer); nr_socket_wrapper_factory_destroy(&ctx->turn_tcp_socket_wrapper); + nr_socket_factory_destroy(&ctx->socket_factory); RFREE(ctx); } diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h index d5cbf2623705..2ef2d653f06b 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h @@ -130,6 +130,7 @@ struct nr_ice_ctx_ { nr_resolver *resolver; /* The resolver to use */ nr_interface_prioritizer *interface_prioritizer; /* Priority decision logic */ nr_socket_wrapper_factory *turn_tcp_socket_wrapper; /* The TURN TCP socket wrapper to use */ + nr_socket_factory *socket_factory; nr_ice_foundation_head foundations; @@ -173,6 +174,7 @@ int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver); int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *prioritizer); int nr_ice_ctx_set_turn_tcp_socket_wrapper(nr_ice_ctx *ctx, nr_socket_wrapper_factory *wrapper); +void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory); int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg); #define NR_ICE_MAX_ATTRIBUTE_SIZE 256 diff --git a/media/mtransport/third_party/nICEr/src/net/nr_socket.c b/media/mtransport/third_party/nICEr/src/net/nr_socket.c index 9c91a4df1909..e158e46e7945 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_socket.c +++ b/media/mtransport/third_party/nICEr/src/net/nr_socket.c @@ -135,3 +135,43 @@ int nr_socket_read(nr_socket *sock,void * restrict buf, size_t maxlen, CHECK_DEFINED(sread); return sock->vtbl->sread(sock->obj, buf, maxlen, len); } + + +int nr_socket_factory_create_int(void *obj, + nr_socket_factory_vtbl *vtbl, nr_socket_factory **factorypp) + { + int _status; + nr_socket_factory *factoryp=0; + + if(!(factoryp=RCALLOC(sizeof(nr_socket_factory)))) + ABORT(R_NO_MEMORY); + + factoryp->obj = obj; + factoryp->vtbl = vtbl; + + *factorypp = factoryp; + + _status=0; + abort: + return(_status); + } + +int nr_socket_factory_destroy(nr_socket_factory **factorypp) + { + nr_socket_factory *factoryp; + + if (!factorypp || !*factorypp) + return (0); + + factoryp = *factorypp; + *factorypp = NULL; + factoryp->vtbl->destroy(&factoryp->obj); + RFREE(factoryp); + return (0); + } + +int nr_socket_factory_create_socket(nr_socket_factory *factory, nr_transport_addr *addr, nr_socket **sockp) + { + return factory->vtbl->create_socket(factory->obj, addr, sockp); + } + diff --git a/media/mtransport/third_party/nICEr/src/net/nr_socket.h b/media/mtransport/third_party/nICEr/src/net/nr_socket.h index ee87f60599d1..71c6c418795c 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_socket.h +++ b/media/mtransport/third_party/nICEr/src/net/nr_socket.h @@ -72,6 +72,15 @@ typedef struct nr_socket_ { nr_socket_vtbl *vtbl; } nr_socket; +typedef struct nr_socket_factory_vtbl_ { + int (*create_socket)(void *obj, nr_transport_addr *addr, nr_socket **sockp); + int (*destroy)(void **obj); +} nr_socket_factory_vtbl; + +typedef struct nr_socket_factory_ { + void *obj; + nr_socket_factory_vtbl *vtbl; +} nr_socket_factory; /* To be called by constructors */ int nr_socket_create_int(void *obj, nr_socket_vtbl *vtbl, nr_socket **sockp); @@ -87,5 +96,9 @@ int nr_socket_connect(nr_socket *sock, nr_transport_addr *addr); int nr_socket_write(nr_socket *sock,const void *msg, size_t len, size_t *written, int flags); int nr_socket_read(nr_socket *sock, void * restrict buf, size_t maxlen, size_t *len, int flags); +int nr_socket_factory_create_int(void *obj, nr_socket_factory_vtbl *vtbl, nr_socket_factory **factorypp); +int nr_socket_factory_destroy(nr_socket_factory **factoryp); +int nr_socket_factory_create_socket(nr_socket_factory *factory, nr_transport_addr *addr, nr_socket **sockp); + #endif diff --git a/media/mtransport/third_party/nICEr/src/net/nr_socket_local.h b/media/mtransport/third_party/nICEr/src/net/nr_socket_local.h index b59953076796..a2f813ff668d 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_socket_local.h +++ b/media/mtransport/third_party/nICEr/src/net/nr_socket_local.h @@ -35,7 +35,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef _nr_socket_local_h #define _nr_socket_local_h -int nr_socket_local_create(nr_transport_addr *addr, nr_socket **sockp); +int nr_socket_local_create(void *obj, nr_transport_addr *addr, nr_socket **sockp); #endif diff --git a/testing/cppunittest.ini b/testing/cppunittest.ini index ab885dc9be0b..e4aaa9987244 100644 --- a/testing/cppunittest.ini +++ b/testing/cppunittest.ini @@ -87,6 +87,7 @@ skip-if = os == 'b2g' #Bug 919595 [TestWebGLElementArrayCache] [buffered_stun_socket_unittest] [ice_unittest] +[test_nr_socket_unittest] [jsapi-tests] skip-if = os == 'b2g' #Bug 1068946 [mediaconduit_unittests]