Bug 929977: Add support for RFC 7675 ICE consent freshness. r=mt,bwc draft
authorNils Ohlmeier [:drno] <drno@ohlmeier.org>
Tue, 26 Apr 2016 13:11:25 -0700
changeset 356642 f05e3854ef240c59a6f499523b03e145633aedd4
parent 356641 8d30d67837bda40c8b39590ae5660918a195592d
child 356643 889f7b756f61ea94ac14fe761e9367da24efe34d
push id16556
push userdrno@ohlmeier.org
push dateTue, 26 Apr 2016 20:10:54 +0000
reviewersmt, bwc
bugs929977
milestone49.0a1
Bug 929977: Add support for RFC 7675 ICE consent freshness. r=mt,bwc MozReview-Commit-ID: HGRM10L0R3M
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/nricemediastream.cpp
media/mtransport/nricemediastream.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test_nr_socket.cpp
media/mtransport/test_nr_socket.h
media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
media/mtransport/third_party/nICEr/src/ice/ice_component.c
media/mtransport/third_party/nICEr/src/ice/ice_component.h
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/ice/ice_media_stream.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
media/mtransport/third_party/nICEr/src/net/transport_addr.c
media/mtransport/third_party/nICEr/src/net/transport_addr.h
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -659,16 +659,20 @@ int NrIceCtx::SetNat(const RefPtr<TestNa
 void NrIceCtx::internal_DeinitializeGlobal() {
   NR_reg_del((char *)"stun");
   NR_reg_del((char *)"ice");
   RLogRingBuffer::DestroyInstance();
   nr_crypto_vtbl = nullptr;
   initialized = false;
 }
 
+void NrIceCtx::internal_SetTimerAccelarator(int divider) {
+  ctx_->test_timer_divider = divider;
+}
+
 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
@@ -226,16 +226,18 @@ class NrIceCtx {
                   const std::string& ufrag,
                   const std::string& pwd);
 
   int SetNat(const RefPtr<TestNat>& aNat);
 
   // Deinitialize all ICE global state. Used only for testing.
   static void internal_DeinitializeGlobal();
 
+  // Divide some timers to faster testing. Used only for testing.
+  void internal_SetTimerAccelarator(int divider);
 
   nr_ice_ctx *ctx() { return ctx_; }
   nr_ice_peer_ctx *peer() { return peer_; }
 
   // Testing only.
   void destroy_peer_ctx();
 
   void SetStream(size_t index, NrIceMediaStream* stream);
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -529,16 +529,41 @@ nsresult NrIceMediaStream::DisableCompon
     MOZ_MTLOG(ML_ERROR, "Couldn't disable '" << name_ << "':" <<
               component_id);
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+nsresult NrIceMediaStream::GetConsentStatus(int component_id, bool *can_send, struct timeval *ts) {
+  if (!stream_)
+    return NS_ERROR_FAILURE;
+
+  nr_ice_media_stream* peer_stream;
+  int r = nr_ice_peer_ctx_find_pstream(ctx_peer_, stream_, &peer_stream);
+  if (r) {
+    MOZ_MTLOG(ML_ERROR, "Failed to find peer stream for '" << name_ << "':" <<
+              component_id);
+    return NS_ERROR_FAILURE;
+  }
+
+  int send = 0;
+  r = nr_ice_media_stream_get_consent_status(peer_stream, component_id,
+                                             &send, ts);
+  if (r) {
+    MOZ_MTLOG(ML_ERROR, "Failed to get consent status for '" << name_ << "':" <<
+              component_id);
+    return NS_ERROR_FAILURE;
+  }
+  *can_send = !!send;
+
+  return NS_OK;
+}
+
 nsresult NrIceMediaStream::SendPacket(int component_id,
                                       const unsigned char *data,
                                       size_t len) {
   if (!stream_)
     return NS_ERROR_FAILURE;
 
   int r = nr_ice_media_stream_send(ctx_peer_, stream_,
                                    component_id,
--- a/media/mtransport/nricemediastream.h
+++ b/media/mtransport/nricemediastream.h
@@ -159,16 +159,20 @@ class NrIceMediaStream {
   nsresult DisableComponent(int component);
 
   // Get the candidate pair currently active. It's the
   // caller's responsibility to free these.
   nsresult GetActivePair(int component,
                          UniquePtr<NrIceCandidate>* local,
                          UniquePtr<NrIceCandidate>* remote);
 
+  // Get the current ICE consent send status plus the timeval of the last
+  // consent update time.
+  nsresult GetConsentStatus(int component, bool *can_send, struct timeval *ts);
+
   // The number of components
   size_t components() const { return components_; }
 
   // The underlying nICEr stream
   nr_ice_media_stream *stream() { return stream_; }
   // Signals to indicate events. API users can (and should)
   // register for these.
 
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -43,27 +43,26 @@
 #include "ice_ctx.h"
 #include "ice_peer_ctx.h"
 #include "ice_media_stream.h"
 
 extern "C" {
 #include "async_timer.h"
 #include "r_data.h"
 #include "util.h"
+#include "r_time.h"
 }
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
 #include "gtest_utils.h"
 
 
 using namespace mozilla;
 
-bool stream_added = false;
-
 static unsigned int kDefaultTimeout = 7000;
 
 //TODO(nils@mozilla.com): This should get replaced with some non-external
 //solution like discussed in bug 860775.
 const std::string kDefaultStunServerHostname(
     (char *)"global.stun.twilio.com");
 const std::string kBogusStunServerHostname(
     (char *)"stun-server-nonexistent.invalid");
@@ -173,16 +172,18 @@ public:
     RLogRingBuffer::DestroyInstance();
 
     MtransportTest::TearDown();
   }
 };
 
 enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL };
 
+enum ConsentStatus { CONSENT_FRESH, CONSENT_STALE, CONSENT_EXPIRED};
+
 const unsigned int ICE_TEST_PEER_OFFERER = (1 << 0);
 const unsigned int ICE_TEST_PEER_ALLOW_LOOPBACK = (1 << 1);
 const unsigned int ICE_TEST_PEER_ENABLED_TCP = (1 << 2);
 const unsigned int ICE_TEST_PEER_ALLOW_LINK_LOCAL = (1 << 3);
 const unsigned int ICE_TEST_PEER_HIDE_NON_DEFAULT = (1 << 4);
 
 typedef std::string (*CandidateFilter)(const std::string& candidate);
 
@@ -407,16 +408,18 @@ class IceTestPeer : public sigslot::has_
       test_utils_(utils) {
     ice_ctx_->ctx()->SignalGatheringStateChange.connect(
         this,
         &IceTestPeer::GatheringStateChange);
     ice_ctx_->ctx()->SignalConnectionStateChange.connect(
         this,
         &IceTestPeer::ConnectionStateChange);
 
+    consent_timestamp_.tv_sec = 0;
+    consent_timestamp_.tv_usec = 0;
     int r = ice_ctx_->ctx()->SetNat(nat_);
     (void)r;
     MOZ_ASSERT(!r);
   }
 
   ~IceTestPeer() {
     test_utils_->sts_target()->Dispatch(WrapRunnable(this,
                                                     &IceTestPeer::Shutdown),
@@ -536,31 +539,43 @@ class IceTestPeer : public sigslot::has_
 
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   void UseNat() {
     nat_->enabled_ = true;
   }
 
+  void SetTimerDivider(int div) {
+    ice_ctx_->ctx()->internal_SetTimerAccelarator(div);
+  }
+
+  void SetStunResponseDelay(uint32_t delay) {
+    nat_->delay_stun_resp_ms_ = delay;
+  }
+
   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;
   }
 
+  void SetBlockStun(bool block) {
+    nat_->block_stun_ = block;
+  }
+
   // Get various pieces of state
   std::vector<std::string> GetGlobalAttributes() {
     std::vector<std::string> attrs(ice_ctx_->ctx()->GetGlobalAttributes());
     if (simulate_ice_lite_) {
       attrs.push_back("ice-lite");
     }
     return attrs;
   }
@@ -1227,16 +1242,30 @@ class IceTestPeer : public sigslot::has_
     }
 
     ASSERT_TRUE(NS_SUCCEEDED(media_stream->SendPacket(component, data, len)));
 
     ++sent_;
     std::cerr << name_ << ": sent " << len << " bytes" << std::endl;
   }
 
