Bug 1035468: A NAT simulator based on NrSocket, and integrate into ice_unittest. r=ekr
authorByron Campen [:bwc] <docfaraday@gmail.com>
Wed, 10 Jun 2015 15:27:12 -0700
changeset 248190 a3afb20a8fd29b7efef8f23cfe09f0ed0f6a656e
parent 248189 f88e60f3954c5dc0729a4a5e4f16c382726c5e2c
child 248191 bc2394237fe534aa246adf32b7477994abe13acc
push id28893
push userkwierso@gmail.com
push dateFri, 12 Jun 2015 00:02:58 +0000
treeherdermozilla-central@8cf9d3e497f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersekr
bugs1035468
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1035468: A NAT simulator based on NrSocket, and integrate into ice_unittest. r=ekr
media/mtransport/common.build
media/mtransport/nr_socket_prsock.cpp
media/mtransport/nr_socket_prsock.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/moz.build
media/mtransport/test/stunserver.cpp
media/mtransport/test/test_nr_socket_unittest.cpp
media/mtransport/test/turn_unittest.cpp
media/mtransport/test_nr_socket.cpp
media/mtransport/test_nr_socket.h
media/mtransport/third_party/nICEr/src/ice/ice_component.c
media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
media/mtransport/third_party/nICEr/src/net/nr_socket.c
media/mtransport/third_party/nICEr/src/net/nr_socket.h
media/mtransport/third_party/nICEr/src/net/nr_socket_local.h
testing/cppunittest.ini
--- a/media/mtransport/common.build
+++ b/media/mtransport/common.build
@@ -11,16 +11,17 @@ mtransport_lcppsrcs = [
     'nricectx.cpp',
     'nricemediastream.cpp',
     'nriceresolver.cpp',
     'nriceresolverfake.cpp',
     'nrinterfaceprioritizer.cpp',
     'rlogringbuffer.cpp',
     'simpletokenbucket.cpp',
     'stun_udp_socket_filter.cpp',
+    'test_nr_socket.cpp',
     'transportflow.cpp',
     'transportlayer.cpp',
     'transportlayerdtls.cpp',
     'transportlayerice.cpp',
     'transportlayerlog.cpp',
     'transportlayerloopback.cpp',
     'transportlayerprsock.cpp',
 ]
--- a/media/mtransport/nr_socket_prsock.cpp
+++ b/media/mtransport/nr_socket_prsock.cpp
@@ -648,17 +648,17 @@ int NrSocket::sendto(const void *msg, si
   }
 
   // TODO: Convert flags?
   status = PR_SendTo(fd_, msg, len, flags, &naddr, PR_INTERVAL_NO_WAIT);
   if (status < 0 || (size_t)status != 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);
   }
 
   _status=0;
 abort:
   return(_status);
 }
 
@@ -669,17 +669,17 @@ int NrSocket::recvfrom(void * buf, size_
   int r,_status;
   PRNetAddr nfrom;
   int32_t status;
 
   status = PR_RecvFrom(fd_, buf, maxlen, flags, &nfrom, PR_INTERVAL_NO_WAIT);
   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;
 
   if((r=nr_praddr_to_transport_addr(&nfrom,from,my_addr_.protocol,0)))
     ABORT(r);
 
   //r_log(LOG_GENERIC,LOG_DEBUG,"Read %d bytes from %s",*len,addr->as_string);
@@ -1304,17 +1304,17 @@ static nr_socket_vtbl nr_socket_local_vt
   nr_socket_local_getfd,
   nr_socket_local_getaddr,
   nr_socket_local_connect,
   nr_socket_local_write,
   nr_socket_local_read,
   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<NrSocketBase> sock;
 
   // create IPC bridge for content process
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     sock = new NrSocket();
   } else {
     sock = new NrSocketIpc();
   }
--- a/media/mtransport/nr_socket_prsock.h
+++ b/media/mtransport/nr_socket_prsock.h
@@ -111,16 +111,19 @@ public:
   uint32_t poll_flags() {
     return poll_flags_;
   }
 
   virtual nr_socket_vtbl *vtbl();  // To access in test classes.
 
   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);
 
   bool connect_invoked_;
   nr_transport_addr my_addr_;
 
 private:
