Bug 1027350 - Allow loopback addresses for ICE. r=bwc
authorEKR <ekr@rtfm.com>
Sun, 22 Jun 2014 15:11:48 -0700
changeset 208976 e31ae14f7c5c6b2266839e654735e25b22373334
parent 208975 8d3d8d907e4e26513c402cbc6379c999328f75df
child 208977 ccf50b47021dcb2492b49e217817f036b2f51ba8
push id50057
push userryanvm@gmail.com
push dateMon, 06 Oct 2014 16:36:32 +0000
treeherdermozilla-inbound@31d28b1d4d7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbwc
bugs1027350
milestone35.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 1027350 - Allow loopback addresses for ICE. r=bwc
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/turn_unittest.cpp
media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
media/mtransport/third_party/nICEr/src/stun/stun_reg.h
media/mtransport/third_party/nICEr/src/stun/stun_util.c
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
modules/libpref/init/all.js
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -70,16 +70,17 @@ extern "C" {
 #include "r_memory.h"
 #include "ice_reg.h"
 #include "ice_util.h"
 #include "transport_addr.h"
 #include "nr_crypto.h"
 #include "nr_socket.h"
 #include "nr_socket_local.h"
 #include "stun_client_ctx.h"
+#include "stun_reg.h"
 #include "stun_server_ctx.h"
 #include "ice_codeword.h"
 #include "ice_ctx.h"
 #include "ice_candidate.h"
 #include "ice_handler.h"
 }
 
 // Local includes
@@ -380,17 +381,18 @@ void NrIceCtx::trickle_cb(void *arg, nr_
   MOZ_MTLOG(ML_INFO, "NrIceCtx(" << ctx->name_ << "): trickling candidate "
             << candidate_str);
 
   s->SignalCandidate(s, candidate_str);
 }
 
 RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& name,
                                   bool offerer,
-                                  bool set_interface_priorities) {
+                                  bool set_interface_priorities,
+                                  bool allow_loopback) {
 
   RefPtr<NrIceCtx> ctx = new NrIceCtx(name, offerer);
 
   // Initialize the crypto callbacks and logging stuff
   if (!initialized) {
     NR_reg_init(NR_REG_MODE_LOCAL);
     RLogRingBuffer::CreateInstance();
     nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl;
@@ -428,16 +430,20 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
       NR_reg_set_uchar((char *)"ice.pref.interface.vmnet7", 235);
       NR_reg_set_uchar((char *)"ice.pref.interface.vmnet8", 234);
       NR_reg_set_uchar((char *)"ice.pref.interface.virbr0", 233);
       NR_reg_set_uchar((char *)"ice.pref.interface.wlan0", 232);
     }
 
     NR_reg_set_uint4((char *)"stun.client.maximum_transmits",7);
     NR_reg_set_uint4((char *)NR_ICE_REG_TRICKLE_GRACE_PERIOD, 5000);
+
+    if (allow_loopback) {
+      NR_reg_set_char((char *)NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, 1);
+    }
   }
 
   // Create the ICE context
   int r;
 
   UINT4 flags = offerer ? NR_ICE_CTX_FLAGS_OFFERER:
       NR_ICE_CTX_FLAGS_ANSWERER;
   flags |= NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION;
@@ -499,16 +505,25 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
   ctx->sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
 
   if (!NS_SUCCEEDED(rv))
     return nullptr;
 
   return ctx;
 }
 