+  void SendFailure(int stream, int component) {
+    RefPtr<NrIceMediaStream> media_stream = ice_ctx_->ctx()->GetStream(stream);
+    if (!media_stream) {
+      ADD_FAILURE() << "No such stream " << stream;
+      return;
+    }
+
+    const std::string d("FAIL");
+    ASSERT_TRUE(NS_FAILED(media_stream->SendPacket(component,
+      reinterpret_cast<const unsigned char *>(d.c_str()), d.length())));
+
+    std::cerr << name_ << ": send failed as expected" << std::endl;
+  }
+
   void SetCandidateFilter(CandidateFilter filter) {
     candidate_filter_ = filter;
   }
 
   void ParseCandidate_s(size_t i, const std::string& candidate) {
     ASSERT_TRUE(ice_ctx_->ctx()->GetStream(i).get()) << "No such stream " << i;
 
     std::vector<std::string> attributes;
@@ -1269,16 +1298,54 @@ class IceTestPeer : public sigslot::has_
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this,
                         &IceTestPeer::DisableComponent_s,
                         stream,
                         component_id),
         NS_DISPATCH_SYNC);
   }
 
+  void AssertConsentRefresh_s(size_t stream, int component_id, ConsentStatus status) {
+    ASSERT_LT(stream, ice_ctx_->ctx()->GetStreamCount());
+    ASSERT_TRUE(ice_ctx_->ctx()->GetStream(stream).get()) << "No such stream "
+                                                          << stream;
+    bool can_send;
+    struct timeval timestamp;
+    nsresult res = ice_ctx_->ctx()->GetStream(stream)->
+                    GetConsentStatus(component_id, &can_send, &timestamp);
+    ASSERT_TRUE(NS_SUCCEEDED(res));
+    if (status == CONSENT_EXPIRED) {
+      ASSERT_EQ(can_send, 0);
+    } else {
+      ASSERT_EQ(can_send, 1);
+    }
+    if (consent_timestamp_.tv_sec) {
+      if (status == CONSENT_FRESH) {
+        ASSERT_EQ(r_timeval_cmp(&timestamp, &consent_timestamp_), 1);
+      } else {
+        ASSERT_EQ(r_timeval_cmp(&timestamp, &consent_timestamp_), 0);
+      }
+    }
+    consent_timestamp_.tv_sec = timestamp.tv_sec;
+    consent_timestamp_.tv_usec = timestamp.tv_usec;
+    std::cerr << name_ << ": new consent timestamp = " <<
+      consent_timestamp_.tv_sec << "." << consent_timestamp_.tv_usec <<
+      std::endl;
+  }
+
+  void AssertConsentRefresh(ConsentStatus status) {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(this,
+                        &IceTestPeer::AssertConsentRefresh_s,
+                        0,
+                        1,
+                        status),
+        NS_DISPATCH_SYNC);
+  }
+
   int trickled() { return trickled_; }
 
   void SetControlling(NrIceCtx::Controlling controlling) {
     nsresult res;
     test_utils_->sts_target()->Dispatch(
         WrapRunnableRet(&res, ice_ctx_->ctx(),
                         &NrIceCtx::SetControlling,
                         controlling),
@@ -1327,16 +1394,17 @@ class IceTestPeer : public sigslot::has_
   std::map<size_t, std::vector<SchedulableTrickleCandidate*> >
     controlled_trickle_candidates_;
   bool gathering_complete_;
   int ready_ct_;
   bool ice_complete_;
   bool ice_reached_checking_;
   size_t received_;
   size_t sent_;
+  struct timeval consent_timestamp_;
   NrIceResolverFake fake_resolver_;
   RefPtr<NrIceResolver> dns_resolver_;
   IceTestPeer *remote_;
   CandidateFilter candidate_filter_;
   NrIceCandidate::Type expected_local_type_;
   std::string expected_local_transport_;
   NrIceCandidate::Type expected_remote_type_;
   std::string expected_remote_addr_;
@@ -1540,17 +1608,17 @@ class WebRtcIceConnectTest : public Stun
 
   void TearDown() override {
     p1_ = nullptr;
     p2_ = nullptr;
 
     StunTest::TearDown();
   }
 
-  void AddStream(const std::string& name, int components) {
+  void AddStream(int components) {
     Init(false, false);
     p1_->AddStream(components);
     p2_->AddStream(components);
   }
 
   void RemoveStream(size_t index) {
     p1_->RemoveStream(index);
     p2_->RemoveStream(index);
@@ -1642,16 +1710,31 @@ class WebRtcIceConnectTest : public Stun
   }
 
   void BlockUdp() {
     // note: |block_udp_| is used only in InitPeer.
     // Use IceTestPeer::SetBlockUdp to act on the peer directly.
     block_udp_ = true;
   }
 
+  void SetupAndCheckConsent() {
+    p1_->SetTimerDivider(10);
+    p2_->SetTimerDivider(10);
+    ASSERT_TRUE(Gather());
+    Connect();
+    p1_->AssertConsentRefresh(CONSENT_FRESH);
+    p2_->AssertConsentRefresh(CONSENT_FRESH);
+    SendReceive();
+  }
+
+  void AssertConsentRefresh(ConsentStatus status = CONSENT_FRESH) {
+    p1_->AssertConsentRefresh(status);
+    p2_->AssertConsentRefresh(status);
+  }
+
   void InitTestStunServer() {
     if (test_stun_server_inited_) {
       return;
     }
 
     std::cerr << "Resetting TestStunServer" << std::endl;
     TestStunServer::GetInstance(AF_INET)->Reset();
     test_stun_server_inited_ = true;
@@ -1820,16 +1903,23 @@ class WebRtcIceConnectTest : public Stun
     if (expect_rx_failure) {
       usleep(1000);
       ASSERT_EQ(previousReceived, p2->received());
     } else {
       ASSERT_TRUE_WAIT(p2->received() == previousReceived+1, 1000);
     }
   }
 
+  void SendFailure() {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(p1_.get(),
+                     &IceTestPeer::SendFailure, 0, 1),
+        NS_DISPATCH_SYNC);
+  }
+
  protected:
   bool initted_;
   bool test_stun_server_inited_;
   nsCOMPtr<nsIEventTarget> target_;
   mozilla::UniquePtr<IceTestPeer> p1_;
   mozilla::UniquePtr<IceTestPeer> p2_;
   bool use_nat_;
   TestNat::NatBehavior filtering_type_;
@@ -2393,42 +2483,42 @@ TEST_F(WebRtcIceGatherTest, TestStunTcpA
   ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
   ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
   WaitForGather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
 }
 
 TEST_F(WebRtcIceConnectTest, TestGather) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherTcp) {
   Init(false, true);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherAutoPrioritize) {
   Init(false, false);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 
 TEST_F(WebRtcIceConnectTest, TestConnect) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 
 TEST_F(WebRtcIceConnectTest, TestConnectRestartIce) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   Connect();
   SendReceive(p1_.get(), p2_.get());
 
   p2_->RestartIce();
   ASSERT_FALSE(p2_->gathering_complete());
 
   // verify p1 and p2 streams are still connected after restarting ice on p2
@@ -2452,17 +2542,17 @@ TEST_F(WebRtcIceConnectTest, TestConnect
 
   SendReceive(p1_.get(), p2_.get(), false, true); // p1 and p2 not connected
 
   p3_ = nullptr;
 }
 
 
 TEST_F(WebRtcIceConnectTest, TestConnectRestartIceThenAbort) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   Connect();
   SendReceive(p1_.get(), p2_.get());
 
   p2_->RestartIce();
   ASSERT_FALSE(p2_->gathering_complete());
 
   // verify p1 and p2 streams are still connected after restarting ice on p2
@@ -2487,238 +2577,238 @@ TEST_F(WebRtcIceConnectTest, TestConnect
   SendReceive(p3_.get(), p2_.get(), false, true); // p3 and p2 not connected
 
   p3_ = nullptr;
 }
 
 
 TEST_F(WebRtcIceConnectTest, TestConnectTcp) {
   Init(false, true);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsTcpCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
     NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
   Connect();
 }
 
 //TCP SO tests works on localhost only with delay applied:
 //  tc qdisc add dev lo root netem delay 10ms
 TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectTcpSo) {
   Init(false, true);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsTcpSoCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
     NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
   Connect();
 }
 
 // Disabled because this breaks with hairpinning.
 TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectDefaultRouteOnly) {
   Init(false, false, true);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
     NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, kNrIceTransportTcp);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestLoopbackOnlySortOf) {
   Init(true, false, false, false);
-  AddStream("first", 1);
+  AddStream(1);
   SetCandidateFilter(IsLoopbackCandidate);
   ASSERT_TRUE(Gather());
   SetExpectedRemoteCandidateAddr("127.0.0.1");
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP1Wins) {
-  AddStream("first", 1);
+  AddStream(1);
   p1_->SetTiebreaker(1);
   p2_->SetTiebreaker(0);
   ASSERT_TRUE(Gather());
   p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP2Wins) {
-  AddStream("first", 1);
+  AddStream(1);
   p1_->SetTiebreaker(0);
   p2_->SetTiebreaker(1);
   ASSERT_TRUE(Gather());
   p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectIceLiteOfferer) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   p1_->SimulateIceLite();
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP1Wins) {
-  AddStream("first", 1);
+  AddStream(1);
   p1_->SetTiebreaker(1);
   p2_->SetTiebreaker(0);
   ASSERT_TRUE(Gather());
   p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   ConnectTrickle();
   SimulateTrickle(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP2Wins) {
-  AddStream("first", 1);
+  AddStream(1);
   p1_->SetTiebreaker(0);
   p2_->SetTiebreaker(1);
   ASSERT_TRUE(Gather());
   p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   ConnectTrickle();
   SimulateTrickle(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestTrickleIceLiteOfferer) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   p1_->SimulateIceLite();
   ConnectTrickle();
   SimulateTrickle(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherFullCone) {
   UseNat();
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherFullConeAutoPrioritize) {
   UseNat();
   Init(true, false);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 
 TEST_F(WebRtcIceConnectTest, TestConnectFullCone) {
   UseNat();
-  AddStream("first", 1);
+  AddStream(1);
   SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectNoNatRouteOnly) {
   Init(false, false, true, false);
-  AddStream("first", 1);
+  AddStream(1);
   UseTestStunServer();
   // Because we are connecting from our host candidate to the
   // other side's apparent srflx (which is also their host)
   // we see a host/srflx pair.
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
                    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectFullConeDefaultRouteOnly) {
   UseNat();
   Init(false, false, true);
-  AddStream("first", 1);
+  AddStream(1);
   SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherAddressRestrictedCone) {
   UseNat();
   SetFilteringType(TestNat::ADDRESS_DEPENDENT);
   SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectAddressRestrictedCone) {
   UseNat();
   SetFilteringType(TestNat::ADDRESS_DEPENDENT);
   SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
-  AddStream("first", 1);
+  AddStream(1);
   SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherPortRestrictedCone) {
   UseNat();
   SetFilteringType(TestNat::PORT_DEPENDENT);
   SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectPortRestrictedCone) {
   UseNat();
   SetFilteringType(TestNat::PORT_DEPENDENT);
   SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
-  AddStream("first", 1);
+  AddStream(1);
   SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherSymmetricNat) {
   UseNat();
   SetFilteringType(TestNat::PORT_DEPENDENT);
   SetMappingType(TestNat::PORT_DEPENDENT);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) {
   if (turn_server_.empty())
     return;
 
   UseNat();
   SetFilteringType(TestNat::PORT_DEPENDENT);
   SetMappingType(TestNat::PORT_DEPENDENT);
-  AddStream("first", 1);
+  AddStream(1);
   p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED);
   p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherNatBlocksUDP) {
   if (turn_server_.empty())
     return;
 
   UseNat();
   BlockUdp();
-  AddStream("first", 1);
+  AddStream(1);
   std::vector<NrIceTurnServer> turn_servers;
   std::vector<unsigned char> password_vec(turn_password_.begin(),
                                           turn_password_.end());
   turn_servers.push_back(
       *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
                                turn_user_, password_vec, kNrIceTransportTcp));
   turn_servers.push_back(
       *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
@@ -2729,17 +2819,17 @@ TEST_F(WebRtcIceConnectTest, TestGatherN
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectNatBlocksUDP) {
   if (turn_server_.empty())
     return;
 
   UseNat();
   BlockUdp();
-  AddStream("first", 1);
+  AddStream(1);
   std::vector<NrIceTurnServer> turn_servers;
   std::vector<unsigned char> password_vec(turn_password_.begin(),
                                           turn_password_.end());
   turn_servers.push_back(
       *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
                                turn_user_, password_vec, kNrIceTransportTcp));
   turn_servers.push_back(
       *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
@@ -2751,84 +2841,84 @@ TEST_F(WebRtcIceConnectTest, TestConnect
   p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED,
                         kNrIceTransportTcp);
   ASSERT_TRUE(Gather(kDefaultTimeout * 3));
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTwoComponents) {
-  AddStream("first", 2);
+  AddStream(2);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTwoComponentsDisableSecond) {
-  AddStream("first", 2);
+  AddStream(2);
   ASSERT_TRUE(Gather());
   p1_->DisableComponent(0, 2);
   p2_->DisableComponent(0, 2);
   Connect();
 }
 
 
 TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectP2();
   PR_Sleep(1000);
   ConnectP1();
   WaitForComplete();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1Trickle) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectP2();
   PR_Sleep(1000);
   ConnectP1(TRICKLE_SIMULATE);
   SimulateTrickleP1(0);
   WaitForComplete();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) {
-  AddStream("first", 1);
-  AddStream("second", 2);
+  AddStream(1);
+  AddStream(2);
   ASSERT_TRUE(Gather());
   ConnectP2();
   PR_Sleep(1000);
   ConnectP1(TRICKLE_SIMULATE);
   SimulateTrickleP1(0);
   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(WebRtcIceConnectTest, TestConnectAutoPrioritize) {
   Init(false, false);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTrickleOneStreamOneComponent) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   SimulateTrickle(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTrickleTwoStreamsOneComponent) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   SimulateTrickle(0);
   SimulateTrickle(1);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
@@ -2898,253 +2988,314 @@ void AddNonPairableCandidates(
   }
 }
 
 void DropTrickleCandidates(
     std::vector<SchedulableTrickleCandidate*>& candidates) {
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamDuringICE) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
-  AddStream("second", 1);
+  AddStream(1);
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamAfterICE) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
-  AddStream("second", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, RemoveStream) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 
   RemoveStream(0);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
 }
 
 TEST_F(WebRtcIceConnectTest, P1NoTrickle) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   DropTrickleCandidates(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(WebRtcIceConnectTest, P2NoTrickle) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   DropTrickleCandidates(p2_->ControlTrickle(0));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(WebRtcIceConnectTest, RemoveAndAddStream) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 
   RemoveStream(0);
-  AddStream("third", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(2));
   RealisticTrickleDelay(p2_->ControlTrickle(2));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(WebRtcIceConnectTest, RemoveStreamBeforeGather) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   ASSERT_TRUE(Gather(0));
   RemoveStream(0);
   WaitForGather();
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(WebRtcIceConnectTest, RemoveStreamDuringGather) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   RemoveStream(0);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(WebRtcIceConnectTest, RemoveStreamDuringConnect) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   RemoveStream(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectRealTrickleOneStreamOneComponent) {
-  AddStream("first", 1);
-  AddStream("second", 1);
+  AddStream(1);
+  AddStream(1);
   ASSERT_TRUE(Gather(0));
   ConnectTrickle(TRICKLE_REAL);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   WaitForGather();  // ICE can complete before we finish gathering.
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceive) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   Connect();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTcp) {
   Init(false, true);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsTcpCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
     NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
   Connect();
   SendReceive();
 }
 
 //TCP SO tests works on localhost only with delay applied:
 //  tc qdisc add dev lo root netem delay 10ms
 TEST_F(WebRtcIceConnectTest, DISABLED_TestSendReceiveTcpSo) {
   Init(false, true);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsTcpSoCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
     NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
   Connect();
   SendReceive();
 }
 