@@ -158,17 +161,17 @@ public:
                        size_t *len, int flags,
                        nr_transport_addr *from) override;
   virtual int getaddr(nr_transport_addr *addrp) override;
   virtual void close() override;
   virtual int connect(nr_transport_addr *addr) override;
   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_);
   }
 
   DISALLOW_COPY_ASSIGN(NrSocket);
 
   PRFileDesc *fd_;
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -30,16 +30,19 @@
 #include "nriceresolverfake.h"
 #include "nriceresolver.h"
 #include "nrinterfaceprioritizer.h"
 #include "mtransport_test_utils.h"
 #include "gtest_ringbuffer_dumper.h"
 #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"
 #include "stun_udp_socket_filter.h"
 #include "mozilla/net/DNS.h"
 
 #include "ice_ctx.h"
 #include "ice_peer_ctx.h"
@@ -245,23 +248,31 @@ class IceTestPeer : public sigslot::has_
       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),
       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() {
     test_utils->sts_target()->Dispatch(WrapRunnable(this,
                                                     &IceTestPeer::Shutdown),
         NS_DISPATCH_SYNC);
 
     // Give the ICE destruction callback time to fire before
@@ -312,16 +323,21 @@ class IceTestPeer : public sigslot::has_
 
     std::vector<NrIceStunServer> stun_servers;
     ScopedDeletePtr<NrIceStunServer> server(NrIceStunServer::Create(addr,
                                                                     port));
     stun_servers.push_back(*server);
     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,
                      const char* transport) {
     std::vector<unsigned char> password_vec(password.begin(), password.end());
     SetTurnServer(addr, port, username, password_vec, transport);
   }
 
@@ -366,16 +382,35 @@ class IceTestPeer : public sigslot::has_
 
     test_utils->sts_target()->Dispatch(
         WrapRunnableRet(ice_ctx_, &NrIceCtx::StartGathering, &res),
         NS_DISPATCH_SYNC);
 
     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<std::string> GetGlobalAttributes() {
     std::vector<std::string> attrs(ice_ctx_->GetGlobalAttributes());
     if (simulate_ice_lite_) {
       attrs.push_back("ice-lite");
     }
     return attrs;
   }
@@ -1003,16 +1038,17 @@ class IceTestPeer : public sigslot::has_
   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_;
   int trickled_;
   bool simulate_ice_lite_;
+  nsRefPtr<mozilla::TestNat> nat_;
 };
 
 void SchedulableTrickleCandidate::Trickle() {
   timer_handle_ = nullptr;
   nsresult res = peer_->TrickleCandidate_s(candidate_, stream_);
   ASSERT_TRUE(NS_SUCCEEDED(res));
 }
 
@@ -1060,16 +1096,22 @@ class IceGatherTest : public ::testing::
                                      uint16_t fake_port) {
     EnsurePeer();
     TestStunServer::GetInstance()->SetResponseAddr(fake_addr, fake_port);
     // Sets an additional stun server
     peer_->SetStunServer(TestStunServer::GetInstance()->addr(),
                          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,
                                   const std::string& match) {
     std::vector<std::string> candidates = peer_->GetCandidates(stream);
     for (size_t c = 0; c < candidates.size(); ++c) {
       if (std::string::npos != candidates[c].find(match)) {
         return true;
@@ -1079,17 +1121,22 @@ class IceGatherTest : public ::testing::
   }
 
  protected:
   mozilla::ScopedDeletePtr<IceTestPeer> peer_;
 };
 
 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;
     target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
     ASSERT_TRUE(NS_SUCCEEDED(rv));
   }
 
   void TearDown() {
@@ -1121,32 +1168,71 @@ class IceConnectTest : public ::testing:
       p1_ = new IceTestPeer("P1", true, set_priorities, allow_loopback);
       p2_ = new IceTestPeer("P2", false, set_priorities, allow_loopback);
     }
     initted_ = true;
   }
 
   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();
 
     if (waitTime) {
       EXPECT_TRUE_WAIT(p1_->gathering_complete(), waitTime);
       if (!p1_->gathering_complete())
         return false;
       EXPECT_TRUE_WAIT(p2_->gathering_complete(), waitTime);
       if (!p2_->gathering_complete())
         return false;
     }
     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,
                      const char* transport = kNrIceTransportUdp) {
     p1_->SetTurnServer(addr, port, username, password, transport);
     p2_->SetTurnServer(addr, port, username, password, transport);
   }
 
@@ -1272,16 +1358,20 @@ class IceConnectTest : public ::testing:
     ASSERT_TRUE_WAIT(p2_->received() == 1, 1000);
   }
 
  protected:
   bool initted_;
   nsCOMPtr<nsIEventTarget> target_;
   mozilla::ScopedDeletePtr<IceTestPeer> p1_;
   mozilla::ScopedDeletePtr<IceTestPeer> p2_;