+// ONLY USE THIS FOR TESTING. Will cause totally unpredictable and possibly very
+// bad effects if ICE is still live.
+void NrIceCtx::internal_DeinitializeGlobal() {
+  NR_reg_del((char *)"stun");
+  NR_reg_del((char *)"ice");
+  RLogRingBuffer::DestroyInstance();
+  nr_crypto_vtbl = nullptr;
+  initialized = false;
+}
 
 NrIceCtx::~NrIceCtx() {
   MOZ_MTLOG(ML_DEBUG, "Destroying ICE ctx '" << name_ <<"'");
   nr_ice_peer_ctx_destroy(&peer_);
   nr_ice_ctx_destroy(&ctx_);
   delete ice_handler_vtbl_;
   delete ice_handler_;
 }
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -185,17 +185,23 @@ class NrIceCtx {
   };
 
   enum Controlling { ICE_CONTROLLING,
                      ICE_CONTROLLED
   };
 
   static RefPtr<NrIceCtx> Create(const std::string& name,
                                  bool offerer,
-                                 bool set_interface_priorities = true);
+                                 bool set_interface_priorities = true,
+                                 bool allow_loopback = false);
+
+  // Deinitialize all ICE global state. Used only for testing.
+  static void internal_DeinitializeGlobal();
+
+
   nr_ice_ctx *ctx() { return ctx_; }
   nr_ice_peer_ctx *peer() { return peer_; }
 
   // Testing only.
   void destroy_peer_ctx();
 
   // Create a media stream
   RefPtr<NrIceMediaStream> CreateStream(const std::string& name,
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -82,16 +82,23 @@ typedef std::string (*CandidateFilter)(c
 
 static std::string IsRelayCandidate(const std::string& candidate) {
   if (candidate.find("typ relay") != std::string::npos) {
     return candidate;
   }
   return std::string();
 }
 
+static std::string IsLoopbackCandidate(const std::string& candidate) {
+  if (candidate.find("127.0.0.") != std::string::npos) {
+    return candidate;
+  }
+  return std::string();
+}
+
 static std::string SabotageHostCandidateAndDropReflexive(
     const std::string& candidate) {
   if (candidate.find("typ srflx") != std::string::npos) {
     return std::string();
   }
 
   if (candidate.find("typ host") != std::string::npos) {
     return kUnreachableHostIceCandidate;
@@ -213,19 +220,20 @@ class SchedulableTrickleCandidate {
     void *timer_handle_;
 
     DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate);
 };
 
 class IceTestPeer : public sigslot::has_slots<> {
  public:
 
-  IceTestPeer(const std::string& name, bool offerer, bool set_priorities) :
+  IceTestPeer(const std::string& name, bool offerer, bool set_priorities,
+              bool allow_loopback = false) :
       name_(name),
-      ice_ctx_(NrIceCtx::Create(name, offerer, set_priorities)),
+      ice_ctx_(NrIceCtx::Create(name, offerer, set_priorities, allow_loopback)),
       streams_(),
       candidates_(),
       gathering_complete_(false),
       ready_ct_(0),
       ice_complete_(false),
       ice_reached_checking_(false),
       received_(0),
       sent_(0),
@@ -269,16 +277,17 @@ class IceTestPeer : public sigslot::has_
     streams_.push_back(stream);
     stream->SignalCandidate.connect(this, &IceTestPeer::CandidateInitialized);
     stream->SignalReady.connect(this, &IceTestPeer::StreamReady);
     stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed);
     stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived);
   }
 
   void SetStunServer(const std::string addr, uint16_t port) {
+
     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 SetTurnServer(const std::string addr, uint16_t port,
@@ -383,16 +392,21 @@ class IceTestPeer : public sigslot::has_
   void SetExpectedTypes(NrIceCandidate::Type local,
                         NrIceCandidate::Type remote,
                         std::string local_transport = kNrIceTransportUdp) {
     expected_local_type_ = local;
     expected_local_transport_ = local_transport;
     expected_remote_type_ = remote;
   }
 
+  void SetExpectedCandidateAddr(const std::string& addr) {
+    expected_local_addr_ = addr;
+    expected_remote_addr_ = addr;
+  }
+
   bool gathering_complete() { return gathering_complete_; }
   int ready_ct() { return ready_ct_; }
   bool is_ready(size_t stream) {
     return streams_[stream]->state() == NrIceMediaStream::ICE_OPEN;
   }
   bool ice_complete() { return ice_complete_; }
   bool ice_reached_checking() { return ice_reached_checking_; }
   size_t received() { return received_; }
@@ -479,16 +493,27 @@ class IceTestPeer : public sigslot::has_
 
   nsresult TrickleCandidate_s(const std::string &candidate, size_t stream) {
     return streams_[stream]->ParseTrickleCandidate(candidate);
   }
 
   void DumpCandidate(std::string which, const NrIceCandidate& cand) {
     std::string type;
 
+    std::string addr;
+    int port;
+
+    if (which.find("Remote") != std::string::npos) {
+      addr = cand.cand_addr.host;
+      port = cand.cand_addr.port;
+    }
+    else {
+      addr = cand.local_addr.host;
+      port = cand.local_addr.port;
+    }
     switch(cand.type) {
       case NrIceCandidate::ICE_HOST:
         type = "host";
         break;
       case NrIceCandidate::ICE_SERVER_REFLEXIVE:
         type = "srflx";
         break;
       case NrIceCandidate::ICE_PEER_REFLEXIVE:
@@ -499,23 +524,24 @@ class IceTestPeer : public sigslot::has_
         if (which.find("Local") != std::string::npos) {
           type += "(" + cand.local_addr.transport + ")";
         }
         break;
       default:
         FAIL();
     };
 
+
     std::cerr << which
               << " --> "
               << type
               << " "
-              << cand.local_addr.host
+              << addr
               << ":"
-              << cand.local_addr.port
+              << port
               << " codeword="
               << cand.codeword
               << std::endl;
   }
 
   void DumpAndCheckActiveCandidates_s() {
     std::cerr << "Active candidates:" << std::endl;
     for (size_t i=0; i < streams_.size(); ++i) {
@@ -530,16 +556,22 @@ class IceTestPeer : public sigslot::has_
           std::cerr << "Component unpaired or disabled." << std::endl;
         } else {
           ASSERT_TRUE(NS_SUCCEEDED(res));
           DumpCandidate("Local  ", *local);
           ASSERT_EQ(expected_local_type_, local->type);
           ASSERT_EQ(expected_local_transport_, local->local_addr.transport);
           DumpCandidate("Remote ", *remote);
           ASSERT_EQ(expected_remote_type_, remote->type);
+          if (!expected_local_addr_.empty()) {
+            ASSERT_EQ(expected_local_addr_, local->cand_addr.host);
+          }
+          if (!expected_remote_addr_.empty()) {
+            ASSERT_EQ(expected_remote_addr_, remote->cand_addr.host);
+          }
           delete local;
           delete remote;
         }
       }
     }
   }
 
   void DumpAndCheckActiveCandidates() {
@@ -856,16 +888,18 @@ class IceTestPeer : public sigslot::has_
   size_t sent_;
   NrIceResolverFake fake_resolver_;
   nsRefPtr<NrIceResolver> dns_resolver_;
   IceTestPeer *remote_;
   CandidateFilter candidate_filter_;
   NrIceCandidate::Type expected_local_type_;
   std::string expected_local_transport_;
   NrIceCandidate::Type expected_remote_type_;
+  std::string expected_local_addr_;
+  std::string expected_remote_addr_;
   TrickleMode trickle_mode_;
   int trickled_;
   bool simulate_ice_lite_;
 };
 
 void SchedulableTrickleCandidate::Trickle() {
   timer_handle_ = nullptr;
   nsresult res = peer_->TrickleCandidate_s(candidate_, stream_);
@@ -873,34 +907,53 @@ void SchedulableTrickleCandidate::Trickl
 }
 
 class IceGatherTest : public ::testing::Test {
  public:
   void SetUp() {
     test_utils->sts_target()->Dispatch(WrapRunnable(TestStunServer::GetInstance(),
                                                     &TestStunServer::Reset),
                                        NS_DISPATCH_SYNC);
-    peer_ = new IceTestPeer("P1", true, false);
-    peer_->AddStream(1);
+  }
+
+  void TearDown() {
+    peer_ = nullptr;
+
+    test_utils->sts_target()->Dispatch(WrapRunnable(this,
+                                                    &IceGatherTest::TearDown_s),
+                                       NS_DISPATCH_SYNC);
+  }
+
+  void TearDown_s() {
+    NrIceCtx::internal_DeinitializeGlobal();
+  }
+
+  void EnsurePeer() {
+    if (!peer_) {
+      peer_ = new IceTestPeer("P1", true, false);
+      peer_->AddStream(1);
+    }
   }
 
   void Gather(unsigned int waitTime = kDefaultTimeout) {
-     peer_->Gather();
+    EnsurePeer();
+    peer_->Gather();
 
     if (waitTime) {
       WaitForGather(waitTime);
     }
   }
 
   void WaitForGather(unsigned int waitTime = kDefaultTimeout) {
     ASSERT_TRUE_WAIT(peer_->gathering_complete(), waitTime);
   }
 
   void UseFakeStunServerWithResponse(const std::string& fake_addr,
                                      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());
   }
 
   // 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.
@@ -924,32 +977,45 @@ class IceConnectTest : public ::testing:
   IceConnectTest() : initted_(false) {}
 
   void SetUp() {
     nsresult rv;
     target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
     ASSERT_TRUE(NS_SUCCEEDED(rv));
   }
 
+  void TearDown() {
+    p1_ = nullptr;
+    p2_ = nullptr;
+
+    test_utils->sts_target()->Dispatch(WrapRunnable(this,
+                                                    &IceConnectTest::TearDown_s),
+                                       NS_DISPATCH_SYNC);
+  }
+
+  void TearDown_s() {
+    NrIceCtx::internal_DeinitializeGlobal();
+  }
+
   void AddStream(const std::string& name, int components) {
-    Init(false);
+    Init(false, false);
     p1_->AddStream(components);
     p2_->AddStream(components);
   }
 
-  void Init(bool set_priorities) {
+  void Init(bool set_priorities, bool allow_loopback) {
     if (!initted_) {
-      p1_ = new IceTestPeer("P1", true, set_priorities);
-      p2_ = new IceTestPeer("P2", false, set_priorities);
+      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);
+    Init(false, false);
     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())
@@ -1006,16 +1072,21 @@ class IceConnectTest : public ::testing:
   }
 
   void SetExpectedTypes(NrIceCandidate::Type local1, NrIceCandidate::Type remote1,
                         NrIceCandidate::Type local2, NrIceCandidate::Type remote2) {
     p1_->SetExpectedTypes(local1, remote1);
     p2_->SetExpectedTypes(local2, remote2);
   }
 
+  void SetExpectedCandidateAddr(const std::string& addr) {
+    p1_->SetExpectedCandidateAddr(addr);
+    p2_->SetExpectedCandidateAddr(addr);
+  }
+
   void ConnectP1(TrickleMode mode = TRICKLE_NONE) {
     p1_->Connect(p2_, mode);
   }
 
   void ConnectP2(TrickleMode mode = TRICKLE_NONE) {
     p2_->Connect(p1_, mode);
   }
 
@@ -1141,21 +1212,34 @@ class PrioritizerTest : public ::testing
   nr_interface_prioritizer *prioritizer_;
 };
 
 class PacketFilterTest : public ::testing::Test {
  public:
   PacketFilterTest(): filter_(nullptr) {}
 
   void SetUp() {
+    // Set up enough of the ICE ctx to allow the packet filter to work
+    ice_ctx_ = NrIceCtx::Create("test", true);
+
     nsCOMPtr<nsIUDPSocketFilterHandler> handler =
       do_GetService(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID);
     handler->NewFilter(getter_AddRefs(filter_));
   }
 
+  void TearDown() {
+    test_utils->sts_target()->Dispatch(WrapRunnable(this,
+                                                    &PacketFilterTest::TearDown_s),
+                                       NS_DISPATCH_SYNC);
+  }
+
+  void TearDown_s() {
+    ice_ctx_ = nullptr;
+  }
+
   void TestIncoming(const uint8_t* data, uint32_t len,
                     uint8_t from_addr, int from_port,
                     bool expected_result) {
     mozilla::net::NetAddr addr;
     MakeNetAddr(&addr, from_addr, from_port);
     bool result;
     nsresult rv = filter_->FilterPacket(&addr, data, len,
                                         nsIUDPSocketFilter::SF_INCOMING,
@@ -1181,91 +1265,114 @@ class PacketFilterTest : public ::testin
   void MakeNetAddr(mozilla::net::NetAddr* net_addr,
                    uint8_t last_digit, uint16_t port) {
     net_addr->inet.family = AF_INET;
     net_addr->inet.ip = 192 << 24 | 168 << 16 | 1 << 8 | last_digit;
     net_addr->inet.port = port;
   }
 
   nsCOMPtr<nsIUDPSocketFilter> filter_;
+  RefPtr<NrIceCtx> ice_ctx_;
 };
 }  // end namespace
 
 TEST_F(IceGatherTest, TestGatherFakeStunServerHostnameNoResolver) {
+  EnsurePeer();
   peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherFakeStunServerIpAddress) {
+  EnsurePeer();
   peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherFakeStunServerHostname) {
+  EnsurePeer();
   peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherFakeStunBogusHostname) {
+  EnsurePeer();
   peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddress) {
+  EnsurePeer();
   peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
   peer_->SetDNSResolver();
   Gather();
   // TODO(jib@mozilla.com): ensure we get server reflexive candidates Bug 848094
 }
 
 TEST_F(IceGatherTest, TestGatherDNSStunServerHostname) {
+  EnsurePeer();
   peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   peer_->SetDNSResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherDNSStunBogusHostname) {
+  EnsurePeer();
   peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
   peer_->SetDNSResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherTurn) {
+  EnsurePeer();
   if (g_turn_server.empty())
     return;
   peer_->SetTurnServer(g_turn_server, kDefaultStunServerPort,
                        g_turn_user, g_turn_password, kNrIceTransportUdp);
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherTurnTcp) {
+  EnsurePeer();
   if (g_turn_server.empty())
     return;
   peer_->SetTurnServer(g_turn_server, kDefaultStunServerPort,
                        g_turn_user, g_turn_password, kNrIceTransportTcp);
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherDisableComponent) {
+  EnsurePeer();
   peer_->SetStunServer(kDefaultStunServerHostname, kDefaultStunServerPort);
   peer_->AddStream(2);
   peer_->DisableComponent(1, 2);
   Gather();
   std::vector<std::string> candidates =
     peer_->GetCandidates(1);
 
   for (size_t i=0; i<candidates.size(); ++i) {
     size_t sp1 = candidates[i].find(' ');
     ASSERT_EQ(0, candidates[i].compare(sp1+1, 1, "1", 1));
   }
 }
 
+TEST_F(IceGatherTest, TestGatherVerifyNoLoopback) {
+  Gather();
+  ASSERT_FALSE(StreamHasMatchingCandidate(0, "127.0.0.1"));
+}
+
+TEST_F(IceGatherTest, TestGatherAllowLoopback) {
+  // Set up peer with loopback allowed.
+  peer_ = new IceTestPeer("P1", true, false, true);
+  peer_->AddStream(1);
+  Gather();
+  ASSERT_TRUE(StreamHasMatchingCandidate(0, "127.0.0.1"));
+}
 
 // Verify that a bogus candidate doesn't cause crashes on the
 // main thread. See bug 856433.
 TEST_F(IceGatherTest, TestBogusCandidate) {
   Gather();
   peer_->ParseCandidate(0, kBogusIceCandidate);
 }
 
@@ -1304,28 +1411,38 @@ TEST_F(IceGatherTest, TestStunServerTric
 }
 
 TEST_F(IceConnectTest, TestGather) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(IceConnectTest, TestGatherAutoPrioritize) {
-  Init(false);
+  Init(false, false);
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
 }
 
 
 TEST_F(IceConnectTest, TestConnect) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
+TEST_F(IceConnectTest, TestLoopbackOnlySortOf) {
+  Init(false, true);
+  AddStream("first", 1);
+  ASSERT_TRUE(Gather());
+  SetCandidateFilter(IsLoopbackCandidate);
+  SetExpectedCandidateAddr("127.0.0.1");
+  Connect();
+}
+
+
 TEST_F(IceConnectTest, TestConnectBothControllingP1Wins) {
   AddStream("first", 1);
   p1_->SetTiebreaker(1);
   p2_->SetTiebreaker(0);
   ASSERT_TRUE(Gather());
   p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   Connect();
@@ -1432,17 +1549,17 @@ TEST_F(IceConnectTest, TestConnectP2Then
   std::cerr << "Sleeping between trickle streams" << std::endl;
   PR_Sleep(1000);  // Give this some time to settle but not complete
                    // all of ICE.
   SimulateTrickleP1(1);
   WaitForComplete(2);
 }
 
 TEST_F(IceConnectTest, TestConnectAutoPrioritize) {
-  Init(false);
+  Init(false, false);
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(IceConnectTest, TestConnectTrickleOneStreamOneComponent) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
@@ -1750,17 +1867,16 @@ TEST_F(IceConnectTest, TestPollCandPairs
   WaitForComplete();
   p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
   p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
   ASSERT_TRUE(ContainsSucceededPair(pairs1));
   ASSERT_TRUE(ContainsSucceededPair(pairs2));
 }
 
 TEST_F(IceConnectTest, TestRLogRingBuffer) {
-  RLogRingBuffer::CreateInstance();
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
 
   p2_->Connect(p1_, TRICKLE_NONE, false);
   p1_->Connect(p2_, TRICKLE_NONE, false);
 
   std::vector<NrIceCandidatePair> pairs1;
   std::vector<NrIceCandidatePair> pairs2;
@@ -1789,18 +1905,16 @@ TEST_F(IceConnectTest, TestRLogRingBuffe
 
   for (auto p = pairs2.begin(); p != pairs2.end(); ++p) {
     std::deque<std::string> logs;
     std::string substring("CAND-PAIR(");
     substring += p->codeword;
     RLogRingBuffer::GetInstance()->Filter(substring, 0, &logs);
     ASSERT_NE(0U, logs.size());
   }
-
-  RLogRingBuffer::DestroyInstance();
 }
 
 TEST_F(PrioritizerTest, TestPrioritizer) {
   SetPriorizer(::mozilla::CreateInterfacePrioritizer());
 
   AddInterface("0", NR_INTERFACE_TYPE_VPN, 100); // unknown vpn
   AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED, 100); // wired vpn
   AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI, 100); // wifi vpn
--- a/media/mtransport/test/turn_unittest.cpp
+++ b/media/mtransport/test/turn_unittest.cpp
@@ -401,17 +401,17 @@ int main(int argc, char **argv)
   NSS_NoDB_Init(nullptr);
   NSS_SetDomesticPolicy();
 
   // Set up the ICE registry, etc.
   // TODO(ekr@rtfm.com): Clean up
   std::string dummy("dummy");
   RUN_ON_THREAD(test_utils->sts_target(),
                 WrapRunnableNM(&NrIceCtx::Create,
-                               dummy, false, false),
+                               dummy, false, false, false),
                 NS_DISPATCH_SYNC);
 
   // Start the tests
   ::testing::InitGoogleTest(&argc, argv);
 
   int rv = RUN_ALL_TESTS();
   delete test_utils;
   return rv;
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
@@ -43,19 +43,23 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include "stun_reg.h"
 #include "nr_crypto.h"
 #include "r_time.h"
 
 static int nr_stun_client_send_request(nr_stun_client_ctx *ctx);
 static void nr_stun_client_timer_expired_cb(NR_SOCKET s, int b, void *cb_arg);
 static int nr_stun_client_get_password(void *arg, nr_stun_message *msg, Data **password);
 
+#define NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD 1
+#define NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK 2
+
 int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *peer, UINT4 RTO, nr_stun_client_ctx **ctxp)
   {
     nr_stun_client_ctx *ctx=0;
+    char allow_loopback;
     int r,_status;
 
     if ((r=nr_stun_startup()))
       ABORT(r);
 
     if(!(ctx=RCALLOC(sizeof(nr_stun_client_ctx))))
       ABORT(R_NO_MEMORY);
 
@@ -80,16 +84,22 @@ int nr_stun_client_ctx_create(char *labe
         ctx->retransmission_backoff_factor = 2.0;
 
     if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS, &ctx->maximum_transmits))
         ctx->maximum_transmits = 7;
 
     if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF, &ctx->final_retransmit_backoff_ms))
         ctx->final_retransmit_backoff_ms = 16 * ctx->rto_ms;
 
