Bug 1027350 - Allow loopback addresses for ICE. r=bwc
authorEKR <ekr@rtfm.com>
Sun, 22 Jun 2014 15:11:48 -0700
changeset 209026 e31ae14f7c5c6b2266839e654735e25b22373334
parent 209025 8d3d8d907e4e26513c402cbc6379c999328f75df
child 209027 ccf50b47021dcb2492b49e217817f036b2f51ba8
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbwc
bugs1027350
milestone35.0a1
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