+  bool use_nat_;
+  TestNat::NatBehavior filtering_type_;
+  TestNat::NatBehavior mapping_type_;
+  bool block_udp_;
 };
 
 class PrioritizerTest : public ::testing::Test {
  public:
   PrioritizerTest():
     prioritizer_(nullptr) {}
 
   ~PrioritizerTest() {
@@ -1638,16 +1728,157 @@ TEST_F(IceConnectTest, TestTrickleIceLit
   p1_->SimulateIceLite();
   ConnectTrickle();
   SimulateTrickle(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   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<NrIceTurnServer> turn_servers;
+  std::vector<unsigned char> 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<NrIceTurnServer> turn_servers;
+  std::vector<unsigned char> 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());
   Connect();
 }
 
 TEST_F(IceConnectTest, TestConnectTwoComponentsDisableSecond) {
   AddStream("first", 2);
--- a/media/mtransport/test/moz.build
+++ b/media/mtransport/test/moz.build
@@ -9,61 +9,74 @@ if CONFIG['OS_TARGET'] != 'WINNT' and CO
         'buffered_stun_socket_unittest',
         'ice_unittest',
         'nrappkit_unittest',
         'proxy_tunnel_socket_unittest',
         'rlogringbuffer_unittest',
         'runnable_utils_unittest',
         'simpletokenbucket_unittest',
         'sockettransportservice_unittest',
+        'test_nr_socket_unittest',
         'TestSyncRunnable',
         'transport_unittests',
         'turn_unittest',
     ])
 
     if CONFIG['MOZ_SCTP']:
         CppUnitTests([
             'sctp_unittest',
         ])
 
 FAIL_ON_WARNINGS = True
 
 for var in ('MOZILLA_INTERNAL_API', 'MOZILLA_XPCOMRT_API', 'MOZILLA_EXTERNAL_LINKAGE', 'HAVE_STRDUP', 'NR_SOCKET_IS_VOID_PTR', 'SCTP_DEBUG', 'INET'):
     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',
     ]
 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',
     ]
     USE_LIBS += [
         'nspr',
     ]
     OS_LIBS += [
         '-lrt',
     ]
 
 if CONFIG['OS_TARGET'] == 'Darwin':
     LOCAL_INCLUDES += [
         '/media/mtransport/third_party/nrappkit/src/port/darwin/include',
     ]
 
 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.
     DEFINES['__Userspace_os_%s' % CONFIG['OS_TARGET']] = 1
 
 if CONFIG['OS_TARGET'] in ('Darwin', 'Android'):
     DEFINES['GTEST_USE_OWN_TR1_TUPLE'] = 1
 