+     ctx->mapped_addr_check_mask = NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD;
+     if (NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback) ||
+         !allow_loopback) {
+       ctx->mapped_addr_check_mask |= NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK;
+     }
+
     /* If we are doing TCP, compute the maximum timeout as if
        we retransmitted and then set the maximum number of
        transmits to 1 and the timeout to maximum timeout*/
     if (ctx->my_addr.protocol == IPPROTO_TCP) {
       ctx->timeout_ms = ctx->final_retransmit_backoff_ms;
       ctx->maximum_transmits = 1;
     }
 
@@ -406,22 +416,22 @@ static int nr_stun_client_send_request(n
 static int nr_stun_client_get_password(void *arg, nr_stun_message *msg, Data **password)
 {
     *password = (Data*)arg;
     if (!arg)
         return(R_NOT_FOUND);
     return(0);
 }
 
-int nr_stun_transport_addr_check(nr_transport_addr* addr)
+int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 check)
   {
-    if(nr_transport_addr_is_wildcard(addr))
+    if((check & NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD) && nr_transport_addr_is_wildcard(addr))
       return(R_BAD_DATA);
 
-    if (nr_transport_addr_is_loopback(addr))
+    if ((check & NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK) && nr_transport_addr_is_loopback(addr))
       return(R_BAD_DATA);
 
     return(0);
   }
 
 int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr)
   {
     int r,_status;
@@ -639,17 +649,18 @@ int nr_stun_client_process_response(nr_s
         if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0))
             ABORT(R_BAD_DATA);
         if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0))
             ABORT(R_BAD_DATA);
 
         if (!nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_RELAY_ADDRESS, &attr))
           ABORT(R_BAD_DATA);
 