+TEST_F(WebRtcIceConnectTest, TestConsent) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  PR_Sleep(1500);
+  AssertConsentRefresh();
+  SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentTcp) {
+  Init(false, true);
+  AddStream(1);
+  SetCandidateFilter(IsTcpCandidate);
+  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+    NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+  SetupAndCheckConsent();
+  PR_Sleep(1500);
+  AssertConsentRefresh();
+  SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentIntermittent) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  p1_->SetBlockStun(true);
+  p2_->SetBlockStun(true);
+  PR_Sleep(1000);
+  AssertConsentRefresh(CONSENT_STALE);
+  SendReceive();
+  p1_->SetBlockStun(false);
+  p2_->SetBlockStun(false);
+  PR_Sleep(600);
+  AssertConsentRefresh();
+  SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentTimeout) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  p1_->SetBlockStun(true);
+  p2_->SetBlockStun(true);
+  PR_Sleep(600);
+  AssertConsentRefresh(CONSENT_STALE);
+  SendReceive();
+  PR_Sleep(2500);
+  AssertConsentRefresh(CONSENT_EXPIRED);
+  SendFailure();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentDelayed) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  /* Note: We don't have a list of STUN transaction IDs of the previously timed
+           out consent requests. Thus responses after sending the next consent
+           request are ignored. */
+  p1_->SetStunResponseDelay(300);
+  p2_->SetStunResponseDelay(300);
+  PR_Sleep(1000);
+  AssertConsentRefresh();
+  SendReceive();
+}
+
 TEST_F(WebRtcIceConnectTest, TestConnectTurn) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithDelay) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
   p1_->Gather();
   PR_Sleep(500);
   p2_->Gather();
   ConnectTrickle(TRICKLE_REAL);
   WaitForGather();
   WaitForComplete();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelay) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
 
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelayOneSided) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   p2_->SimulateTrickle(0);
 
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithLargeTrickleDelay) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   // Trickle host candidates immediately, but delay relay candidates
   DelayRelayCandidates(p1_->ControlTrickle(0), 3700);
   DelayRelayCandidates(p2_->ControlTrickle(0), 3700);