--- a/media/mtransport/test/stunserver.cpp
+++ b/media/mtransport/test/stunserver.cpp
@@ -216,17 +216,17 @@ int TestStunServer::TryOpenListenSocket(
       return R_INTERNAL;
     }
 
     if (nr_transport_addr_fmt_addr_string(&addr->addr)) {
       MOZ_MTLOG(ML_ERROR, "Couldn't re-set addr string");
       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;
     }
 
     return 0;
 }
 
 TestStunServer* TestStunServer::Create() {
new file mode 100644
--- /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 <vector>
+
+#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<TestNrSocket> 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<TestNrSocket> 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<TestNat> CreatePrivateAddrs(size_t size,
+                                       const char* ip_str = "127.0.0.1") {
+    nsRefPtr<TestNat> result;
+    sts_->Dispatch(
+        WrapRunnableRet(this,
+                        &TestNrSocketTest::CreatePrivateAddrs_s,
+                        size,
+                        ip_str,
+                        &result),
+        NS_DISPATCH_SYNC);
+    return result;
+  }
+
+  nsRefPtr<TestNat> CreatePrivateAddrs_s(size_t count, const char* ip_str) {
+    nsRefPtr<TestNat> 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<nr_transport_addr*>(&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<TestNrSocketTest*>(test_fixture);
+    test->wait_done_for_main_ = true;
+  }
+
+  // Simple busywait boolean for the test cases to spin on.
+  Atomic<bool> wait_done_for_main_;
+
+  nsCOMPtr<nsIEventTarget> sts_;
+  std::vector<nsRefPtr<TestNrSocket>> public_addrs_;
+  std::vector<nsRefPtr<TestNrSocket>> private_addrs_;
+  std::vector<nsRefPtr<TestNat>> 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<TestNat> 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<TestNat> 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<TestNat> nat1(CreatePrivateAddrs(1));
+  nat1->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+  nat1->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+  nsRefPtr<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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<TestNat> 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;
+}
--- a/media/mtransport/test/turn_unittest.cpp
+++ b/media/mtransport/test/turn_unittest.cpp
@@ -114,17 +114,17 @@ class TurnClient : public ::testing::Tes
   }
 
   void Init_s() {
     int r;
     nr_transport_addr addr;
     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) {
       int r =
           nr_socket_buffered_stun_create(real_socket_, 100000,
                                          &buffered_socket_);
       ASSERT_EQ(0, r);
       net_socket_ = buffered_socket_;
new file mode 100644
--- /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<NrSocketBase> sock = new TestNrSocket(static_cast<TestNat*>(obj));
+
+  int r, _status;
+
+  r = sock->create(addr);
+  if (r)
+    ABORT(r);
+
+  r = nr_socket_create_int(static_cast<void *>(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<TestNat*>(*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<nr_transport_addr*>(&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<NrSocket> 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<nr_transport_addr*>(&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<NrSocket> 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<NrSocket> 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<NrSocket> 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<nr_transport_addr*>(&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<TestNrSocket*>(test_sock_v);
+  NrSocket *external_socket = static_cast<NrSocket*>(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<TestNrSocket*>(test_sock_v);
+  NrSocket *external_socket = static_cast<NrSocket*>(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<TestNrSocket*>(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<nr_transport_addr*>(&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<NrSocket> &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<NrSocket> &external_socket) :
+  external_socket_(external_socket) {
+  // TODO(bug 1170299): Remove const_cast when no longer necessary
+  nr_transport_addr_copy(&remote_address_,
+                         const_cast<nr_transport_addr*>(&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<nr_transport_addr*>(&to));
+
+  if (r == R_WOULDBLOCK) {
+    r_log(LOG_GENERIC, LOG_INFO, "Enqueueing UDP packet to %s", to.as_string);
+    send_queue_.push_back(nsRefPtr<UdpPacket>(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
+
new file mode 100644
--- /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 <set>
+#include <vector>
+#include <map>
+#include <list>
+
+#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<TestNrSocket*> 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<const uint8_t*>(msg), len)) {
+          // TODO(bug 1170299): Remove const_cast when no longer necessary
+          nr_transport_addr_copy(&remote_address_,
+                                 const_cast<nr_transport_addr*>(&addr));
+        }
+
+        nr_transport_addr remote_address_;
+        UniquePtr<DataBuffer> buffer_;
+
+        NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UdpPacket);
+      private:
+        ~UdpPacket(){}
+    };
+
+    class PortMapping {
+      public:
+        PortMapping(const nr_transport_addr &remote_address,
+                    const nsRefPtr<NrSocket> &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<NrSocket> 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<nsRefPtr<UdpPacket>> 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<NrSocket> &external_socket) const;
+    nsRefPtr<NrSocket> create_external_socket(
+        const nr_transport_addr &remote_addr) const;
+
+    nsRefPtr<NrSocket> readable_socket_;
+    nsRefPtr<TestNat> 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<nsRefPtr<PortMapping>> port_mappings_;
+};
+
+} // namespace mozilla
+
+#endif // test_nr_socket__
+
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -191,17 +191,17 @@ static int nr_ice_component_initialize_u
         if(r!=R_NOT_FOUND)
           ABORT(r);
       }
       else{
         if(suppress)
           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;
       }
 
       if(r=nr_ice_socket_create(ctx,component,sock,&isock))
         ABORT(r);
       /* Create one host candidate */
       if(r=nr_ice_candidate_create(ctx,component,isock,sock,HOST,0,
@@ -318,17 +318,17 @@ static int nr_ice_component_initialize_t
           continue;
 
         /* Create a local socket */
         if ((r=nr_transport_addr_copy(&addr, &addrs[i].addr)))
           ABORT(r);
         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;
         }
 
         r_log(LOG_ICE,LOG_DEBUG,"nr_ice_component_initialize_tcp create");
 
         if (ctx->turn_tcp_socket_wrapper) {
           /* Wrap it */
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -49,27 +49,36 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include <nr_api.h>
 #include <registry.h>
 #include "stun.h"
 #include "ice_ctx.h"
 #include "ice_reg.h"
 #include "nr_crypto.h"
 #include "async_timer.h"
 #include "util.h"
+#include "nr_socket_local.h"
 
 
 int LOG_ICE = 0;
 
 static int nr_ice_random_string(char *str, int len);
 static int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out);
 #ifdef USE_TURN
 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)
   {
     int r,_status;
     nr_ice_stun_server *servers = 0;
     int i;
     NR_registry child;
     char *addr=0;
@@ -224,16 +233,22 @@ int nr_ice_ctx_set_turn_tcp_socket_wrapp
 
     ctx->turn_tcp_socket_wrapper = wrapper;
 
     _status=0;
    abort:
     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)
   {
     int r,_status;
     nr_ice_turn_server *servers = 0;
     int i;
     NR_registry child;
     char *addr=0;
@@ -376,16 +391,19 @@ int nr_ice_ctx_create(char *label, UINT4
         ABORT(r);
       }
     }
 #endif /* USE_TURN */
 
 
     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);
     STAILQ_INIT(&ctx->peers);
     STAILQ_INIT(&ctx->ids);
 
     *ctxp=ctx;
 
@@ -434,16 +452,17 @@ static void nr_ice_ctx_destroy_cb(NR_SOC
     STAILQ_FOREACH_SAFE(id1, &ctx->ids, entry, id2){
       STAILQ_REMOVE(&ctx->ids,id1,nr_ice_stun_id_,entry);
       RFREE(id1);
     }
 
     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);
   }
 
 int nr_ice_ctx_destroy(nr_ice_ctx **ctxp)
   {
     if(!ctxp || !*ctxp)
       return(0);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -125,16 +125,17 @@ struct nr_ice_ctx_ {
   nr_ice_turn_server *turn_servers;           /* The list of turn servers */
   int turn_server_ct;
   nr_local_addr *local_addrs;                 /* The list of available local addresses and corresponding interface information */
   int local_addr_ct;
 
   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;
 
   nr_ice_media_stream_head streams;           /* Media streams */
   int stream_ct;
   nr_ice_socket_head sockets;                 /* The sockets we're using */
   int uninitialized_candidates;
 
@@ -168,16 +169,17 @@ int nr_ice_ctx_deliver_packet(nr_ice_ctx
 int nr_ice_ctx_is_known_id(nr_ice_ctx *ctx, UCHAR id[12]);
 int nr_ice_ctx_remember_id(nr_ice_ctx *ctx, nr_stun_message *msg);
 int nr_ice_ctx_finalize(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx);
 int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers, int ct);
 int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int ct);
 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
 
 extern int LOG_ICE;
 
 #ifdef __cplusplus
 }
--- a/media/mtransport/third_party/nICEr/src/net/nr_socket.c
+++ b/media/mtransport/third_party/nICEr/src/net/nr_socket.c
@@ -130,8 +130,48 @@ int nr_socket_write(nr_socket *sock,cons
 
 
 int nr_socket_read(nr_socket *sock,void * restrict buf, size_t maxlen,
   size_t *len, int flags)
   {
     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);
+  }
+
--- a/media/mtransport/third_party/nICEr/src/net/nr_socket.h
+++ b/media/mtransport/third_party/nICEr/src/net/nr_socket.h
@@ -67,25 +67,38 @@ typedef struct nr_socket_vtbl_ {
   int (*close)(void *obj);
 } nr_socket_vtbl;
 
 typedef struct nr_socket_ {
   void *obj;
   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);
 int nr_socket_destroy(nr_socket **sockp);
 int nr_socket_sendto(nr_socket *sock,const void *msg, size_t len,
   int flags,nr_transport_addr *addr);
 int nr_socket_recvfrom(nr_socket *sock,void * restrict buf, size_t maxlen,
   size_t *len, int flags, nr_transport_addr *addr);
 int nr_socket_getfd(nr_socket *sock, NR_SOCKET *fd);
 int nr_socket_getaddr(nr_socket *sock, nr_transport_addr *addrp);
 int nr_socket_close(nr_socket *sock);
 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
 
--- a/media/mtransport/third_party/nICEr/src/net/nr_socket_local.h
+++ b/media/mtransport/third_party/nICEr/src/net/nr_socket_local.h
@@ -30,12 +30,12 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT
 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
 
--- a/testing/cppunittest.ini
+++ b/testing/cppunittest.ini
@@ -82,16 +82,17 @@ skip-if = os == 'b2g'  #Bug 919595
 [TestTypedEnum]
 [TestUDPSocket]
 [TestUniquePtr]
 [TestVolatileBuffer]
 [TestWeakPtr]
 [TestWebGLElementArrayCache]
 [buffered_stun_socket_unittest]
 [ice_unittest]
+[test_nr_socket_unittest]
 [jsapi-tests]
 skip-if = os == 'b2g'  #Bug 1068946
 [mediaconduit_unittests]
 [mediapipeline_unittest]
 skip-if = os == 'b2g' || os == 'android'  # Bug 919646
 [mediapipeline_unittest_standalone]
 skip-if = os == 'b2g' || os == 'android'  # Bug 919646
 [nrappkit_unittest]