-        if ((r=nr_stun_transport_addr_check(&attr->u.relay_address.unmasked)))
+        if ((r=nr_stun_transport_addr_check(&attr->u.relay_address.unmasked,
+                                            ctx->mapped_addr_check_mask)))
           ABORT(r);
 
         if ((r=nr_transport_addr_copy(
                 &ctx->results.allocate_response.relay_addr,
                 &attr->u.relay_address.unmasked)))
           ABORT(r);
 
         if (!nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_LIFETIME, &attr))
@@ -683,24 +694,26 @@ int nr_stun_client_process_response(nr_s
     /* make sure we have the most up-to-date address from this peer */
     if (nr_transport_addr_cmp(&ctx->peer_addr, peer_addr, NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
         r_log(NR_LOG_STUN,LOG_INFO,"STUN-CLIENT(%s): Peer moved from %s to %s", ctx->label, ctx->peer_addr.as_string, peer_addr->as_string);
         nr_transport_addr_copy(&ctx->peer_addr, peer_addr);
     }
 
     if (mapped_addr) {
         if (nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, &attr)) {
-            if ((r=nr_stun_transport_addr_check(&attr->u.xor_mapped_address.unmasked)))
+            if ((r=nr_stun_transport_addr_check(&attr->u.xor_mapped_address.unmasked,
+                                                ctx->mapped_addr_check_mask)))
                 ABORT(r);
 
             if ((r=nr_transport_addr_copy(mapped_addr, &attr->u.xor_mapped_address.unmasked)))
                 ABORT(r);
         }
         else if (nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MAPPED_ADDRESS, &attr)) {
-            if ((r=nr_stun_transport_addr_check(&attr->u.mapped_address)))
+            if ((r=nr_stun_transport_addr_check(&attr->u.mapped_address,
+                                                ctx->mapped_addr_check_mask)))
                 ABORT(r);
 
             if ((r=nr_transport_addr_copy(mapped_addr, &attr->u.mapped_address)))
                 ABORT(r);
         }
         else
             ABORT(R_BAD_DATA);
 
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
@@ -167,30 +167,31 @@ struct nr_stun_client_ctx_ {
   char *nonce;
   char *realm;
   void *timer_handle;
   int request_ct;
   UINT4 rto_ms;    /* retransmission time out */
   double retransmission_backoff_factor;
   UINT4 maximum_transmits;
   UINT4 final_retransmit_backoff_ms;
+  UINT4 mapped_addr_check_mask;  /* What checks to run on mapped addresses */
   int timeout_ms;
   struct timeval timer_set;
   int retry_ct;
   NR_async_cb finished_cb;
   void *cb_arg;
   nr_stun_message *request;
   nr_stun_message *response;
   UINT2 error_code;
 };
 
 int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *peer, UINT4 RTO, nr_stun_client_ctx **ctxp);
 int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished_cb, void *cb_arg);
 int nr_stun_client_restart(nr_stun_client_ctx *ctx);
 int nr_stun_client_force_retransmit(nr_stun_client_ctx *ctx);
 int nr_stun_client_reset(nr_stun_client_ctx *ctx);
 int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp);