@@ -3153,88 +3304,88 @@ TEST_F(WebRtcIceConnectTest, TestConnect
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnTcp) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_, kNrIceTransportTcp);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnOnly) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnTcpOnly) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_, kNrIceTransportTcp);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED,
                    kNrIceTransportTcp);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnOnly) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED);
   Connect();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnTcpOnly) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_, kNrIceTransportTcp);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED,
                    kNrIceTransportTcp);
   Connect();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnBothOnly) {
   if (turn_server_.empty())
     return;
 
-  AddStream("first", 1);
+  AddStream(1);
   std::vector<NrIceTurnServer> turn_servers;
   std::vector<unsigned char> password_vec(turn_password_.begin(),
                                           turn_password_.end());
   turn_servers.push_back(*NrIceTurnServer::Create(
                            turn_server_, kDefaultStunServerPort,
                            turn_user_, password_vec, kNrIceTransportTcp));
   turn_servers.push_back(*NrIceTurnServer::Create(
                            turn_server_, kDefaultStunServerPort,
@@ -3246,38 +3397,38 @@ TEST_F(WebRtcIceConnectTest, TestSendRec
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED,
                    kNrIceTransportUdp);
   Connect();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectShutdownOneSide) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   ConnectThenDelete();
 }
 
 TEST_F(WebRtcIceConnectTest, TestPollCandPairsBeforeConnect) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 
   std::vector<NrIceCandidatePair> pairs;
   nsresult res = p1_->GetCandidatePairs(0, &pairs);
   // There should be no candidate pairs prior to calling Connect()
   ASSERT_EQ(NS_OK, res);
   ASSERT_EQ(0U, pairs.size());
 
   res = p2_->GetCandidatePairs(0, &pairs);
   ASSERT_EQ(NS_OK, res);
   ASSERT_EQ(0U, pairs.size());
 }
 
 TEST_F(WebRtcIceConnectTest, TestPollCandPairsAfterConnect) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   Connect();
 
   std::vector<NrIceCandidatePair> pairs;
   nsresult r = p1_->GetCandidatePairs(0, &pairs);
   ASSERT_EQ(NS_OK, r);
   // How detailed of a check do we want to do here? If the turn server is
   // functioning, we'll get at least two pairs, but this is probably not
@@ -3293,17 +3444,17 @@ TEST_F(WebRtcIceConnectTest, TestPollCan
   ASSERT_TRUE(p2_->CandidatePairsPriorityDescending(pairs));
   ASSERT_TRUE(ContainsSucceededPair(pairs));
 }
 
 // TODO Bug 1259842 - disabled until we find a better way to handle two
 // candidates from different RFC1918 ranges
 TEST_F(WebRtcIceConnectTest, DISABLED_TestHostCandPairingFilter) {
   Init(false, false, false, false);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsIpv4Candidate);
 
   int host_net = p1_->GetCandidatesPrivateIpv4Range(0);
   if (host_net <= 0) {
     // TODO bug 1226838: make this work with multiple private IPs
     FAIL() << "This test needs exactly one private IPv4 host candidate to work" << std::endl;
   }
@@ -3327,17 +3478,17 @@ TEST_F(WebRtcIceConnectTest, DISABLED_Te
 
 // TODO Bug 1226838 - See Comment 2 - this test can't work as written
 TEST_F(WebRtcIceConnectTest, DISABLED_TestSrflxCandPairingFilter) {
   if (stun_server_address_.empty()) {
     return;
   }
 
   Init(false, false, false, false);
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsSrflxCandidate);
 
   if (p1_->GetCandidatesPrivateIpv4Range(0) <= 0) {
     // TODO bug 1226838: make this work with public IP addresses
     std::cerr << "Don't run this test at IETF meetings!" << std::endl;
     FAIL() << "This test needs one private IPv4 host candidate to work" << std::endl;
   }
@@ -3365,17 +3516,17 @@ TEST_F(WebRtcIceConnectTest, DISABLED_Te
     nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
     ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0);
     nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
     ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0);
   }
 }
 
 TEST_F(WebRtcIceConnectTest, TestPollCandPairsDuringConnect) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 
   p2_->Connect(p1_.get(), TRICKLE_NONE, false);
   p1_->Connect(p2_.get(), TRICKLE_NONE, false);
 
   std::vector<NrIceCandidatePair> pairs1;
   std::vector<NrIceCandidatePair> pairs2;
 
@@ -3390,17 +3541,17 @@ TEST_F(WebRtcIceConnectTest, TestPollCan
   WaitForComplete();
   p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
   p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
   ASSERT_TRUE(ContainsSucceededPair(pairs1));
   ASSERT_TRUE(ContainsSucceededPair(pairs2));
 }
 
 TEST_F(WebRtcIceConnectTest, TestRLogRingBuffer) {
-  AddStream("first", 1);
+  AddStream(1);
   ASSERT_TRUE(Gather());
 
   p2_->Connect(p1_.get(), TRICKLE_NONE, false);
   p1_->Connect(p2_.get(), TRICKLE_NONE, false);
 
   std::vector<NrIceCandidatePair> pairs1;
   std::vector<NrIceCandidatePair> pairs2;
 
--- a/media/mtransport/test_nr_socket.cpp
+++ b/media/mtransport/test_nr_socket.cpp
@@ -79,16 +79,17 @@ nrappkit copyright:
 */
 
 // 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 "async_timer.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"
@@ -194,17 +195,18 @@ int TestNat::create_socket_factory(nr_so
                                        factorypp);
   if (!r) {
     AddRef();
   }
   return r;
 }
 
 TestNrSocket::TestNrSocket(TestNat *nat)