-int nr_stun_transport_addr_check(nr_transport_addr* addr);
+int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 mask);
 int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr);
 int nr_stun_client_cancel(nr_stun_client_ctx *ctx);
 
 #endif
 
--- a/media/mtransport/third_party/nICEr/src/stun/stun_reg.h
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_reg.h
@@ -39,16 +39,17 @@ using namespace std;
 extern "C" {
 #endif /* __cplusplus */
 
 #define NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT    "stun.client.retransmission_timeout"
 #define NR_STUN_REG_PREF_CLNT_RETRANSMIT_BACKOFF    "stun.client.retransmission_backoff_factor"
 #define NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS     "stun.client.maximum_transmits"
 #define NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF   "stun.client.final_retransmit_backoff"
 
+#define NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS            "stun.allow_loopback"
 #define NR_STUN_REG_PREF_ADDRESS_PRFX               "stun.address"
 #define NR_STUN_REG_PREF_SERVER_NAME                "stun.server.name"
 #define NR_STUN_REG_PREF_SERVER_NONCE_SIZE          "stun.server.nonce_size"
 #define NR_STUN_REG_PREF_SERVER_REALM               "stun.server.realm"
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
--- a/media/mtransport/third_party/nICEr/src/stun/stun_util.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_util.c
@@ -105,17 +105,26 @@ nr_stun_find_local_addresses(nr_local_ad
 
     if ((r=NR_reg_get_child_count(NR_STUN_REG_PREF_ADDRESS_PRFX, (unsigned int*)count)))
         if (r == R_NOT_FOUND)
             *count = 0;
         else
             ABORT(r);
 
     if (*count == 0) {
-        if ((r=nr_stun_get_addrs(addrs, maxaddrs, 1, count)))
+        char allow_loopback;
+
+        if ((r=NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback))) {
+            if (r == R_NOT_FOUND)
+                allow_loopback = 0;
+            else
+                ABORT(r);
+        }
+
+        if ((r=nr_stun_get_addrs(addrs, maxaddrs, !allow_loopback, count)))
             ABORT(r);
 
         goto done;
     }
 
     if (*count >= maxaddrs) {
         r_log(NR_LOG_STUN, LOG_INFO, "Address list truncated from %d to %d", *count, maxaddrs);
        *count = maxaddrs;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -199,19 +199,29 @@ PeerConnectionMedia::PeerConnectionMedia
       mIceCtx(nullptr),
       mDNSResolver(new mozilla::NrIceResolver()),
       mMainThread(mParent->GetMainThread()),
       mSTSThread(mParent->GetSTSThread()) {}
 
 nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
                                    const std::vector<NrIceTurnServer>& turn_servers)
 {
+#ifdef MOZILLA_INTERNAL_API
+  bool allow_loopback = Preferences::GetBool(
+    "media.peerconnection.ice.loopback", false);
+#else
+  bool allow_loopback = false;
+#endif
+
   // TODO(ekr@rtfm.com): need some way to set not offerer later
   // Looks like a bug in the NrIceCtx API.
-  mIceCtx = NrIceCtx::Create("PC:" + mParent->GetName(), true);
+  mIceCtx = NrIceCtx::Create("PC:" + mParent->GetName(),
+                             true, // Offerer
+                             true, // Trickle
+                             allow_loopback);
   if(!mIceCtx) {
     CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
   nsresult rv;
   if (NS_FAILED(rv = mIceCtx->SetStunServers(stun_servers))) {
     CSFLogError(logTag, "%s: Failed to set stun servers", __FUNCTION__);
     return rv;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -332,16 +332,17 @@ pref("media.getusermedia.browser.enabled
 // Desktop is typically VGA capture or more; and qm_select will not drop resolution
 // below 1/2 in each dimension (or so), so QVGA (320x200) is the lowest here usually.
 pref("media.peerconnection.video.min_bitrate", 200);
 pref("media.peerconnection.video.start_bitrate", 300);
 pref("media.peerconnection.video.max_bitrate", 2000);
 #endif
 pref("media.navigator.permission.disabled", false);
 pref("media.peerconnection.default_iceservers", "[{\"url\": \"stun:stun.services.mozilla.com\"}]");
+pref("media.peerconnection.ice.loopback", false); // Set only for testing in offline environments.
 pref("media.peerconnection.use_document_iceservers", true);
 // Do not enable identity before ensuring that the UX cannot be spoofed
 // see Bug 884573 for details
 // Do not enable identity before fixing domain comparison: see Bug 958741
 // Do not enable identity before fixing origin spoofing: see Bug 968335
 pref("media.peerconnection.identity.enabled", false);
 pref("media.peerconnection.identity.timeout", 10000);
 // These values (aec, agc, and noice) are from media/webrtc/trunk/webrtc/common_types.h