-  : nat_(nat) {
+  : nat_(nat),
+    timer_handle_(nullptr) {
   nat_->insert_socket(this);
 }
 
 TestNrSocket::~TestNrSocket() {
   nat_->erase_socket(this);
 }
 
 RefPtr<NrSocketBase> TestNrSocket::create_external_socket(
@@ -247,16 +249,20 @@ int TestNrSocket::create(nr_transport_ad
   return NrSocketBase::CreateSocket(addr, &internal_socket_);
 }
 
 int TestNrSocket::getaddr(nr_transport_addr *addrp) {
   return internal_socket_->getaddr(addrp);
 }
 
 void TestNrSocket::close() {
+  if (timer_handle_) {
+    NR_async_timer_cancel(timer_handle_);
+    timer_handle_ = 0;
+  }
   internal_socket_->close();
   for (RefPtr<PortMapping>& port_mapping : port_mappings_) {
     port_mapping->external_socket_->close();
   }
 }
 
 int TestNrSocket::listen(int backlog) {
   MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
@@ -277,21 +283,49 @@ int TestNrSocket::accept(nr_transport_ad
   if (nat_->enabled_ && !nat_->is_an_internal_tuple(*addrp)) {
     nr_socket_destroy(sockp);
     return R_IO_ERROR;
   }
 
   return 0;
 }
 
+void TestNrSocket::process_delayed_cb(NR_SOCKET s, int how, void *cb_arg) {
+  DeferredPacket *op = static_cast<DeferredPacket *>(cb_arg);
+  op->socket_->timer_handle_ = nullptr;
+  r_log(LOG_GENERIC, LOG_DEBUG,
+        "TestNrSocket %s sending delayed STUN response",
+        op->internal_socket_->my_addr().as_string);
+  op->internal_socket_->sendto(op->buffer_.data(), op->buffer_.len(),
+                               op->flags_, &op->to_);
+
+  delete op;
+}
+
 int TestNrSocket::sendto(const void *msg, size_t len,
                          int flags, nr_transport_addr *to) {
   MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
 
+  UCHAR *buf = static_cast<UCHAR*>(const_cast<void*>(msg));
+  if (nat_->block_stun_ &&
+      nr_is_stun_message(buf, len)) {
+    return 0;
+  }
+
+  /* TODO: improve the functionality of this in bug 1253657 */
   if (!nat_->enabled_ || nat_->is_an_internal_tuple(*to)) {
+    if (nat_->delay_stun_resp_ms_ &&
+        nr_is_stun_response_message(buf, len)) {
+      NR_ASYNC_TIMER_SET(nat_->delay_stun_resp_ms_,
+                         process_delayed_cb,
+                         new DeferredPacket(this, msg, len, flags, to,
+                                            internal_socket_),
+                         &timer_handle_);
+      return 0;
+    }
     return internal_socket_->sendto(msg, len, flags, to);
   }
 
   destroy_stale_port_mappings();
 
   if (to->protocol == IPPROTO_UDP && nat_->block_udp_) {
     // Silently eat the packet
     return 0;
--- a/media/mtransport/test_nr_socket.h
+++ b/media/mtransport/test_nr_socket.h
@@ -132,16 +132,18 @@ class TestNat {
     TestNat() :
       enabled_(false),
       filtering_type_(ENDPOINT_INDEPENDENT),
       mapping_type_(ENDPOINT_INDEPENDENT),
       mapping_timeout_(30000),
       allow_hairpinning_(false),
       refresh_on_ingress_(false),
       block_udp_(false),
+      block_stun_(false),
+      delay_stun_resp_ms_(0),
       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;
 
@@ -161,16 +163,19 @@ class 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_;
+    bool block_stun_;
+    /* Note: this can only delay a single response so far (bug 1253657) */
+    uint32_t delay_stun_resp_ms_;
 
   private:
     std::set<TestNrSocket*> sockets_;
 
     ~TestNat(){}
 };
 
 /**
@@ -253,16 +258,36 @@ class TestNrSocket : public NrSocketBase
         }
 
         // 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<RefPtr<UdpPacket>> send_queue_;
     };
 
+    struct DeferredPacket {
+      DeferredPacket(TestNrSocket *sock,
+                     const void *data, size_t len,
+                     int flags,
+                     nr_transport_addr *addr,
+                     RefPtr<NrSocketBase> internal_socket) :
+          socket_(sock),
+          buffer_(reinterpret_cast<const uint8_t *>(data), len),
+          flags_(flags),
+          internal_socket_(internal_socket) {
+        nr_transport_addr_copy(&to_, addr);
+      }
+
+      TestNrSocket *socket_;
+      DataBuffer buffer_;
+      int flags_;
+      nr_transport_addr to_;
+      RefPtr<NrSocketBase> internal_socket_;
+    };
+
     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 socket_readable_callback(void *real_sock_v,
                                          int how,
                                          void *test_sock_v);
@@ -283,24 +308,28 @@ class TestNrSocket : public NrSocketBase
     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 RefPtr<NrSocketBase> &external_socket) const;
     RefPtr<NrSocketBase> create_external_socket(
         const nr_transport_addr &remote_addr) const;
 
+    static void process_delayed_cb(NR_SOCKET s, int how, void *cb_arg);
+
     RefPtr<NrSocketBase> readable_socket_;
     // The socket for the "internal" address; used to talk to stuff behind the
     // same nat.
     RefPtr<NrSocketBase> internal_socket_;
     RefPtr<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<RefPtr<PortMapping>> port_mappings_;
+
+    void *timer_handle_;
 };
 
 } // namespace mozilla
 
 #endif // test_nr_socket__
 
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
@@ -120,17 +120,16 @@ int nr_ice_candidate_pair_create(nr_ice_
       lcand->osock,
       &rcand->addr,RTO,&pair->stun_client))
       ABORT(r);
     if(!(pair->stun_client->params.ice_binding_request.username=r_strdup(rcand->stream->l2r_user)))
       ABORT(R_NO_MEMORY);
     if(r=r_data_copy(&pair->stun_client->params.ice_binding_request.password,
       &rcand->stream->l2r_pass))
       ABORT(r);
-    pair->stun_client->params.ice_binding_request.priority=t_priority;
     /* TODO(ekr@rtfm.com): Do we need to frob this when we change role. Bug 890667 */
     pair->stun_client->params.ice_binding_request.control = pctx->controlling?
       NR_ICE_CONTROLLING:NR_ICE_CONTROLLED;
     pair->stun_client->params.ice_binding_request.priority=t_priority;
 
     pair->stun_client->params.ice_binding_request.tiebreaker=pctx->tiebreaker;
 
     *pairp=pair;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -44,19 +44,22 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include "stun.h"
 #include "nr_socket_local.h"
 #include "nr_socket_turn.h"
 #include "nr_socket_wrapper.h"
 #include "nr_socket_buffered_stun.h"
 #include "nr_socket_multi_tcp.h"
 #include "ice_reg.h"
 #include "nr_crypto.h"
+#include "r_time.h"
 
 static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_ctx *stun_ctx,nr_socket *sock, nr_stun_server_request *req, int *dont_free, int *error);
 static int nr_ice_pre_answer_request_destroy(nr_ice_pre_answer_request **parp);
+void nr_ice_component_consent_schedule_consent_timer(nr_ice_component *comp);
+void nr_ice_component_consent_destroy(nr_ice_component *comp);
 
 /* This function takes ownership of the contents of req (but not req itself) */
 static int nr_ice_pre_answer_request_create(nr_socket *sock, nr_stun_server_request *req, nr_ice_pre_answer_request **parp)
   {
     int r, _status;
     nr_ice_pre_answer_request *par = 0;
     nr_stun_message_attribute *attr;
 
@@ -136,16 +139,18 @@ int nr_ice_component_destroy(nr_ice_comp
     nr_ice_pre_answer_request *r1,*r2;
 
     if(!componentp || !*componentp)
       return(0);
 
     component=*componentp;
     *componentp=0;
 
+    nr_ice_component_consent_destroy(component);
+
     /* Detach ourselves from the sockets */
     if (component->local_component){
       nr_ice_socket *isock=STAILQ_FIRST(&component->local_component->sockets);
       while(isock){
         nr_stun_server_remove_client(isock->stun_server, component);
         isock=STAILQ_NEXT(isock, entry);
       }
     }
@@ -162,20 +167,16 @@ int nr_ice_component_destroy(nr_ice_comp
       nr_ice_socket_destroy(&s1);
     }
 
     STAILQ_FOREACH_SAFE(r1, &component->pre_answer_reqs, entry, r2){
       STAILQ_REMOVE(&component->pre_answer_reqs,r1,nr_ice_pre_answer_request_, entry);
       nr_ice_pre_answer_request_destroy(&r1);
     }
 
-    if(component->keepalive_timer)
-      NR_async_timer_cancel(component->keepalive_timer);
-    nr_stun_client_ctx_destroy(&component->keepalive_ctx);
-
     RFREE(component);
     return(0);
   }
 
 static int nr_ice_component_create_stun_server_ctx(nr_ice_component *component, nr_ice_socket *isock, nr_socket *sock, nr_transport_addr *addr, char *lufrag, Data *pwd)
   {
     char label[256];
     int r,_status;
@@ -1149,26 +1150,224 @@ static int nr_ice_component_stun_server_
     *dont_free = 1;
     STAILQ_INSERT_TAIL(&comp->pre_answer_reqs, par, entry);
 
     _status=0;
  abort:
     return(_status);
   }
 
+#define NR_ICE_CONSENT_TIMER_DEFAULT 5000
+#define NR_ICE_CONSENT_TIMEOUT_DEFAULT 30000
+
+static void nr_ice_component_consent_failed(nr_ice_component *comp)
+  {
+    if (!comp->can_send) {
+      return;
+    }
+
+    r_log(LOG_ICE,LOG_INFO,"ICE(%s)/STREAM(%s)/COMP(%d): Consent refresh failed",
+          comp->ctx->label, comp->stream->label, comp->component_id);
+    comp->can_send = 0;
+
+    if (comp->consent_timeout) {
+      NR_async_timer_cancel(comp->consent_timeout);
+      comp->consent_timeout = 0;
+    }
+    if (comp->consent_timer) {
+      NR_async_timer_cancel(comp->consent_timer);
+      comp->consent_timer = 0;
+    }
+    /* We are turning the consent failure into a ICE component failure to
+     * alert the browser via ICE connection state change about this event. */
+    if (nr_ice_media_stream_component_failed(comp->stream, comp))
+      r_log(LOG_ICE,LOG_ERR,"ICE(%s)/STREAM(%s)/COMP(%d): failed to mark component as failed",
+        comp->ctx->label, comp->stream->label, comp->component_id);
+  }
+
+static void nr_ice_component_consent_timeout_cb(NR_SOCKET s, int how, void *cb_arg)
+  {
+    nr_ice_component *comp=cb_arg;
+
+    comp->consent_timeout = 0;
+
+    r_log(LOG_ICE,LOG_WARNING,"ICE(%s)/STREAM(%s)/COMP(%d): Consent refresh timed out",
+          comp->ctx->label, comp->stream->label, comp->component_id);
+    nr_ice_component_consent_failed(comp);
+  }
+
+static void nr_ice_component_consent_refreshed(nr_ice_component *comp)
+  {
+    uint16_t tval;
+
+    if (!comp->can_send) {
+      return;
+    }
+
+    gettimeofday(&comp->consent_last_seen, 0);
+    r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): consent_last_seen is now %lu",
+        comp->ctx->label, comp->stream->label, comp->component_id,
+        comp->consent_last_seen.tv_sec);
+    if (comp->consent_timeout)
+      NR_async_timer_cancel(comp->consent_timeout);
+
+    tval = NR_ICE_CONSENT_TIMEOUT_DEFAULT;
+    if (comp->ctx->test_timer_divider)
+      tval = tval / comp->ctx->test_timer_divider;
+
+    NR_ASYNC_TIMER_SET(tval, nr_ice_component_consent_timeout_cb, comp,
+                       &comp->consent_timeout);
+  }
+
+static void nr_ice_component_refresh_consent_cb(NR_SOCKET s, int how, void *cb_arg)
+  {
+    nr_ice_component *comp=cb_arg;
+
+    switch (comp->consent_ctx->state) {
+      case NR_STUN_CLIENT_STATE_FAILED:
+        if (comp->consent_ctx->error_code == 403) {
+          r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): Consent revoked by peer",
+                comp->ctx->label, comp->stream->label, comp->component_id);
+          nr_ice_component_consent_failed(comp);
+        }
+        break;
+      case NR_STUN_CLIENT_STATE_DONE:
+        r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): Consent refreshed",
+              comp->ctx->label, comp->stream->label, comp->component_id);
+        nr_ice_component_consent_refreshed(comp);
+        break;
+      default:
+        break;
+    }
+  }
+
+int nr_ice_component_refresh_consent(nr_stun_client_ctx *ctx, NR_async_cb finished_cb, void *cb_arg)
+  {
+    int r,_status;
+
+    nr_stun_client_reset(ctx);
+
+    if (r=nr_stun_client_start(ctx, NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH, finished_cb, cb_arg))
+      ABORT(r);
+
+    _status=0;
+  abort:
+    return(_status);
+  }
+
+static void nr_ice_component_consent_timer_cb(NR_SOCKET s, int how, void *cb_arg)
+  {
+    nr_ice_component *comp=cb_arg;
+    int r;
+
+    comp->consent_timer = 0;
+
+    if (r=nr_ice_component_refresh_consent(comp->consent_ctx,
+                                           nr_ice_component_refresh_consent_cb,
+                                           comp)) {
+      r_log(LOG_ICE,LOG_ERR,"ICE(%s)/STREAM(%s)/COMP(%d): Refresh consent failed with %d",
+            comp->ctx->label, comp->stream->label, comp->component_id, r);
+      /* In case our attempt to send the refresh binding request reports an
+       * error we don't have to wait for timeouts, but declare this connection
+       * dead right away. */
+      if (r != R_WOULDBLOCK) {
+        nr_ice_component_consent_failed(comp);
+      }
+    }
+
+    nr_ice_component_consent_schedule_consent_timer(comp);
+
+  }
+
+void nr_ice_component_consent_schedule_consent_timer(nr_ice_component *comp)
+  {
+    uint16_t trange, trand, tval;
+    void *buf = &trand;
+
+    trange = NR_ICE_CONSENT_TIMER_DEFAULT / 100 * 20;
+    tval = NR_ICE_CONSENT_TIMER_DEFAULT - trange;
+    if (!nr_crypto_random_bytes(buf, sizeof(trand)))
+      tval += (trand % (trange * 2));
+
+    if (comp->ctx->test_timer_divider)
+      tval = tval / comp->ctx->test_timer_divider;
+
+    NR_ASYNC_TIMER_SET(tval, nr_ice_component_consent_timer_cb, comp,
+                       &comp->consent_timer);
+  }
+
+void nr_ice_component_consent_destroy(nr_ice_component *comp)
+  {
+    if (comp->consent_timer) {
+      NR_async_timer_cancel(comp->consent_timer);
+      comp->consent_timer = 0;
+    }
+    if (comp->consent_timeout) {
+      NR_async_timer_cancel(comp->consent_timeout);
+      comp->consent_timeout = 0;
+    }
+    if (comp->consent_handle) {
+      nr_ice_socket_deregister(comp->active->local->isock,
+                               comp->consent_handle);
+      comp->consent_handle = 0;
+    }
+    if (comp->consent_ctx) {
+      nr_stun_client_ctx_destroy(&comp->consent_ctx);
+    }
+  }
+
+int nr_ice_component_setup_consent(nr_ice_component *comp)
+  {
+    int r,_status;
+
+    r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): Setting up refresh consent",
+          comp->ctx->label, comp->stream->label, comp->component_id);
+
+    if (r=nr_stun_client_ctx_create("consent", comp->active->local->osock,
+                                    &comp->active->remote->addr, 0,
+                                    &comp->consent_ctx))
+      ABORT(r);
+    /* Consent request get send only once. */
+    comp->consent_ctx->maximum_transmits = 1;
+    /* The timeout of the transaction is the maximum time until we send the
+     * next consent request.
+     * TODO: set this every time we calculate the new random timeout. */
+    comp->consent_ctx->maximum_transmits_timeout_ms = 6000;
+    comp->consent_ctx->params.stun_binding_request.username =
+      comp->active->remote->stream->l2r_user;
+    comp->consent_ctx->params.stun_binding_request.password =
+      &comp->active->remote->stream->l2r_pass;
+
+    if (r=nr_ice_socket_register_stun_client(comp->active->local->isock,
+            comp->consent_ctx, &comp->consent_handle))
+      ABORT(r);
+
+    comp->can_send = 1;
+    nr_ice_component_consent_refreshed(comp);
+
+    nr_ice_component_consent_schedule_consent_timer(comp);
+
+    _status=0;
+  abort:
+    return(_status);
+  }
+
 int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair)
   {
     int r,_status;
     nr_ice_cand_pair *p2;
 
     /* Are we changing what the nominated pair is? */
     if(comp->nominated){
       if(comp->nominated->priority >= pair->priority)
         return(0);
       r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): replacing pair %s with CAND-PAIR(%s)",comp->stream->pctx->label,comp->stream->label,comp->component_id,comp->nominated->codeword,comp->nominated->as_string,pair->codeword);
+      /* As consent doesn't hold a reference to its isock this needs to happen
+       * before making the new pair the active one. */
+      nr_ice_component_consent_destroy(comp);
     }
 
     /* Set the new nominated pair */
     r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): nominated pair is %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->codeword,pair->as_string);
     comp->state=NR_ICE_COMPONENT_NOMINATED;
     comp->nominated=pair;
     comp->active=pair;
 
@@ -1200,16 +1399,19 @@ int nr_ice_component_nominated_pair(nr_i
         if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2,0))
           ABORT(r);
       }
 
       p2=TAILQ_NEXT(p2,check_queue_entry);
     }
     r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): cancelling done",comp->stream->pctx->label,comp->stream->label,comp->component_id);
 
+    if(r=nr_ice_component_setup_consent(comp))
+      ABORT(r);
+
     if(r=nr_ice_media_stream_component_nominated(comp->stream,comp))
       ABORT(r);
 
     _status=0;
   abort:
     return(_status);
   }
 
@@ -1300,67 +1502,35 @@ int nr_ice_component_select_pair(nr_ice_
 
     _status=0;
   abort:
     RFREE(pairs);
     return(_status);
   }
 
 
-static void nr_ice_component_keepalive_cb(NR_SOCKET s, int how, void *cb_arg)
-  {
-    nr_ice_component *comp=cb_arg;
-    UINT4 keepalive_timeout;
-
-    assert(comp->keepalive_ctx);
-
-    if(NR_reg_get_uint4(NR_ICE_REG_KEEPALIVE_TIMER,&keepalive_timeout)){
-      keepalive_timeout=15000; /* Default */
-    }
-
-    if(comp->keepalive_needed)
-      nr_stun_client_force_retransmit(comp->keepalive_ctx);
-
-    comp->keepalive_needed=1;
-    NR_ASYNC_TIMER_SET(keepalive_timeout,nr_ice_component_keepalive_cb,cb_arg,&comp->keepalive_timer);
-  }
-
-
 /* Close the underlying sockets for everything but the nominated candidate */
 int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp)
   {
     nr_ice_socket *isock=0;
-    int r,_status;
     nr_ice_socket *s1,*s2;
 
     if(rcomp->state==NR_ICE_COMPONENT_NOMINATED){
       assert(rcomp->active == rcomp->nominated);
       isock=rcomp->nominated->local->isock;
     }
 
     STAILQ_FOREACH_SAFE(s1, &lcomp->sockets, entry, s2){
       if(s1!=isock){
         STAILQ_REMOVE(&lcomp->sockets,s1,nr_ice_socket_,entry);
         nr_ice_socket_destroy(&s1);
       }
     }
 
-    /* Set up the keepalives for the chosen socket */
-    if(r=nr_stun_client_ctx_create("keepalive",rcomp->nominated->local->osock,
-      &rcomp->nominated->remote->addr,0,&rcomp->keepalive_ctx))
-      ABORT(r);
-    if(r=nr_stun_client_start(rcomp->keepalive_ctx,NR_STUN_CLIENT_MODE_KEEPALIVE,0,0))
-      ABORT(r);
-    nr_ice_component_keepalive_cb(0,0,rcomp);
-
-
-    _status=0;
-  abort:
-
-    return(_status);
+    return(0);
   }
 
 
 int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair)
   {
     int r,_status;
 
     /* Pairs for peer reflexive are marked SUCCEEDED immediately */
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.h
@@ -65,19 +65,22 @@ struct nr_ice_component_ {
   nr_ice_candidate_head candidates;
   int candidate_ct;
   nr_ice_pre_answer_request_head pre_answer_reqs;
 
   int valid_pairs;
   struct nr_ice_cand_pair_ *nominated; /* Highest priority nomninated pair */
   struct nr_ice_cand_pair_ *active;
 
-  int keepalive_needed;
-  void *keepalive_timer;
-  nr_stun_client_ctx *keepalive_ctx;
+  nr_stun_client_ctx *consent_ctx;
+  void *consent_timer;
+  void *consent_timeout;
+  void *consent_handle;
+  int can_send;
+  struct timeval consent_last_seen;
 
   STAILQ_ENTRY(nr_ice_component_)entry;
 };
 
 typedef STAILQ_HEAD(nr_ice_component_head_,nr_ice_component_) nr_ice_component_head;
 
 int nr_ice_component_create(struct nr_ice_media_stream_ *stream, int component_id, nr_ice_component **componentp);
 int nr_ice_component_destroy(nr_ice_component **componentp);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -408,16 +408,18 @@ int nr_ice_ctx_create_with_credentials(c
         ABORT(r);
       }
     }
 #endif /* USE_TURN */
 
 
     ctx->Ta = 20;
 
+    ctx->test_timer_divider = 0;
+
     if (r=nr_socket_factory_create_int(NULL, &default_socket_factory_vtbl, &ctx->socket_factory))
       ABORT(r);
 
     if ((r=NR_reg_get_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, ctx->force_net_interface, sizeof(ctx->force_net_interface)))) {
       if (r == R_NOT_FOUND) {
         ctx->force_net_interface[0] = 0;
       } else {
         ABORT(r);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -137,16 +137,18 @@ struct nr_ice_ctx_ {
   nr_ice_media_stream_head streams;           /* Media streams */
   int stream_ct;
   nr_ice_socket_head sockets;                 /* The sockets we're using */
   int uninitialized_candidates;
 
   UINT4 gather_rto;
   UINT4 stun_delay;
 
+  UINT4 test_timer_divider;
+
   nr_ice_peer_ctx_head peers;
   nr_ice_stun_id_head ids;
 
   NR_async_cb done_cb;
   void *cb_arg;
 
   nr_ice_trickle_candidate_cb trickle_cb;
   void *trickle_cb_arg;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -733,21 +733,24 @@ int nr_ice_media_stream_send(nr_ice_peer
     /* First find the peer component */
     if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
       ABORT(r);
 
     /* Do we have an active pair yet? We should... */
     if(!comp->active)
       ABORT(R_NOT_FOUND);
 
+    /* Does fresh ICE consent exist? */
+    if(!comp->can_send)
+      ABORT(R_FAILED);
+
     /* OK, write to that pair, which means:
        1. Use the socket on our local side.
        2. Use the address on the remote side
     */
-    comp->keepalive_needed=0; /* Suppress keepalives */
     if(r=nr_socket_sendto(comp->active->local->osock,data,len,0,
                           &comp->active->remote->addr))
       ABORT(r);
 
     _status=0;
   abort:
     return(_status);
   }
@@ -841,16 +844,33 @@ int nr_ice_media_stream_pair_new_trickle
     if (r=nr_ice_component_pair_candidate(pctx, comp, cand, 1))
       ABORT(r);
 
     _status=0;
   abort:
     return(_status);
   }
 
+int nr_ice_media_stream_get_consent_status(nr_ice_media_stream *stream, int
+component_id, int *can_send, struct timeval *ts)
+  {
+    int r,_status;
+    nr_ice_component *comp;
+
+    if ((r=nr_ice_media_stream_find_component(stream, component_id, &comp)))
+      ABORT(r);
+
+    *can_send = comp->can_send;
+    ts->tv_sec = comp->consent_last_seen.tv_sec;
+    ts->tv_usec = comp->consent_last_seen.tv_usec;
+    _status=0;
+  abort:
+    return(_status);
+  }
+
 int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id)
   {
     int r,_status;
     nr_ice_component *comp;
 
     if (stream->ice_state != NR_ICE_MEDIA_STREAM_UNPAIRED)
       ABORT(R_FAILED);
 
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -90,16 +90,17 @@ int nr_ice_media_stream_component_failed
 int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
 int nr_ice_media_stream_get_best_candidate(nr_ice_media_stream *str, int component, nr_ice_candidate **candp);
 int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len);
 int nr_ice_media_stream_get_active(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_ice_candidate **local, nr_ice_candidate **remote);
 int nr_ice_media_stream_find_component(nr_ice_media_stream *str, int comp_id, nr_ice_component **compp);
 int nr_ice_media_stream_addrs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_transport_addr *local, nr_transport_addr *remote);
 int
 nr_ice_peer_ctx_parse_media_stream_attribute(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *attr);
+int nr_ice_media_stream_get_consent_status(nr_ice_media_stream *stream, int component_id, int *can_send, struct timeval *ts);
 int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id);
 int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, nr_ice_candidate *cand);
 void nr_ice_media_stream_role_change(nr_ice_media_stream *stream);
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 #endif
--- a/media/mtransport/third_party/nICEr/src/net/transport_addr.c
+++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.c
@@ -50,17 +50,17 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #endif
 #include <assert.h>
 #include "nr_api.h"
 #include "transport_addr.h"
 
 int nr_transport_addr_fmt_addr_string(nr_transport_addr *addr)
   {
     int _status;
-    /* Max length for normalized IPv6 address string represntation is 39 */
+    /* Max length for normalized IPv6 address string representation is 39 */
     char buffer[40];
     const char *protocol;
 
     switch(addr->protocol){
       case IPPROTO_TCP:
         protocol = "TCP";
         break;
       case IPPROTO_UDP:
@@ -464,9 +464,12 @@ int nr_transport_addr_get_private_addr_r
         return(0);
       default:
         UNIMPLEMENTED;
     }
 
     return(0);
   }
 
-
+int nr_transport_addr_is_reliable_transport(nr_transport_addr *addr)
+  {
+    return addr->protocol == IPPROTO_TCP;
+  }
--- a/media/mtransport/third_party/nICEr/src/net/transport_addr.h
+++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.h
@@ -92,11 +92,12 @@ int nr_transport_addr_is_wildcard(nr_tra
 int nr_transport_addr_is_loopback(nr_transport_addr *addr);
 int nr_transport_addr_get_private_addr_range(nr_transport_addr *addr);
 int nr_transport_addr_is_link_local(nr_transport_addr *addr);
 int nr_transport_addr_copy(nr_transport_addr *to, nr_transport_addr *from);
 int nr_transport_addr_copy_keep_ifname(nr_transport_addr *to, nr_transport_addr *from);
 int nr_transport_addr_fmt_addr_string(nr_transport_addr *addr);
 int nr_transport_addr_fmt_ifname_addr_string(const nr_transport_addr *addr, char *buf, int len);
 int nr_transport_addr_set_port(nr_transport_addr *addr, int port);
+int nr_transport_addr_is_reliable_transport(nr_transport_addr *addr);
 
 #endif