Bug 906986 - Ice restart and tests. r=bwc, r=drno, r=smaug
authorMichael Froman <mfroman@mozilla.com>
Fri, 08 Apr 2016 09:20:53 -0500
changeset 316253 651e8c7355bad2c551e99676a922dec0cb7486da
parent 316252 f6dd0039f260e8082895f3f1beb5e4306f254092
child 316254 dbde614eac1bd8fe2a793e45a19716b2a5ac5c0b
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbwc, drno, smaug
bugs906986
milestone48.0a1
Bug 906986 - Ice restart and tests. r=bwc, r=drno, r=smaug MozReview-Commit-ID: AMEi7SZebBG MozReview-Commit-ID: GS2EkamNGc7
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/test_peerConnection_restartIce.html
dom/media/tests/mochitest/test_peerConnection_restartIceLocalAndRemoteRollback.html
dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollback.html
dom/media/tests/mochitest/test_peerConnection_restartIceNoBundle.html
dom/media/tests/mochitest/test_peerConnection_restartIceNoBundleNoRtcpMux.html
dom/media/tests/mochitest/test_peerConnection_restartIceNoRtcpMux.html
dom/webidl/RTCPeerConnection.webidl
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/nricectxhandler.cpp
media/mtransport/nricectxhandler.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/multi_tcp_socket_unittest.cpp
media/mtransport/test/transport_unittests.cpp
media/mtransport/test/turn_unittest.cpp
media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
media/mtransport/transportlayerice.cpp
media/mtransport/transportlayerice.h
media/webrtc/signaling/src/jsep/JsepSession.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/src/sdp/SdpHelper.cpp
media/webrtc/signaling/src/sdp/SdpHelper.h
media/webrtc/signaling/test/signaling_unittests.cpp
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -147,16 +147,28 @@ skip-if = toolkit == 'gonk' # B2G emulat
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_offerRequiresReceiveAudio.html]
 [test_peerConnection_offerRequiresReceiveVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideoAudio.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_promiseSendOnly.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
+[test_peerConnection_restartIce.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g (Bug 1059867)
+[test_peerConnection_restartIceNoBundle.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g (Bug 1059867)
+[test_peerConnection_restartIceNoBundleNoRtcpMux.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g (Bug 1059867)
+[test_peerConnection_restartIceNoRtcpMux.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g (Bug 1059867)
+[test_peerConnection_restartIceLocalRollback.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g (Bug 1059867)
+[test_peerConnection_restartIceLocalAndRemoteRollback.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g (Bug 1059867)
 [test_peerConnection_scaleResolution.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18') # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_simulcastOffer.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version # b2g(Bug 960442, video support for WebRTC is disabled on b2g), no simulcast support on android
 #[test_peerConnection_relayOnly.html]
 #skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_callbacks.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -724,16 +724,17 @@ function PeerConnectionWrapper(label, co
 
   this.expectedLocalTrackInfoById = {};
   this.expectedRemoteTrackInfoById = {};
   this.observedRemoteTrackInfoById = {};
 
   this.disableRtpCountChecking = false;
 
   this.iceCheckingRestartExpected = false;
+  this.iceCheckingIceRollbackExpected = false;
 
   info("Creating " + this);
   this._pc = new RTCPeerConnection(this.configuration);
 
   /**
    * Setup callback handlers
    */
   // This allows test to register their own callbacks for ICE connection state changes
@@ -1209,16 +1210,21 @@ PeerConnectionWrapper.prototype = {
       var newstate = this._pc.iceConnectionState;
       var oldstate = this.iceConnectionLog[this.iceConnectionLog.length - 1]
       if (Object.keys(iceStateTransitions).indexOf(oldstate) != -1) {
         if (this.iceCheckingRestartExpected) {
           is(newstate, "checking",
              "iceconnectionstate event \'" + newstate +
              "\' matches expected state \'checking\'");
           this.iceCheckingRestartExpected = false;
+        } else if (this.iceCheckingIceRollbackExpected) {
+          is(newstate, "connected",
+             "iceconnectionstate event \'" + newstate +
+             "\' matches expected state \'connected\'");
+          this.iceCheckingIceRollbackExpected = false;
         } else {
           ok(iceStateTransitions[oldstate].indexOf(newstate) != -1, this + ": legal ICE state transition from " + oldstate + " to " + newstate);
         }
       } else {
         ok(false, this + ": old ICE state " + oldstate + " missing in ICE transition array");
       }
       this.iceConnectionLog.push(newstate);
     };
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIce.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "906986",
+    title: "Renegotiation: restart ice"
+  });
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+
+    addRenegotiation(test.chain,
+      [
+        // causes a full, normal ice restart
+        function PC_LOCAL_SET_OFFER_OPTION(test) {
+          test.setOfferOptions({ iceRestart: true });
+        },
+        function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+          test.pcLocal.iceCheckingRestartExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+          test.pcRemote.iceCheckingRestartExpected = true;
+        }
+      ]
+    );
+
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceLocalAndRemoteRollback.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "906986",
+    title: "Renegotiation: restart ice, local and remote rollback"
+  });
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    var firstNegotiationSize = test.chain.commands.length;
+
+    addRenegotiation(test.chain,
+      [
+        // causes a full, normal ice restart
+        function PC_LOCAL_SET_OFFER_OPTION(test) {
+          test.setOfferOptions({ iceRestart: true });
+        }
+      ]
+    );
+
+    test.chain.replaceAfter('PC_REMOTE_CREATE_ANSWER',
+      [
+        function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+          test.pcLocal.setupIceCandidateHandler(test);
+          if (test.testOptions.steeplechase) {
+            test.pcLocal.endOfTrickleIce.then(() => {
+              send_message({"type": "end_of_trickle_ice"});
+            });
+          }
+        },
+        function PC_REMOTE_SETUP_ICE_HANDLER(test) {
+          test.pcRemote.setupIceCandidateHandler(test);
+          if (test.testOptions.steeplechase) {
+            test.pcRemote.endOfTrickleIce.then(() => {
+              send_message({"type": "end_of_trickle_ice"});
+            });
+          }
+        },
+
+        function PC_LOCAL_EXPECT_ICE_CONNECTED(test) {
+          test.pcLocal.iceCheckingIceRollbackExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CONNECTED(test) {
+          test.pcRemote.iceCheckingIceRollbackExpected = true;
+        },
+
+        function PC_REMOTE_ROLLBACK(test) {
+          return test.setRemoteDescription(
+              test.pcRemote,
+              new RTCSessionDescription({ type: "rollback" }),
+              STABLE);
+        },
+
+        function PC_LOCAL_ROLLBACK(test) {
+          // We haven't negotiated the new stream yet.
+          test.pcLocal.expectNegotiationNeeded();
+          return test.setLocalDescription(
+              test.pcLocal,
+              new RTCSessionDescription({ type: "rollback", sdp: ""}),
+              STABLE);
+        },
+
+        // Rolling back should shut down gathering
+        function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+          return test.pcLocal.endOfTrickleIce;
+        },
+        function PC_REMOTE_WAIT_FOR_END_OF_TRICKLE(test) {
+          return test.pcRemote.endOfTrickleIce;
+        },
+
+        function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+          test.pcLocal.iceCheckingRestartExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+          test.pcRemote.iceCheckingRestartExpected = true;
+        }
+      ],
+      firstNegotiationSize // Replaces after second PC_REMOTE_CREATE_ANSWER
+    );
+    test.chain.append(commandsPeerConnectionOfferAnswer);
+
+    // for now, only use one stream, because rollback doesn't seem to
+    // like multiple streams.  See bug 1259465.
+    test.setMediaConstraints([{audio: true}],
+                             [{audio: true}]);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollback.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "906986",
+    title: "Renegotiation: restart ice, local rollback"
+  });
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+
+    addRenegotiation(test.chain,
+      [
+        // causes a full, normal ice restart
+        function PC_LOCAL_SET_OFFER_OPTION(test) {
+          test.setOfferOptions({ iceRestart: true });
+        },
+        // causes an ice restart and then rolls it back
+        // (does not result in sending an offer)
+        function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+          test.pcLocal.setupIceCandidateHandler(test);
+          if (test.testOptions.steeplechase) {
+            test.pcLocal.endOfTrickleIce.then(() => {
+              send_message({"type": "end_of_trickle_ice"});
+            });
+          }
+        },
+        function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
+          return test.createOffer(test.pcLocal).then(offer => {
+            return test.setLocalDescription(test.pcLocal,
+                                            offer,
+                                            HAVE_LOCAL_OFFER);
+          });
+        },
+        function PC_LOCAL_EXPECT_ICE_CONNECTED(test) {
+          test.pcLocal.iceCheckingIceRollbackExpected = true;
+        },
+        function PC_LOCAL_ROLLBACK(test) {
+          return test.setLocalDescription(
+              test.pcLocal,
+              new RTCSessionDescription({ type: "rollback",
+                                          sdp: ""}),
+              STABLE);
+        },
+        // Rolling back should shut down gathering
+        function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+          return test.pcLocal.endOfTrickleIce;
+        },
+        function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+          test.pcLocal.iceCheckingRestartExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+          test.pcRemote.iceCheckingRestartExpected = true;
+        }
+      ]
+    );
+
+    // for now, only use one stream, because rollback doesn't seem to
+    // like multiple streams.  See bug 1259465.
+    test.setMediaConstraints([{audio: true}],
+                             [{audio: true}]);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceNoBundle.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "906986",
+    title: "Renegotiation: restart ice, no bundle"
+  });
+
+  var test;
+  runNetworkTest(function (options) {
+    options = options || { };
+    options.bundle = false;
+    test = new PeerConnectionTest(options);
+
+    addRenegotiation(test.chain,
+      [
+        // causes a full, normal ice restart
+        function PC_LOCAL_SET_OFFER_OPTION(test) {
+          test.setOfferOptions({ iceRestart: true });
+        },
+        function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+          test.pcLocal.iceCheckingRestartExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+          test.pcRemote.iceCheckingRestartExpected = true;
+        }
+      ]
+    );
+
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceNoBundleNoRtcpMux.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "906986",
+    title: "Renegotiation: restart ice, no bundle and disabled RTCP-Mux"
+  });
+
+  var test;
+  runNetworkTest(function (options) {
+    options = options || { };
+    options.bundle = false;
+    options.rtcpmux = false;
+    test = new PeerConnectionTest(options);
+
+    addRenegotiation(test.chain,
+      [
+        // causes a full, normal ice restart
+        function PC_LOCAL_SET_OFFER_OPTION(test) {
+          test.setOfferOptions({ iceRestart: true });
+        },
+        function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+          test.pcLocal.iceCheckingRestartExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+          test.pcRemote.iceCheckingRestartExpected = true;
+        }
+      ]
+    );
+
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceNoRtcpMux.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "906986",
+    title: "Renegotiation: restart ice, with disabled RTCP-Mux"
+  });
+
+  var test;
+  runNetworkTest(function (options) {
+    options = options || { };
+    options.rtcpmux = false;
+    test = new PeerConnectionTest(options);
+
+    addRenegotiation(test.chain,
+      [
+        // causes a full, normal ice restart
+        function PC_LOCAL_SET_OFFER_OPTION(test) {
+          test.setOfferOptions({ iceRestart: true });
+        },
+        function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+          test.pcLocal.iceCheckingRestartExpected = true;
+        },
+        function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+          test.pcRemote.iceCheckingRestartExpected = true;
+        }
+      ]
+    );
+
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -57,17 +57,17 @@ dictionary RTCOfferAnswerOptions {
 };
 
 dictionary RTCAnswerOptions : RTCOfferAnswerOptions {
 };
 
 dictionary RTCOfferOptions : RTCOfferAnswerOptions {
   long    offerToReceiveVideo;
   long    offerToReceiveAudio;
-  // boolean iceRestart = false; // Not implemented (Bug 906986)
+  boolean iceRestart = false;
 
   // Mozilla proprietary options (at risk: Bug 1196974)
   boolean mozDontOfferDataChannel;
   boolean mozBundleOnly;
 
   // TODO: Remove old constraint-like RTCOptions support soon (Bug 1064223).
   DeprecatedRTCOfferOptionsSet mandatory;
   sequence<DeprecatedRTCOfferOptionsSet> _optional;
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -393,19 +393,19 @@ void NrIceCtx::trickle_cb(void *arg, nr_
   MOZ_MTLOG(ML_INFO, "NrIceCtx(" << ctx->name_ << "): trickling candidate "
             << candidate_str);
 
   s->SignalCandidate(s, candidate_str);
 }
 
 
 void
-NrIceCtx::InitializeCryptoAndLogging(bool allow_loopback,
-                                     bool tcp_enabled,
-                                     bool allow_link_local) {
+NrIceCtx::InitializeGlobals(bool allow_loopback,
+                            bool tcp_enabled,
+                            bool allow_link_local) {
   // Initialize the crypto callbacks and logging stuff
   if (!initialized) {
     NR_reg_init(NR_REG_MODE_LOCAL);
     nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl;
     initialized = true;
 
     // Set the priorites for candidate type preferences.
     // These numbers come from RFC 5245 S. 4.1.2.2
@@ -469,57 +469,114 @@ NrIceCtx::InitializeCryptoAndLogging(boo
     if (force_net_interface.Length() > 0) {
       // Stupid cast.... but needed
       const nsCString& flat = PromiseFlatCString(static_cast<nsACString&>(force_net_interface));
       NR_reg_set_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, const_cast<char*>(flat.get()));
     }
   }
 }
 
+std::string
+NrIceCtx::GetNewUfrag()
+{
+  char* ufrag;
+  int r;
+
+  if ((r=nr_ice_get_new_ice_ufrag(&ufrag))) {
+    MOZ_CRASH("Unable to get new ice ufrag");
+    return "";
+  }
+
+  std::string ufragStr = ufrag;
+  RFREE(ufrag);
+
+  return ufragStr;
+}
+
+std::string
+NrIceCtx::GetNewPwd()
+{
+  char* pwd;
+  int r;
+
+  if ((r=nr_ice_get_new_ice_pwd(&pwd))) {
+    MOZ_CRASH("Unable to get new ice pwd");
+    return "";
+  }
+
+  std::string pwdStr = pwd;
+  RFREE(pwd);
+
+  return pwdStr;
+}
+
 bool
-NrIceCtx::Initialize(NrIceCtx* ctx,
-                     bool hide_non_default)
+NrIceCtx::Initialize(bool hide_non_default)
 {
+  std::string ufrag = GetNewUfrag();
+  std::string pwd = GetNewPwd();
+
+  return Initialize(hide_non_default,
+                    ufrag,
+                    pwd);
+}
+
+bool
+NrIceCtx::Initialize(bool hide_non_default,
+                     const std::string& ufrag,
+                     const std::string& pwd)
+{
+  MOZ_ASSERT(!ufrag.empty());
+  MOZ_ASSERT(!pwd.empty());
+  if (ufrag.empty() || pwd.empty()) {
+    return false;
+  }
+
   // Create the ICE context
   int r;
 
-  UINT4 flags = ctx->offerer_ ? NR_ICE_CTX_FLAGS_OFFERER:
+  UINT4 flags = offerer_ ? NR_ICE_CTX_FLAGS_OFFERER:
       NR_ICE_CTX_FLAGS_ANSWERER;
   flags |= NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION;
-  if (ctx->policy_ == ICE_POLICY_RELAY) {
+  if (policy_ == ICE_POLICY_RELAY) {
     flags |= NR_ICE_CTX_FLAGS_RELAY_ONLY;
   }
 
   if (hide_non_default)
     flags |= NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS;
 
-  r = nr_ice_ctx_create(const_cast<char *>(ctx->name_.c_str()),
-                        flags,
-                        &ctx->ctx_);
+  r = nr_ice_ctx_create_with_credentials(const_cast<char *>(name_.c_str()),
+                                         flags,
+                                         const_cast<char *>(ufrag.c_str()),
+                                         const_cast<char *>(pwd.c_str()),
+                                         &ctx_);
+  MOZ_ASSERT(ufrag == ctx_->ufrag);
+  MOZ_ASSERT(pwd == ctx_->pwd);
+
   if (r) {
-    MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << ctx->name_ << "'");
+    MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << name_ << "'");
     return false;
   }
 
   nr_interface_prioritizer *prioritizer = CreateInterfacePrioritizer();
   if (!prioritizer) {
     MOZ_MTLOG(LogLevel::Error, "Couldn't create interface prioritizer.");
     return false;
   }
 
-  r = nr_ice_ctx_set_interface_prioritizer(ctx->ctx_, prioritizer);
+  r = nr_ice_ctx_set_interface_prioritizer(ctx_, prioritizer);
   if (r) {
     MOZ_MTLOG(LogLevel::Error, "Couldn't set interface prioritizer.");
     return false;
   }
 
-  if (ctx->generating_trickle()) {
-    r = nr_ice_ctx_set_trickle_cb(ctx->ctx_, &NrIceCtx::trickle_cb, ctx);
+  if (generating_trickle()) {
+    r = nr_ice_ctx_set_trickle_cb(ctx_, &NrIceCtx::trickle_cb, this);
     if (r) {
-      MOZ_MTLOG(ML_ERROR, "Couldn't set trickle cb for '" << ctx->name_ << "'");
+      MOZ_MTLOG(ML_ERROR, "Couldn't set trickle cb for '" << name_ << "'");
       return false;
     }
   }
 
   char* mapping_type = nullptr;
   char* filtering_type = nullptr;
   bool block_udp = false;
 
@@ -546,44 +603,44 @@ NrIceCtx::Initialize(NrIceCtx* ctx,
   if (mapping_type && filtering_type) {
     MOZ_MTLOG(ML_DEBUG, "NAT filtering type: " << filtering_type);
     MOZ_MTLOG(ML_DEBUG, "NAT mapping type: " << mapping_type);
     TestNat* test_nat = new TestNat;
     test_nat->filtering_type_ = TestNat::ToNatBehavior(filtering_type);
     test_nat->mapping_type_ = TestNat::ToNatBehavior(mapping_type);
     test_nat->block_udp_ = block_udp;
     test_nat->enabled_ = true;
-    ctx->SetNat(test_nat);
+    SetNat(test_nat);
   }
 
   // Create the handler objects
-  ctx->ice_handler_vtbl_ = new nr_ice_handler_vtbl();
-  ctx->ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair;
-  ctx->ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready;
-  ctx->ice_handler_vtbl_->stream_failed = &NrIceCtx::stream_failed;
-  ctx->ice_handler_vtbl_->ice_completed = &NrIceCtx::ice_completed;
-  ctx->ice_handler_vtbl_->msg_recvd = &NrIceCtx::msg_recvd;
-  ctx->ice_handler_vtbl_->ice_checking = &NrIceCtx::ice_checking;
+  ice_handler_vtbl_ = new nr_ice_handler_vtbl();
+  ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair;
+  ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready;
+  ice_handler_vtbl_->stream_failed = &NrIceCtx::stream_failed;
+  ice_handler_vtbl_->ice_completed = &NrIceCtx::ice_completed;
+  ice_handler_vtbl_->msg_recvd = &NrIceCtx::msg_recvd;
+  ice_handler_vtbl_->ice_checking = &NrIceCtx::ice_checking;
 
-  ctx->ice_handler_ = new nr_ice_handler();
-  ctx->ice_handler_->vtbl = ctx->ice_handler_vtbl_;
-  ctx->ice_handler_->obj = ctx;
+  ice_handler_ = new nr_ice_handler();
+  ice_handler_->vtbl = ice_handler_vtbl_;
+  ice_handler_->obj = this;
 
   // Create the peer ctx. Because we do not support parallel forking, we
   // only have one peer ctx.
-  std::string peer_name = ctx->name_ + ":default";
-  r = nr_ice_peer_ctx_create(ctx->ctx_, ctx->ice_handler_,
+  std::string peer_name = name_ + ":default";
+  r = nr_ice_peer_ctx_create(ctx_, ice_handler_,
                              const_cast<char *>(peer_name.c_str()),
-                             &ctx->peer_);
+                             &peer_);
   if (r) {
-    MOZ_MTLOG(ML_ERROR, "Couldn't create ICE peer ctx for '" << ctx->name_ << "'");
+    MOZ_MTLOG(ML_ERROR, "Couldn't create ICE peer ctx for '" << name_ << "'");
     return false;
   }
 
-  ctx->sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+  sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
 
   if (!NS_SUCCEEDED(rv))
     return false;
 
   return true;
 }
 
 int NrIceCtx::SetNat(const RefPtr<TestNat>& aNat) {
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -187,16 +187,17 @@ class NrIceProxyServer {
   std::string host_;
   uint16_t port_;
   std::string alpn_;
 };
 
 class TestNat;
 
 class NrIceCtx {
+ friend class NrIceCtxHandler;
  public:
   enum ConnectionState { ICE_CTX_INIT,
                          ICE_CTX_CHECKING,
                          ICE_CTX_OPEN,
                          ICE_CTX_FAILED
   };
 
   enum GatheringState { ICE_CTX_GATHER_INIT,
@@ -208,22 +209,27 @@ class NrIceCtx {
                      ICE_CONTROLLED
   };
 
   enum Policy { ICE_POLICY_NONE,
                 ICE_POLICY_RELAY,
                 ICE_POLICY_ALL
   };
 
-  static void InitializeCryptoAndLogging(bool allow_loopback = false,
-                                         bool tcp_enabled = true,
-                                         bool allow_link_local = false);
+  // initialize ICE globals, crypto, and logging
+  static void InitializeGlobals(bool allow_loopback = false,
+                                bool tcp_enabled = true,
+                                bool allow_link_local = false);
+  static std::string GetNewUfrag();
+  static std::string GetNewPwd();
 
-  static bool Initialize(NrIceCtx* ice_ctx,
-                         bool hide_non_default);
+  bool Initialize(bool hide_non_default);
+  bool Initialize(bool hide_non_default,
+                  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();
 
 
   nr_ice_ctx *ctx() { return ctx_; }
@@ -319,24 +325,23 @@ class NrIceCtx {
   sigslot::signal2<NrIceCtx*, NrIceCtx::ConnectionState>
     SignalConnectionStateChange;
 
   // The thread to direct method calls to
   nsCOMPtr<nsIEventTarget> thread() { return sts_target_; }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceCtx)
 
- protected:
-  virtual ~NrIceCtx();
-
+private:
   NrIceCtx(const std::string& name,
            bool offerer,
            Policy policy);
 
- private:
+  virtual ~NrIceCtx();
+
   DISALLOW_COPY_ASSIGN(NrIceCtx);
 
   // Callbacks for nICEr
   static void gather_cb(NR_SOCKET s, int h, void *arg);  // ICE gather complete
 
   // Handler implementation
   static int select_pair(void *obj,nr_ice_media_stream *stream,
                          int component_id, nr_ice_cand_pair **potentials,
@@ -355,17 +360,16 @@ class NrIceCtx {
   RefPtr<NrIceMediaStream> FindStream(nr_ice_media_stream *stream);
 
   // Set the state
   void SetConnectionState(ConnectionState state);
 
   // Set the state
   void SetGatheringState(GatheringState state);
 
-protected:
   ConnectionState connection_state_;
   GatheringState gathering_state_;
   const std::string name_;
   bool offerer_;
   std::vector<RefPtr<NrIceMediaStream> > streams_;
   nr_ice_ctx *ctx_;
   nr_ice_peer_ctx *peer_;
   nr_ice_handler_vtbl* ice_handler_vtbl_;  // Must be pointer
--- a/media/mtransport/nricectxhandler.cpp
+++ b/media/mtransport/nricectxhandler.cpp
@@ -1,52 +1,131 @@
 #include <sstream>
 
+#include "logging.h"
+
 // nICEr includes
 extern "C" {
 #include "nr_api.h"
 #include "ice_ctx.h"
 }
 
 // Local includes
 #include "nricectxhandler.h"
 #include "nricemediastream.h"
 
 namespace mozilla {
 
-NrIceCtxHandler::~NrIceCtxHandler()
+MOZ_MTLOG_MODULE("mtransport")
+
+NrIceCtxHandler::NrIceCtxHandler(const std::string& name,
+                                 bool offerer,
+                                 NrIceCtx::Policy policy)
+  : current_ctx(new NrIceCtx(name, offerer, policy)),
+    old_ctx(nullptr),
+    restart_count(0)
 {
 }
 
-
 RefPtr<NrIceCtxHandler>
 NrIceCtxHandler::Create(const std::string& name,
                         bool offerer,
                         bool allow_loopback,
                         bool tcp_enabled,
                         bool allow_link_local,
                         bool hide_non_default,
-                        Policy policy)
+                        NrIceCtx::Policy policy)
 {
-  // InitializeCryptoAndLogging only executes once
-  NrIceCtx::InitializeCryptoAndLogging(allow_loopback,
-                                       tcp_enabled,
-                                       allow_link_local);
+  // InitializeGlobals only executes once
+  NrIceCtx::InitializeGlobals(allow_loopback, tcp_enabled, allow_link_local);
 
   RefPtr<NrIceCtxHandler> ctx = new NrIceCtxHandler(name, offerer, policy);
 
-  if (!NrIceCtx::Initialize(ctx,
-                            hide_non_default)) {
+  if (ctx == nullptr ||
+      ctx->current_ctx == nullptr ||
+      !ctx->current_ctx->Initialize(hide_non_default)) {
     return nullptr;
   }
 
   return ctx;
 }
 
 
 RefPtr<NrIceMediaStream>
 NrIceCtxHandler::CreateStream(const std::string& name, int components)
 {
-  return NrIceMediaStream::Create(this, name, components);
+  // To make tracking NrIceMediaStreams easier during ICE restart
+  // prepend an int to the name that increments with each ICE restart
+  std::ostringstream os;
+  os << restart_count << "-" << name;
+  return NrIceMediaStream::Create(this->current_ctx, os.str(), components);
+}
+
+
+RefPtr<NrIceCtx>
+NrIceCtxHandler::CreateCtx(bool hide_non_default) const
+{
+  return CreateCtx(NrIceCtx::GetNewUfrag(),
+                   NrIceCtx::GetNewPwd(),
+                   hide_non_default);
+}
+
+
+RefPtr<NrIceCtx>
+NrIceCtxHandler::CreateCtx(const std::string& ufrag,
+                           const std::string& pwd,
+                           bool hide_non_default) const
+{
+  RefPtr<NrIceCtx> new_ctx = new NrIceCtx(this->current_ctx->name(),
+                                          true, // offerer (hardcoded per bwc)
+                                          this->current_ctx->policy());
+  if (new_ctx == nullptr) {
+    return nullptr;
+  }
+
+  if (!new_ctx->Initialize(hide_non_default, ufrag, pwd)) {
+    return nullptr;
+  }
+
+  return new_ctx;
+}
+
+
+bool
+NrIceCtxHandler::BeginIceRestart(RefPtr<NrIceCtx> new_ctx)
+{
+  MOZ_ASSERT(!old_ctx, "existing ice restart in progress");
+  if (old_ctx) {
+    MOZ_MTLOG(ML_ERROR, "Existing ice restart in progress");
+    return false; // ice restart already in progress
+  }
+
+  if (new_ctx == nullptr) {
+    return false;
+  }
+
+  ++restart_count;
+  old_ctx = current_ctx;
+  current_ctx = new_ctx;
+  return true;
+}
+
+
+void
+NrIceCtxHandler::FinalizeIceRestart()
+{
+  // no harm calling this even if we're not in the middle of restarting
+  old_ctx = nullptr;
+}
+
+
+void
+NrIceCtxHandler::RollbackIceRestart()
+{
+  if (old_ctx == nullptr) {
+    return;
+  }
+  current_ctx = old_ctx;
+  old_ctx = nullptr;
 }
 
 
 } // close namespace
--- a/media/mtransport/nricectxhandler.h
+++ b/media/mtransport/nricectxhandler.h
@@ -1,36 +1,54 @@
 #ifndef nricectxhandler_h__
 #define nricectxhandler_h__
 
 #include "nricectx.h"
 
 namespace mozilla {
 
-class NrIceCtxHandler : public NrIceCtx {
+class NrIceCtxHandler {
 public:
   // TODO(ekr@rtfm.com): Too many bools here. Bug 1193437.
   static RefPtr<NrIceCtxHandler> Create(const std::string& name,
-                                 bool offerer,
-                                 bool allow_loopback = false,
-                                 bool tcp_enabled = true,
-                                 bool allow_link_local = false,
-                                 bool hide_non_default = false,
-                                 Policy policy = ICE_POLICY_ALL);
+                                        bool offerer,
+                                        bool allow_loopback = false,
+                                        bool tcp_enabled = true,
+                                        bool allow_link_local = false,
+                                        bool hide_non_default = false,
+                                        NrIceCtx::Policy policy =
+                                          NrIceCtx::ICE_POLICY_ALL);
 
-  // Create a media stream
   RefPtr<NrIceMediaStream> CreateStream(const std::string& name,
                                         int components);
+  // CreateCtx is necessary so we can create and initialize the context
+  // on main thread, but begin the ice restart mechanics on STS thread
+  RefPtr<NrIceCtx> CreateCtx(bool hide_non_default = false) const; // for test
+  RefPtr<NrIceCtx> CreateCtx(const std::string& ufrag,
+                             const std::string& pwd,
+                             bool hide_non_default) const;
 
-protected:
+  RefPtr<NrIceCtx> ctx() { return current_ctx; }
+
+  bool BeginIceRestart(RefPtr<NrIceCtx> new_ctx);
+  bool IsRestarting() const { return (old_ctx != nullptr); }
+  void FinalizeIceRestart();
+  void RollbackIceRestart();
+
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceCtxHandler)
+
+private:
   NrIceCtxHandler(const std::string& name,
                   bool offerer,
-                  Policy policy)
-  : NrIceCtx(name, offerer, policy)
-    {}
+                  NrIceCtx::Policy policy);
+  NrIceCtxHandler() = delete;
+  ~NrIceCtxHandler() {}
+  DISALLOW_COPY_ASSIGN(NrIceCtxHandler);
 
-  NrIceCtxHandler(); // disable
-  virtual ~NrIceCtxHandler();
+  RefPtr<NrIceCtx> current_ctx;
+  RefPtr<NrIceCtx> old_ctx; // for while restart is in progress
+  int restart_count; // used to differentiate streams between restarted ctx
 };
 
 } // close namespace
 
 #endif // nricectxhandler_h__
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -379,17 +379,18 @@ class IceTestPeer : public sigslot::has_
   // TODO(ekr@rtfm.com): Convert to flags when NrIceCtx::Create() does.
   // Bug 1193437.
   IceTestPeer(const std::string& name, MtransportTestUtils* utils,
               bool offerer,
               bool allow_loopback = false, bool enable_tcp = true,
               bool allow_link_local = false, bool hide_non_default = false) :
       name_(name),
       ice_ctx_(NrIceCtxHandler::Create(name, offerer, allow_loopback,
-                                       enable_tcp, allow_link_local, hide_non_default)),
+                                       enable_tcp, allow_link_local,
+                                       hide_non_default)),
       candidates_(),
       gathering_complete_(false),
       ready_ct_(0),
       ice_complete_(false),
       ice_reached_checking_(false),
       received_(0),
       sent_(0),
       fake_resolver_(),
@@ -399,24 +400,24 @@ class IceTestPeer : public sigslot::has_
       expected_local_type_(NrIceCandidate::ICE_HOST),
       expected_local_transport_(kNrIceTransportUdp),
       expected_remote_type_(NrIceCandidate::ICE_HOST),
       trickle_mode_(TRICKLE_NONE),
       trickled_(0),
       simulate_ice_lite_(false),
       nat_(new TestNat),
       test_utils_(utils) {
-    ice_ctx_->SignalGatheringStateChange.connect(
+    ice_ctx_->ctx()->SignalGatheringStateChange.connect(
         this,
         &IceTestPeer::GatheringStateChange);
-    ice_ctx_->SignalConnectionStateChange.connect(
+    ice_ctx_->ctx()->SignalConnectionStateChange.connect(
         this,
         &IceTestPeer::ConnectionStateChange);
 
-    int r = ice_ctx_->SetNat(nat_);
+    int r = ice_ctx_->ctx()->SetNat(nat_);
     (void)r;
     MOZ_ASSERT(!r);
   }
 
   ~IceTestPeer() {
     test_utils_->sts_target()->Dispatch(WrapRunnable(this,
                                                     &IceTestPeer::Shutdown),
         NS_DISPATCH_SYNC);
@@ -424,38 +425,38 @@ class IceTestPeer : public sigslot::has_
     // Give the ICE destruction callback time to fire before
     // we destroy the resolver.
     PR_Sleep(1000);
   }
 
   void AddStream_s(int components) {
     char name[100];
     snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
-             (int)ice_ctx_->GetStreamCount());
+             (int)ice_ctx_->ctx()->GetStreamCount());
 
     RefPtr<NrIceMediaStream> stream =
         ice_ctx_->CreateStream(static_cast<char *>(name), components);
-    ice_ctx_->SetStream(ice_ctx_->GetStreamCount(), stream);
+    ice_ctx_->ctx()->SetStream(ice_ctx_->ctx()->GetStreamCount(), stream);
 
     ASSERT_TRUE(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 AddStream(int components)
   {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this, &IceTestPeer::AddStream_s, components),
         NS_DISPATCH_SYNC);
   }
 
   void RemoveStream_s(size_t index) {
-    ice_ctx_->SetStream(index, nullptr);
+    ice_ctx_->ctx()->SetStream(index, nullptr);
   }
 
   void RemoveStream(size_t index) {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this, &IceTestPeer::RemoveStream_s, index),
         NS_DISPATCH_SYNC);
   }
 
@@ -469,17 +470,17 @@ class IceTestPeer : public sigslot::has_
     std::vector<NrIceStunServer> stun_servers;
     UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
         addr, port, transport));
     stun_servers.push_back(*server);
     SetStunServers(stun_servers);
   }
 
   void SetStunServers(const std::vector<NrIceStunServer> &servers) {
-    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(servers)));
+    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetStunServers(servers)));
   }
 
   void UseTestStunServer() {
     SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
                   TestStunServer::GetInstance(AF_INET)->port());
   }
 
   void SetTurnServer(const std::string addr, uint16_t port,
@@ -494,48 +495,48 @@ class IceTestPeer : public sigslot::has_
   void SetTurnServer(const std::string addr, uint16_t port,
                      const std::string username,
                      const std::vector<unsigned char> password,
                      const char* transport) {
     std::vector<NrIceTurnServer> turn_servers;
     UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(
         addr, port, username, password, transport));
     turn_servers.push_back(*server);
-    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(turn_servers)));
+    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetTurnServers(turn_servers)));
   }
 
   void SetTurnServers(const std::vector<NrIceTurnServer> servers) {
-    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(servers)));
+    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetTurnServers(servers)));
   }
 
   void SetFakeResolver(const std::string& ip,
                        const std::string& fqdn) {
     ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
     if (!ip.empty() && !fqdn.empty()) {
       PRNetAddr addr;
       PRStatus status = PR_StringToNetAddr(ip.c_str(), &addr);
       addr.inet.port = kDefaultStunServerPort;
       ASSERT_EQ(PR_SUCCESS, status);
       fake_resolver_.SetAddr(fqdn, addr);
     }
-    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver(
+    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetResolver(
         fake_resolver_.AllocateResolver())));
   }
 
   void SetDNSResolver() {
     ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
-    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver(
+    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetResolver(
         dns_resolver_->AllocateResolver())));
   }
 
   void Gather() {
     nsresult res;
 
     test_utils_->sts_target()->Dispatch(
-        WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartGathering),
+        WrapRunnableRet(&res, ice_ctx_->ctx(), &NrIceCtx::StartGathering),
         NS_DISPATCH_SYNC);
 
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   void UseNat() {
     nat_->enabled_ = true;
   }
@@ -552,17 +553,17 @@ class IceTestPeer : public sigslot::has_
 
   void SetBlockUdp(bool block) {
     MOZ_ASSERT(!nat_->has_port_mappings());
     nat_->block_udp_ = block;
   }
 
   // Get various pieces of state
   std::vector<std::string> GetGlobalAttributes() {
-    std::vector<std::string> attrs(ice_ctx_->GetGlobalAttributes());
+    std::vector<std::string> attrs(ice_ctx_->ctx()->GetGlobalAttributes());
     if (simulate_ice_lite_) {
       attrs.push_back("ice-lite");
     }
     return attrs;
   }
 
   std::vector<std::string> GetCandidates(size_t stream) {
     std::vector<std::string> v;
@@ -579,28 +580,30 @@ class IceTestPeer : public sigslot::has_
       return candidate_filter_(candidate);
     }
     return candidate;
   }
 
   std::vector<std::string> GetCandidates_s(size_t stream) {
     std::vector<std::string> candidates;
 
-    if (stream >= ice_ctx_->GetStreamCount() || !ice_ctx_->GetStream(stream)) {
+    if (stream >= ice_ctx_->ctx()->GetStreamCount() ||
+        !ice_ctx_->ctx()->GetStream(stream)) {
       EXPECT_TRUE(false) << "No such stream " << stream;
       return candidates;
     }
 
     std::vector<std::string> candidates_in =
-      ice_ctx_->GetStream(stream)->GetCandidates();
+      ice_ctx_->ctx()->GetStream(stream)->GetCandidates();
 
     for (size_t i=0; i < candidates_in.size(); i++) {
       std::string candidate(FilterCandidate(candidates_in[i]));
       if (!candidate.empty()) {
-        std::cerr << name_ << " Returning candidate: " << candidate << std::endl;
+        std::cerr << name_ << " Returning candidate: "
+                           << candidate << std::endl;
         candidates.push_back(candidate);
       }
     }
 
     return candidates;
   }
 
   void SetExpectedTypes(NrIceCandidate::Type local,
@@ -639,81 +642,136 @@ class IceTestPeer : public sigslot::has_
       }
     }
     return host_net;
   }
 
   bool gathering_complete() { return gathering_complete_; }
   int ready_ct() { return ready_ct_; }
   bool is_ready_s(size_t stream) {
-    if (!ice_ctx_->GetStream(stream)) {
+    RefPtr<NrIceMediaStream> media_stream = ice_ctx_->ctx()->GetStream(stream);
+    if (!media_stream) {
       EXPECT_TRUE(false) << "No such stream " << stream;
       return false;
     }
-    return ice_ctx_->GetStream(stream)->state() == NrIceMediaStream::ICE_OPEN;
+    return media_stream->state() == NrIceMediaStream::ICE_OPEN;
   }
   bool is_ready(size_t stream)
   {
     bool result;
     test_utils_->sts_target()->Dispatch(
         WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream),
         NS_DISPATCH_SYNC);
     return result;
   }
   bool ice_complete() { return ice_complete_; }
   bool ice_reached_checking() { return ice_reached_checking_; }
   size_t received() { return received_; }
   size_t sent() { return sent_; }
 
+
+  void RestartIce() {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(this,
+                     &IceTestPeer::RestartIce_s,
+                     ice_ctx_->CreateCtx()),
+        NS_DISPATCH_SYNC);
+  }
+
+
+  void RestartIce_s(RefPtr<NrIceCtx> new_ctx) {
+    ice_ctx_->BeginIceRestart(new_ctx);
+
+    // set signals for the newly restarted ctx
+    ice_ctx_->ctx()->SignalGatheringStateChange.connect(
+        this,
+        &IceTestPeer::GatheringStateChange);
+    ice_ctx_->ctx()->SignalConnectionStateChange.connect(
+        this,
+        &IceTestPeer::ConnectionStateChange);
+
+    // take care of some local bookkeeping
+    ready_ct_ = 0;
+    gathering_complete_ = false;
+    ice_complete_ = false;
+    ice_reached_checking_ = false;
+    remote_ = nullptr;
+  }
+
+
+  void FinalizeIceRestart() {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(this, &IceTestPeer::FinalizeIceRestart_s),
+        NS_DISPATCH_SYNC);
+  }
+
+
+  void FinalizeIceRestart_s() {
+    ice_ctx_->FinalizeIceRestart();
+  }
+
+
+  void RollbackIceRestart() {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(this, &IceTestPeer::RollbackIceRestart_s),
+        NS_DISPATCH_SYNC);
+  }
+
+
+  void RollbackIceRestart_s() {
+    ice_ctx_->RollbackIceRestart();
+  }
+
+
   // Start connecting to another peer
   void Connect_s(IceTestPeer *remote, TrickleMode trickle_mode,
                  bool start = true) {
     nsresult res;
 
     remote_ = remote;
 
     trickle_mode_ = trickle_mode;
     ice_complete_ = false;
     ice_reached_checking_ = false;
-    res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes());
+    res = ice_ctx_->ctx()->ParseGlobalAttributes(remote->GetGlobalAttributes());
     ASSERT_TRUE(NS_SUCCEEDED(res));
 
     if (trickle_mode == TRICKLE_NONE ||
         trickle_mode == TRICKLE_REAL) {
-      for (size_t i=0; i<ice_ctx_->GetStreamCount(); ++i) {
-        RefPtr<NrIceMediaStream> aStream = ice_ctx_->GetStream(i);
+      for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
+        RefPtr<NrIceMediaStream> aStream = ice_ctx_->ctx()->GetStream(i);
         if (!aStream || aStream->HasParsedAttributes()) {
           continue;
         }
         std::vector<std::string> candidates =
             remote->GetCandidates(i);
 
         for (size_t j=0; j<candidates.size(); ++j) {
           std::cerr << name_ << " Candidate: " + candidates[j] << std::endl;
         }
         res = aStream->ParseAttributes(candidates);
         ASSERT_TRUE(NS_SUCCEEDED(res));
       }
     } else {
       // Parse empty attributes and then trickle them out later
-      for (size_t i=0; i<ice_ctx_->GetStreamCount(); ++i) {
-        RefPtr<NrIceMediaStream> aStream = ice_ctx_->GetStream(i);
+      for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
+        RefPtr<NrIceMediaStream> aStream = ice_ctx_->ctx()->GetStream(i);
         if (!aStream || aStream->HasParsedAttributes()) {
           continue;
         }
         std::vector<std::string> empty_attrs;
         std::cout << "Calling ParseAttributes on stream " << i << std::endl;
         res = aStream->ParseAttributes(empty_attrs);
         ASSERT_TRUE(NS_SUCCEEDED(res));
       }
     }
 
     if (start) {
       // Now start checks
-      res = ice_ctx_->StartChecks();
+      res = ice_ctx_->ctx()->StartChecks();
       ASSERT_TRUE(NS_SUCCEEDED(res));
     }
   }
 
   void Connect(IceTestPeer *remote, TrickleMode trickle_mode,
                bool start = true) {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(
@@ -721,20 +779,19 @@ class IceTestPeer : public sigslot::has_
         NS_DISPATCH_SYNC);
   }
 
   void SimulateTrickle(size_t stream) {
     std::cerr << name_ << " Doing trickle for stream " << stream << std::endl;
     // If we are in trickle deferred mode, now trickle in the candidates
     // for |stream|
 
-    // The size of streams is not going to change out from under us, so should
-    // be safe here.
-    ASSERT_GT(remote_->ice_ctx_->GetStreamCount(), stream);
-    ASSERT_TRUE(remote_->ice_ctx_->GetStream(stream).get());
+    // We should be safe here since stream changes happen on STS thread.
+    ASSERT_GT(remote_->ice_ctx_->ctx()->GetStreamCount(), stream);
+    ASSERT_TRUE(remote_->ice_ctx_->ctx()->GetStream(stream).get());
 
     std::vector<SchedulableTrickleCandidate*>& candidates =
       ControlTrickle(stream);
 
     for (auto i = candidates.begin(); i != candidates.end(); ++i) {
       (*i)->Schedule(0);
     }
   }
@@ -753,21 +810,21 @@ class IceTestPeer : public sigslot::has_
           new SchedulableTrickleCandidate(
               this, stream, candidates[j], test_utils_));
     }
 
     return controlled_trickle_candidates_[stream];
   }
 
   nsresult TrickleCandidate_s(const std::string &candidate, size_t stream) {
-    if (!ice_ctx_->GetStream(stream)) {
+    if (!ice_ctx_->ctx()->GetStream(stream)) {
       // stream might have gone away before the trickle timer popped
       return NS_OK;
     }
-    return ice_ctx_->GetStream(stream)->ParseTrickleCandidate(candidate);
+    return ice_ctx_->ctx()->GetStream(stream)->ParseTrickleCandidate(candidate);
   }
 
   void DumpCandidate(std::string which, const NrIceCandidate& cand) {
     std::string type;
     std::string tcp_type;
 
     std::string addr;
     int port;
@@ -829,28 +886,31 @@ class IceTestPeer : public sigslot::has_
               << tcp_type
               << " codeword="
               << cand.codeword
               << std::endl;
   }
 
   void DumpAndCheckActiveCandidates_s() {
     std::cerr << name_ << " Active candidates:" << std::endl;
-    for (size_t i=0; i < ice_ctx_->GetStreamCount(); ++i) {
-      if (!ice_ctx_->GetStream(i)) {
+    for (size_t i=0; i < ice_ctx_->ctx()->GetStreamCount(); ++i) {
+      if (!ice_ctx_->ctx()->GetStream(i)) {
         continue;
       }
 
-      for (size_t j=0; j < ice_ctx_->GetStream(i)->components(); ++j) {
-        std::cerr << name_ << " Stream " << i << " component " << j+1 << std::endl;
+      for (size_t j=0; j < ice_ctx_->ctx()->GetStream(i)->components(); ++j) {
+        std::cerr << name_ << " Stream " << i
+                           << " component " << j+1 << std::endl;
 
         UniquePtr<NrIceCandidate> local;
         UniquePtr<NrIceCandidate> remote;
 
-        nsresult res = ice_ctx_->GetStream(i)->GetActivePair(j+1, &local, &remote);
+        nsresult res = ice_ctx_->ctx()->GetStream(i)->GetActivePair(j+1,
+                                                                    &local,
+                                                                    &remote);
         if (res == NS_ERROR_NOT_AVAILABLE) {
           std::cerr << "Component unpaired or disabled." << std::endl;
         } else {
           ASSERT_TRUE(NS_SUCCEEDED(res));
           DumpCandidate("Local  ", *local);
           /* Depending on timing, and the whims of the network
            * stack/configuration we're running on top of, prflx is always a
            * possibility. */
@@ -882,17 +942,17 @@ class IceTestPeer : public sigslot::has_
   void DumpAndCheckActiveCandidates() {
     test_utils_->sts_target()->Dispatch(
       WrapRunnable(this, &IceTestPeer::DumpAndCheckActiveCandidates_s),
       NS_DISPATCH_SYNC);
   }
 
   void Close() {
     test_utils_->sts_target()->Dispatch(
-      WrapRunnable(ice_ctx_, &NrIceCtx::destroy_peer_ctx),
+      WrapRunnable(ice_ctx_->ctx(), &NrIceCtx::destroy_peer_ctx),
       NS_DISPATCH_SYNC);
   }
 
   void Shutdown() {
     std::cerr << name_ << " Shutdown" << std::endl;
     for (auto s = controlled_trickle_candidates_.begin();
          s != controlled_trickle_candidates_.end();
          ++s) {
@@ -914,43 +974,43 @@ class IceTestPeer : public sigslot::has_
     remote_ = nullptr;
   }
 
   void StartChecks() {
     nsresult res;
 
     // Now start checks
     test_utils_->sts_target()->Dispatch(
-        WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartChecks),
+        WrapRunnableRet(&res, ice_ctx_->ctx(), &NrIceCtx::StartChecks),
         NS_DISPATCH_SYNC);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   // Handle events
   void GatheringStateChange(NrIceCtx* ctx,
                             NrIceCtx::GatheringState state) {
     (void)ctx;
     if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
       return;
     }
 
-    std::cerr << "Gathering complete for " <<  name_ << std::endl;
+    std::cerr << name_ << " Gathering complete" << std::endl;
     gathering_complete_ = true;
 
     std::cerr << name_ << " CANDIDATES:" << std::endl;
-    for (size_t i=0; i<ice_ctx_->GetStreamCount(); ++i) {
+    for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
       std::cerr << "Stream " << name_ << std::endl;
 
-      if (!ice_ctx_->GetStream(i)) {
+      if (!ice_ctx_->ctx()->GetStream(i)) {
         std::cerr << "DISABLED" << std::endl;
         continue;
       }
 
       std::vector<std::string> candidates =
-          ice_ctx_->GetStream(i)->GetCandidates();
+          ice_ctx_->ctx()->GetStream(i)->GetCandidates();
 
       for(size_t j=0; j<candidates.size(); ++j) {
         std::cerr << candidates[j] << std::endl;
       }
     }
     std::cerr << std::endl;
 
   }
@@ -963,44 +1023,42 @@ class IceTestPeer : public sigslot::has_
     std::cerr << "Candidate for stream " << stream->name() << " initialized: "
       << candidate << std::endl;
     candidates_[stream->name()].push_back(candidate);
 
     // If we are connected, then try to trickle to the other side.
     if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) {
       // first, find the index of the stream we've been given so
       // we can get the corresponding stream on the remote side
-      size_t index = -1;
-      for (size_t i=0; i<ice_ctx_->GetStreamCount(); ++i) {
-        if (ice_ctx_->GetStream(i) == stream) {
-          index = i;
-          break;
+      for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
+        if (ice_ctx_->ctx()->GetStream(i) == stream) {
+          RefPtr<NrIceCtx> ctx = remote_->ice_ctx_->ctx();
+          ASSERT_GT(ctx->GetStreamCount(), i);
+          nsresult res = ctx->GetStream(i)->ParseTrickleCandidate(candidate);
+          ASSERT_TRUE(NS_SUCCEEDED(res));
+          ++trickled_;
+          return;
         }
       }
-      ASSERT_NE(index, -1u);
-
-      ASSERT_GT(remote_->ice_ctx_->GetStreamCount(), index);
-      nsresult res = remote_->ice_ctx_->GetStream(index)->ParseTrickleCandidate(
-          candidate);
-      ASSERT_TRUE(NS_SUCCEEDED(res));
-      ++trickled_;
+      ADD_FAILURE() << "No matching stream found for " << stream;
     }
   }
 
   nsresult GetCandidatePairs_s(size_t stream_index,
                                std::vector<NrIceCandidatePair>* pairs)
   {
     MOZ_ASSERT(pairs);
-    if (stream_index >= ice_ctx_->GetStreamCount() || !ice_ctx_->GetStream(stream_index)) {
+    if (stream_index >= ice_ctx_->ctx()->GetStreamCount() ||
+        !ice_ctx_->ctx()->GetStream(stream_index)) {
       // Is there a better error for "no such index"?
       ADD_FAILURE() << "No such media stream index: " << stream_index;
       return NS_ERROR_INVALID_ARG;
     }
 
-    return ice_ctx_->GetStream(stream_index)->GetCandidatePairs(pairs);
+    return ice_ctx_->ctx()->GetStream(stream_index)->GetCandidatePairs(pairs);
   }
 
   nsresult GetCandidatePairs(size_t stream_index,
                              std::vector<NrIceCandidatePair>* pairs) {
     nsresult v;
     test_utils_->sts_target()->Dispatch(
         WrapRunnableRet(&v, this,
                         &IceTestPeer::GetCandidatePairs_s,
@@ -1032,21 +1090,21 @@ class IceTestPeer : public sigslot::has_
          p != pairs.end(); ++p) {
       DumpCandidatePair(*p);
     }
     std::cerr << "]" << std::endl;
   }
 
   void DumpCandidatePairs_s() {
     std::cerr << "Dumping candidate pairs for all streams [" << std::endl;
-    for (size_t s = 0; s < ice_ctx_->GetStreamCount(); ++s) {
-      if (!ice_ctx_->GetStream(s)) {
+    for (size_t s = 0; s < ice_ctx_->ctx()->GetStreamCount(); ++s) {
+      if (!ice_ctx_->ctx()->GetStream(s)) {
         continue;
       }
-      DumpCandidatePairs_s(ice_ctx_->GetStream(s).get());
+      DumpCandidatePairs_s(ice_ctx_->ctx()->GetStream(s).get());
     }
     std::cerr << "]" << std::endl;
   }
 
   bool CandidatePairsPriorityDescending(const std::vector<NrIceCandidatePair>&
                                         pairs) {
     // Verify that priority is descending
     uint64_t priority = std::numeric_limits<uint64_t>::max();
@@ -1120,89 +1178,94 @@ class IceTestPeer : public sigslot::has_
     }
 
     ASSERT_TRUE(removed_pairs.empty()) << "At least one candidate pair has "
                                           "gone missing.";
   }
 
   void StreamReady(NrIceMediaStream *stream) {
     ++ready_ct_;
-    std::cerr << "Stream ready for " << stream->name() << " ct=" << ready_ct_ << std::endl;
+    std::cerr << name_ << " Stream ready for " << stream->name()
+                       << " ct=" << ready_ct_ << std::endl;
     DumpCandidatePairs_s(stream);
   }
   void StreamFailed(NrIceMediaStream *stream) {
-    std::cerr << "Stream failed for " << stream->name() << " ct=" << ready_ct_ << std::endl;
+    std::cerr << name_ << " Stream failed for " << stream->name()
+                       << " ct=" << ready_ct_ << std::endl;
     DumpCandidatePairs_s(stream);
   }
 
   void ConnectionStateChange(NrIceCtx* ctx,
                              NrIceCtx::ConnectionState state) {
     (void)ctx;
     switch (state) {
       case NrIceCtx::ICE_CTX_INIT:
         break;
       case NrIceCtx::ICE_CTX_CHECKING:
-        std::cerr << "ICE reached checking for " << name_ << std::endl;
+        std::cerr << name_ << " ICE reached checking " << std::endl;
         ice_reached_checking_ = true;
         break;
       case NrIceCtx::ICE_CTX_OPEN:
-        std::cerr << "ICE completed for " << name_ << std::endl;
+        std::cerr << name_ << " ICE completed " << std::endl;
         ice_complete_ = true;
         break;
       case NrIceCtx::ICE_CTX_FAILED:
         break;
     }
   }
 
   void PacketReceived(NrIceMediaStream *stream, int component, const unsigned char *data,
                       int len) {
     std::cerr << name_ << ": received " << len << " bytes" << std::endl;
     ++received_;
   }
 
   void SendPacket(int stream, int component, const unsigned char *data,
                   int len) {
-    if (!ice_ctx_->GetStream(stream)) {
+    RefPtr<NrIceMediaStream> media_stream = ice_ctx_->ctx()->GetStream(stream);
+    if (!media_stream) {
       ADD_FAILURE() << "No such stream " << stream;
       return;
     }
 
-    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->GetStream(stream)->SendPacket(component, data, len)));
+    ASSERT_TRUE(NS_SUCCEEDED(media_stream->SendPacket(component, data, len)));
 
     ++sent_;
     std::cerr << name_ << ": sent " << len << " bytes" << std::endl;
   }
 
   void SetCandidateFilter(CandidateFilter filter) {
     candidate_filter_ = filter;
   }
 
   void ParseCandidate_s(size_t i, const std::string& candidate) {
-    ASSERT_TRUE(ice_ctx_->GetStream(i).get()) << "No such stream " << i;
+    ASSERT_TRUE(ice_ctx_->ctx()->GetStream(i).get()) << "No such stream " << i;
 
     std::vector<std::string> attributes;
 
     attributes.push_back(candidate);
-    ice_ctx_->GetStream(i)->ParseAttributes(attributes);
+    ice_ctx_->ctx()->GetStream(i)->ParseAttributes(attributes);
   }
 
   void ParseCandidate(size_t i, const std::string& candidate)
   {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this,
                         &IceTestPeer::ParseCandidate_s,
                         i,
                         candidate),
         NS_DISPATCH_SYNC);
   }
 
   void DisableComponent_s(size_t stream, int component_id) {
-    ASSERT_LT(stream, ice_ctx_->GetStreamCount());
-    ASSERT_TRUE(ice_ctx_->GetStream(stream).get()) << "No such stream " << stream;
-    nsresult res = ice_ctx_->GetStream(stream)->DisableComponent(component_id);
+    ASSERT_LT(stream, ice_ctx_->ctx()->GetStreamCount());
+    ASSERT_TRUE(ice_ctx_->ctx()->GetStream(stream).get()) << "No such stream "
+                                                          << stream;
+    nsresult res =
+      ice_ctx_->ctx()->GetStream(stream)->DisableComponent(component_id);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   void DisableComponent(size_t stream, int component_id)
   {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this,
                         &IceTestPeer::DisableComponent_s,
@@ -1211,33 +1274,33 @@ class IceTestPeer : public sigslot::has_
         NS_DISPATCH_SYNC);
   }
 
   int trickled() { return trickled_; }
 
   void SetControlling(NrIceCtx::Controlling controlling) {
     nsresult res;
     test_utils_->sts_target()->Dispatch(
-        WrapRunnableRet(&res, ice_ctx_,
+        WrapRunnableRet(&res, ice_ctx_->ctx(),
                         &NrIceCtx::SetControlling,
                         controlling),
         NS_DISPATCH_SYNC);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   void SetTiebreaker(uint64_t tiebreaker) {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this,
                      &IceTestPeer::SetTiebreaker_s,
                      tiebreaker),
         NS_DISPATCH_SYNC);
   }
 
   void SetTiebreaker_s(uint64_t tiebreaker) {
-    ice_ctx_->peer()->tiebreaker = tiebreaker;
+    ice_ctx_->ctx()->peer()->tiebreaker = tiebreaker;
   }
 
   void SimulateIceLite() {
     simulate_ice_lite_ = true;
     SetControlling(NrIceCtx::ICE_CONTROLLED);
   }
 
   nsresult GetDefaultCandidate(unsigned int stream, NrIceCandidate* cand) {
@@ -1248,17 +1311,17 @@ class IceTestPeer : public sigslot::has_
                         &IceTestPeer::GetDefaultCandidate_s,
                         stream, cand),
         NS_DISPATCH_SYNC);
 
     return rv;
   }
 
   nsresult GetDefaultCandidate_s(unsigned int stream, NrIceCandidate* cand) {
-    return ice_ctx_->GetStream(stream)->GetDefaultCandidate(1, cand);
+    return ice_ctx_->ctx()->GetStream(stream)->GetDefaultCandidate(1, cand);
   }
 
  private:
   std::string name_;
   RefPtr<NrIceCtxHandler> ice_ctx_;
   std::map<std::string, std::vector<std::string> > candidates_;
   // Maps from stream id to list of remote trickle candidates
   std::map<size_t, std::vector<SchedulableTrickleCandidate*> >
@@ -1456,17 +1519,17 @@ class WebRtcIceGatherTest : public StunT
  protected:
   mozilla::UniquePtr<IceTestPeer> peer_;
 };
 
 class WebRtcIceConnectTest : public StunTest {
  public:
   WebRtcIceConnectTest() :
     initted_(false),
-    test_stun_server_initedd_(false),
+    test_stun_server_inited_(false),
     use_nat_(false),
     filtering_type_(TestNat::ENDPOINT_INDEPENDENT),
     mapping_type_(TestNat::ENDPOINT_INDEPENDENT),
     block_udp_(false) {}
 
   void SetUp() override {
     StunTest::SetUp();
 
@@ -1491,46 +1554,46 @@ class WebRtcIceConnectTest : public Stun
   void RemoveStream(size_t index) {
     p1_->RemoveStream(index);
     p2_->RemoveStream(index);
   }
 
   void Init(bool allow_loopback,
             bool enable_tcp,
             bool default_only = false,
-            bool setupStunServers = true) {
+            bool setup_stun_servers = true) {
     if (initted_) {
       return;
     }
 
     p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, allow_loopback,
                                   enable_tcp, false, default_only);
     p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, allow_loopback,
                                   enable_tcp, false, default_only);
-    InitPeer(p1_.get(), setupStunServers);
-    InitPeer(p2_.get(), setupStunServers);
+    InitPeer(p1_.get(), setup_stun_servers);
+    InitPeer(p2_.get(), setup_stun_servers);
 
     initted_ = true;
   }
 
-  void InitPeer(IceTestPeer* peer, bool setupStunServers = true) {
+  void InitPeer(IceTestPeer* peer, bool setup_stun_servers = true) {
     if (use_nat_) {
       // If we enable nat simulation, but still use a real STUN server somewhere
       // on the internet, we will see failures if there is a real NAT in
       // addition to our simulated one, particularly if it disallows
       // hairpinning.
-      if (setupStunServers) {
+      if (setup_stun_servers) {
         InitTestStunServer();
         peer->UseTestStunServer();
       }
       peer->UseNat();
       peer->SetFilteringType(filtering_type_);
       peer->SetMappingType(mapping_type_);
       peer->SetBlockUdp(block_udp_);
-    } else if (setupStunServers) {
+    } else if (setup_stun_servers) {
       std::vector<NrIceStunServer> stun_servers;
 
       stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
                                                       kDefaultStunServerPort, kNrIceTransportUdp));
       stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
                                                       kDefaultStunServerPort, kNrIceTransportTcp));
 
       peer->SetStunServers(stun_servers);
@@ -1574,29 +1637,29 @@ class WebRtcIceConnectTest : public Stun
 
   void SetMappingType(TestNat::NatBehavior type) {
     // to be useful, this method should be called before Init
     ASSERT_FALSE(initted_);
     mapping_type_ = type;
   }
 
   void BlockUdp() {
-    // to be useful, this method should be called before Init
-    ASSERT_FALSE(initted_);
+    // note: |block_udp_| is used only in InitPeer.
+    // Use IceTestPeer::SetBlockUdp to act on the peer directly.
     block_udp_ = true;
   }
 
   void InitTestStunServer() {
-    if (test_stun_server_initedd_) {
+    if (test_stun_server_inited_) {
       return;
     }
 
     std::cerr << "Resetting TestStunServer" << std::endl;
     TestStunServer::GetInstance(AF_INET)->Reset();
-    test_stun_server_initedd_ = true;
+    test_stun_server_inited_ = true;
   }
 
   void UseTestStunServer() {
     InitTestStunServer();
     p1_->UseTestStunServer();
     p2_->UseTestStunServer();
   }
 
@@ -1616,44 +1679,44 @@ class WebRtcIceConnectTest : public Stun
   void SetCandidateFilter(CandidateFilter filter, bool both=true) {
     p1_->SetCandidateFilter(filter);
     if (both) {
       p2_->SetCandidateFilter(filter);
     }
   }
 
   void Connect() {
-    ConnectCallerAndCallee(p2_.get(), p1_.get());
+    ConnectCallerAndCallee(p1_.get(), p2_.get());
   }
 
   void ConnectCallerAndCallee(IceTestPeer* caller, IceTestPeer* callee) {
-    ASSERT_TRUE(caller->ready_ct() == 0 && 
-                caller->ice_complete() == 0 && 
-                caller->ice_reached_checking() == 0);
-    ASSERT_TRUE(callee->ready_ct() == 0 && 
-                callee->ice_complete() == 0 && 
-                callee->ice_reached_checking() == 0);
-
-    // IceTestPeer::Connect grabs attributes from the first arg, and gives them
-    // to |this|, meaning that p2_->Connect(p1_, ...) simulates p1 sending an
-    // offer to p2. Order matters here because it determines which peer is
-    // controlling.
+    ASSERT_TRUE(caller->ready_ct() == 0);
+    ASSERT_TRUE(caller->ice_complete() == 0);
+    ASSERT_TRUE(caller->ice_reached_checking() == 0);
+    ASSERT_TRUE(callee->ready_ct() == 0);
+    ASSERT_TRUE(callee->ice_complete() == 0);
+    ASSERT_TRUE(callee->ice_reached_checking() == 0);
+
+    // IceTestPeer::Connect grabs attributes from the first arg, and
+    // gives them to |this|, meaning that callee->Connect(caller, ...)
+    // simulates caller sending an offer to callee. Order matters here
+    // because it determines which peer is controlling.
+    callee->Connect(caller, TRICKLE_NONE);
     caller->Connect(callee, TRICKLE_NONE);
-    callee->Connect(caller, TRICKLE_NONE);
-
-    ASSERT_TRUE_WAIT(callee->ready_ct() == 1 && caller->ready_ct() == 1,
+
+    ASSERT_TRUE_WAIT(caller->ready_ct() == 1 && callee->ready_ct() == 1,
                      kDefaultTimeout);
-    ASSERT_TRUE_WAIT(callee->ice_complete() && caller->ice_complete(),
+    ASSERT_TRUE_WAIT(caller->ice_complete() && callee->ice_complete(),
                      kDefaultTimeout);
 
+    ASSERT_TRUE(caller->ice_reached_checking());
     ASSERT_TRUE(callee->ice_reached_checking());
-    ASSERT_TRUE(caller->ice_reached_checking());
-
+
+    caller->DumpAndCheckActiveCandidates();
     callee->DumpAndCheckActiveCandidates();
-    caller->DumpAndCheckActiveCandidates();
   }
 
   void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote,
                         std::string transport = kNrIceTransportUdp) {
     p1_->SetExpectedTypes(local, remote, transport);
     p2_->SetExpectedTypes(local, remote, transport);
   }
 
@@ -1733,39 +1796,43 @@ class WebRtcIceConnectTest : public Stun
   }
 
   // default is p1_ sending to p2_
   void SendReceive() {
     SendReceive(p1_.get(), p2_.get());
   }
 
   void SendReceive(IceTestPeer *p1, IceTestPeer *p2,
-                   bool expectTxFailure = false,
-                   bool expectRxFailure = false) {
+                   bool expect_tx_failure = false,
+                   bool expect_rx_failure = false) {
+    size_t previousSent = p1->sent();
+    size_t previousReceived = p2->received();
+
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(p1,
                      &IceTestPeer::SendPacket, 0, 1,
                      reinterpret_cast<const unsigned char *>("TEST"), 4),
         NS_DISPATCH_SYNC);
-    if (expectTxFailure) {
-      ASSERT_NE(1u, p1->sent());
+
+    if (expect_tx_failure) {
+      ASSERT_EQ(previousSent, p1->sent());
     } else {
-      ASSERT_EQ(1u, p1->sent());
+      ASSERT_EQ(previousSent+1, p1->sent());
     }
-    if (expectRxFailure) {
+    if (expect_rx_failure) {
       usleep(1000);
-      ASSERT_TRUE(p2->received() == 0);
+      ASSERT_EQ(previousReceived, p2->received());
     } else {
-      ASSERT_TRUE_WAIT(p2->received() == 1, 1000);
+      ASSERT_TRUE_WAIT(p2->received() == previousReceived+1, 1000);
     }
   }
 
  protected:
   bool initted_;
-  bool test_stun_server_initedd_;
+  bool test_stun_server_inited_;
   nsCOMPtr<nsIEventTarget> target_;
   mozilla::UniquePtr<IceTestPeer> p1_;
   mozilla::UniquePtr<IceTestPeer> p2_;
   bool use_nat_;
   TestNat::NatBehavior filtering_type_;
   TestNat::NatBehavior mapping_type_;
   bool block_udp_;
 };
@@ -2349,16 +2416,85 @@ TEST_F(WebRtcIceConnectTest, TestGatherA
 
 
 TEST_F(WebRtcIceConnectTest, TestConnect) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
+
+TEST_F(WebRtcIceConnectTest, TestConnectRestartIce) {
+  AddStream("first", 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
+  SendReceive(p1_.get(), p2_.get());
+
+  mozilla::UniquePtr<IceTestPeer> p3_;
+  p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, false,
+                                false, false, false);
+  InitPeer(p3_.get());
+  p3_->AddStream(1);
+
+  p2_->AddStream(1);
+  ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+  std::cout << "-------------------------------------------------" << std::endl;
+  ConnectCallerAndCallee(p3_.get(), p2_.get());
+  SendReceive(p1_.get(), p2_.get()); // p1 and p2 still connected
+  SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected
+
+  p2_->FinalizeIceRestart();
+  SendReceive(p3_.get(), p2_.get()); // p3 and p2 are still connected
+
+  SendReceive(p1_.get(), p2_.get(), false, true); // p1 and p2 not connected
+
+  p3_ = nullptr;
+}
+
+
+TEST_F(WebRtcIceConnectTest, TestConnectRestartIceThenAbort) {
+  AddStream("first", 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
+  SendReceive(p1_.get(), p2_.get());
+
+  mozilla::UniquePtr<IceTestPeer> p3_;
+  p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, false,
+                                false, false, false);
+  InitPeer(p3_.get());
+  p3_->AddStream(1);
+
+  p2_->AddStream(1);
+  ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+  std::cout << "-------------------------------------------------" << std::endl;
+  ConnectCallerAndCallee(p3_.get(), p2_.get());
+  SendReceive(p1_.get(), p2_.get()); // p1 and p2 still connected
+  SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected
+
+  p2_->RollbackIceRestart();
+  SendReceive(p1_.get(), p2_.get()); // p1 and p2 are still connected
+
+  SendReceive(p3_.get(), p2_.get(), false, true); // p3 and p2 not connected
+
+  p3_ = nullptr;
+}
+
+
 TEST_F(WebRtcIceConnectTest, TestConnectTcp) {
   Init(false, true);
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsTcpCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
     NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
   Connect();
@@ -3156,17 +3292,17 @@ TEST_F(WebRtcIceConnectTest, TestPollCan
   ASSERT_NE(0U, pairs.size());
   ASSERT_TRUE(p2_->CandidatePairsPriorityDescending(pairs));
   ASSERT_TRUE(ContainsSucceededPair(pairs));
 }
 
 TEST_F(WebRtcIceConnectTest, TestHostCandPairingFilter) {
   Init(false, false, false, false);
   AddStream("first", 1);
-  ASSERT_TRUE(Gather(kDefaultTimeout));
+  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;
   }
 
@@ -3190,17 +3326,17 @@ TEST_F(WebRtcIceConnectTest, TestHostCan
 // 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);
-  ASSERT_TRUE(Gather(kDefaultTimeout));
+  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;
   }
 
--- a/media/mtransport/test/multi_tcp_socket_unittest.cpp
+++ b/media/mtransport/test/multi_tcp_socket_unittest.cpp
@@ -104,26 +104,26 @@ class MultiTcpSocketTest : public Mtrans
     int r;
 
     if (!stun_server_addr.empty()) {
       std::vector<NrIceStunServer> stun_servers;
       UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
           stun_server_addr, stun_server_port, kNrIceTransportTcp));
       stun_servers.push_back(*server);
 
-      ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
+      ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetStunServers(stun_servers)));
     }
 
     r = 1;
     for (int tries=10; tries && r; --tries) {
       r = nr_str_port_to_transport_addr(
         (char *)"127.0.0.1", EnsureEphemeral(port_s++), IPPROTO_TCP, &local);
       ASSERT_EQ(0, r);
 
-      r = nr_socket_multi_tcp_create(ice_ctx_->ctx(),
+      r = nr_socket_multi_tcp_create(ice_ctx_->ctx()->ctx(),
           &local, tcp_type, 1, 2048, sock);
     }
 
     ASSERT_EQ(0, r);
     printf("Creating socket on %s\n", local.as_string);
     r = nr_socket_multi_tcp_set_readable_cb(*sock,
         &MultiTcpSocketTest::SockReadable, this);
     ASSERT_EQ(0, r);
@@ -343,17 +343,17 @@ class MultiTcpSocketTest : public Mtrans
   bool IsReadable() const {
     return readable;
   }
   void SetReadable(bool r) {
     readable=r;
   }
   std::vector<nr_socket *> socks;
   Atomic<bool> readable;
-  RefPtr<NrIceCtx> ice_ctx_;
+  RefPtr<NrIceCtxHandler> ice_ctx_;
 };
 }
 
 TEST_F(MultiTcpSocketTest, TestListen) {
   socks[0] = Create(TCP_TYPE_PASSIVE);
   Listen(socks[0]);
 }
 
--- a/media/mtransport/test/transport_unittests.cpp
+++ b/media/mtransport/test/transport_unittests.cpp
@@ -451,17 +451,17 @@ class TransportTestPeer : public sigslot
         enabled_cipersuites_(),
         disabled_cipersuites_(),
         reuse_dhe_key_(false),
         test_utils_(utils) {
     std::vector<NrIceStunServer> stun_servers;
     UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
         std::string((char *)"stun.services.mozilla.com"), 3478));
     stun_servers.push_back(*server);
-    EXPECT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
+    EXPECT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetStunServers(stun_servers)));
 
     dtls_->SetIdentity(identity_);
     dtls_->SetRole(name == "P2" ?
                    TransportLayerDtls::CLIENT :
                    TransportLayerDtls::SERVER);
 
     nsresult res = identity_->ComputeFingerprint("sha-1",
                                              fingerprint_,
@@ -594,38 +594,38 @@ class TransportTestPeer : public sigslot
                                peer),
                   NS_DISPATCH_SYNC);
   }
 
   void InitIce() {
     nsresult res;
 
     // Attach our slots
-    ice_ctx_->SignalGatheringStateChange.
+    ice_ctx_->ctx()->SignalGatheringStateChange.
         connect(this, &TransportTestPeer::GatheringStateChange);
 
     char name[100];
     snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
              (int)streams_.size());
 
     // Create the media stream
     RefPtr<NrIceMediaStream> stream =
         ice_ctx_->CreateStream(static_cast<char *>(name), 1);
 
     ASSERT_TRUE(stream != nullptr);
-    ice_ctx_->SetStream(streams_.size(), stream);
+    ice_ctx_->ctx()->SetStream(streams_.size(), stream);
     streams_.push_back(stream);
 
     // Listen for candidates
     stream->SignalCandidate.
         connect(this, &TransportTestPeer::GotCandidate);
 
     // Create the transport layer
     ice_ = new TransportLayerIce(name);
-    ice_->SetParameters(ice_ctx_, stream, 1);
+    ice_->SetParameters(ice_ctx_->ctx(), stream, 1);
 
     // Assemble the stack
     nsAutoPtr<std::queue<mozilla::TransportLayer *> > layers(
       new std::queue<mozilla::TransportLayer *>);
     layers->push(ice_);
     layers->push(dtls_);
 
     test_utils_->sts_target()->Dispatch(
@@ -635,17 +635,17 @@ class TransportTestPeer : public sigslot
     ASSERT_EQ((nsresult)NS_OK, res);
 
     // Listen for media events
     flow_->SignalPacketReceived.connect(this, &TransportTestPeer::PacketReceived);
     flow_->SignalStateChange.connect(this, &TransportTestPeer::StateChanged);
 
     // Start gathering
     test_utils_->sts_target()->Dispatch(
-        WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartGathering),
+        WrapRunnableRet(&res, ice_ctx_->ctx(), &NrIceCtx::StartGathering),
         NS_DISPATCH_SYNC);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   void ConnectIce(TransportTestPeer *peer) {
     peer_ = peer;
 
     // If gathering is already complete, push the candidates over
@@ -675,33 +675,33 @@ class TransportTestPeer : public sigslot
     // Don't send to the other side
     if (!peer_) {
       gathering_complete_ = true;
       return;
     }
 
     // First send attributes
     test_utils_->sts_target()->Dispatch(
-      WrapRunnableRet(&res, peer_->ice_ctx_,
+      WrapRunnableRet(&res, peer_->ice_ctx_->ctx(),
                       &NrIceCtx::ParseGlobalAttributes,
-                      ice_ctx_->GetGlobalAttributes()),
+                      ice_ctx_->ctx()->GetGlobalAttributes()),
       NS_DISPATCH_SYNC);
     ASSERT_TRUE(NS_SUCCEEDED(res));
 
     for (size_t i=0; i<streams_.size(); ++i) {
       test_utils_->sts_target()->Dispatch(
         WrapRunnableRet(&res, peer_->streams_[i], &NrIceMediaStream::ParseAttributes,
                         candidates_[streams_[i]->name()]), NS_DISPATCH_SYNC);
 
       ASSERT_TRUE(NS_SUCCEEDED(res));
     }
 
     // Start checks on the other peer.
     test_utils_->sts_target()->Dispatch(
-      WrapRunnableRet(&res, peer_->ice_ctx_, &NrIceCtx::StartChecks),
+      WrapRunnableRet(&res, peer_->ice_ctx_->ctx(), &NrIceCtx::StartChecks),
       NS_DISPATCH_SYNC);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   TransportResult SendPacket(const unsigned char* data, size_t len) {
     TransportResult ret;
     test_utils_->sts_target()->Dispatch(
       WrapRunnableRet(&ret, flow_, &TransportFlow::SendPacket, data, len),
--- a/media/mtransport/test/turn_unittest.cpp
+++ b/media/mtransport/test/turn_unittest.cpp
@@ -97,17 +97,17 @@ class TurnClient : public MtransportTest
         received_(0),
         protocol_(IPPROTO_UDP) {
   }
 
   ~TurnClient() {
   }
 
   static void SetUpTestCase() {
-    NrIceCtx::InitializeCryptoAndLogging(false, false, false);
+    NrIceCtx::InitializeGlobals(false, false, false);
   }
 
   void SetTcp() {
     protocol_ = IPPROTO_TCP;
   }
 
   void Init_s() {
     int r;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -52,16 +52,18 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include "stun.h"
 #include "ice_ctx.h"
 #include "ice_reg.h"
 #include "nr_crypto.h"
 #include "async_timer.h"
 #include "util.h"
 #include "nr_socket_local.h"
 
+#define ICE_UFRAG_LEN 8
+#define ICE_PWD_LEN 32
 
 int LOG_ICE = 0;
 
 static int nr_ice_random_string(char *str, int len);
 static int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out);
 #ifdef USE_TURN
 static int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out);
 #endif /* USE_TURN */
@@ -311,38 +313,55 @@ int nr_ice_fetch_turn_servers(int ct, nr
     if (_status) RFREE(servers);
     return(_status);
   }
 #endif /* USE_TURN */
 
 #define MAXADDRS 100 /* Ridiculously high */
 int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp)
   {
+    int r,_status;
+    char *ufrag = 0;
+    char *pwd = 0;
+
+    if (r=nr_ice_get_new_ice_ufrag(&ufrag))
+      ABORT(r);
+    if (r=nr_ice_get_new_ice_pwd(&pwd))
+      ABORT(r);
+
+    if (r=nr_ice_ctx_create_with_credentials(label, flags, ufrag, pwd, ctxp))
+      ABORT(r);
+
+    _status=0;
+  abort:
+    RFREE(ufrag);
+    RFREE(pwd);
+
+    return(_status);
+  }
+
+int nr_ice_ctx_create_with_credentials(char *label, UINT4 flags, char *ufrag, char *pwd, nr_ice_ctx **ctxp)
+  {
     nr_ice_ctx *ctx=0;
     int r,_status;
-    char buf[100];
 
     if(r=r_log_register("ice", &LOG_ICE))
       ABORT(r);
 
     if(!(ctx=RCALLOC(sizeof(nr_ice_ctx))))
       ABORT(R_NO_MEMORY);
 
     ctx->flags=flags;
 
     if(!(ctx->label=r_strdup(label)))
       ABORT(R_NO_MEMORY);
 
-    if(r=nr_ice_random_string(buf,8))
-      ABORT(r);
-    if(!(ctx->ufrag=r_strdup(buf)))
+    if(!(ctx->ufrag=r_strdup(ufrag)))
       ABORT(r);
-    if(r=nr_ice_random_string(buf,32))
-      ABORT(r);
-    if(!(ctx->pwd=r_strdup(buf)))
+    if(!(ctx->pwd=r_strdup(pwd)))
       ABORT(r);
 
     /* Get the STUN servers */
     if(r=NR_reg_get_child_count(NR_ICE_REG_STUN_SRV_PRFX,
       (unsigned int *)&ctx->stun_server_ct)||ctx->stun_server_ct==0) {
       r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): No STUN servers specified in nICEr registry", ctx->label);
       ctx->stun_server_ct=0;
     }
@@ -947,8 +966,47 @@ int nr_ice_ctx_hide_candidate(nr_ice_ctx
 
     if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) {
       if (cand->type == HOST)
         return 1;
     }
 
     return 0;
   }
+
+int nr_ice_get_new_ice_ufrag(char** ufrag)
+  {
+    int r,_status;
+    char buf[ICE_UFRAG_LEN+1];
+
+    if(r=nr_ice_random_string(buf,ICE_UFRAG_LEN))
+      ABORT(r);
+    if(!(*ufrag=r_strdup(buf)))
+      ABORT(r);
+
+    _status=0;
+  abort:
+    if(_status) {
+      RFREE(*ufrag);
+      *ufrag = 0;
+    }
+    return(_status);
+  }
+
+int nr_ice_get_new_ice_pwd(char** pwd)
+  {
+    int r,_status;
+    char buf[ICE_PWD_LEN+1];
+
+    if(r=nr_ice_random_string(buf,ICE_PWD_LEN))
+      ABORT(r);
+    if(!(*pwd=r_strdup(buf)))
+      ABORT(r);
+
+    _status=0;
+  abort:
+    if(_status) {
+      RFREE(*pwd);
+      *pwd = 0;
+    }
+    return(_status);
+  }
+
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -150,16 +150,17 @@ struct nr_ice_ctx_ {
 
   nr_ice_trickle_candidate_cb trickle_cb;
   void *trickle_cb_arg;
 
   char force_net_interface[MAXIFNAME];
 };
 
 int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp);
+int nr_ice_ctx_create_with_credentials(char *label, UINT4 flags, char* ufrag, char* pwd, nr_ice_ctx **ctxp);
 #define NR_ICE_CTX_FLAGS_OFFERER                           1
 #define NR_ICE_CTX_FLAGS_ANSWERER                          (1<<1)
 #define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION             (1<<2)
 #define NR_ICE_CTX_FLAGS_LITE                              (1<<3)
 #define NR_ICE_CTX_FLAGS_RELAY_ONLY                        (1<<4)
 #define NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS                (1<<5)
 
 int nr_ice_ctx_destroy(nr_ice_ctx **ctxp);
@@ -176,16 +177,18 @@ int nr_ice_ctx_finalize(nr_ice_ctx *ctx,
 int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers, int ct);
 int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int ct);
 int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver);
 int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *prioritizer);
 int nr_ice_ctx_set_turn_tcp_socket_wrapper(nr_ice_ctx *ctx, nr_socket_wrapper_factory *wrapper);
 void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory);
 int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg);
 int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
+int nr_ice_get_new_ice_ufrag(char** ufrag);
+int nr_ice_get_new_ice_pwd(char** pwd);
 
 #define NR_ICE_MAX_ATTRIBUTE_SIZE 256
 
 extern int LOG_ICE;
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
--- a/media/mtransport/transportlayerice.cpp
+++ b/media/mtransport/transportlayerice.cpp
@@ -80,48 +80,106 @@ namespace mozilla {
 
 #ifdef ERROR
 #undef ERROR
 #endif
 
 MOZ_MTLOG_MODULE("mtransport")
 
 TransportLayerIce::TransportLayerIce(const std::string& name)
-    : name_(name), ctx_(nullptr), stream_(nullptr), component_(0) {}
+    : name_(name),
+      ctx_(nullptr), stream_(nullptr), component_(0),
+      old_stream_(nullptr)
+{
+  // setup happens later
+}
 
 TransportLayerIce::~TransportLayerIce() {
   // No need to do anything here, since we use smart pointers
 }
 
 void TransportLayerIce::SetParameters(RefPtr<NrIceCtx> ctx,
                                       RefPtr<NrIceMediaStream> stream,
                                       int component) {
+  // If SetParameters is called and we already have a stream_, this means
+  // we're handling an ICE restart.  We need to hold the old stream until
+  // we know the new stream is working.
+  if (stream_ && !old_stream_) {
+    MOZ_ASSERT(stream_ != stream); // make sure we're getting a different stream
+
+    // Here we leave the old stream's signals connected until we don't need
+    // it anymore.  They will be disconnected if ice restart is successful.
+    old_stream_ = stream_;
+    MOZ_MTLOG(ML_INFO, LAYER_INFO << "SetParameters save old stream("
+                                  << old_stream_->name() << ")");
+  }
+
   ctx_ = ctx;
   stream_ = stream;
   component_ = component;
 
   PostSetup();
 }
 
 void TransportLayerIce::PostSetup() {
   target_ = ctx_->thread();
 
   stream_->SignalReady.connect(this, &TransportLayerIce::IceReady);
   stream_->SignalFailed.connect(this, &TransportLayerIce::IceFailed);
   stream_->SignalPacketReceived.connect(this,
                                         &TransportLayerIce::IcePacketReceived);
   if (stream_->state() == NrIceMediaStream::ICE_OPEN) {
     TL_SET_STATE(TS_OPEN);
+    // Reset old ice stream if new stream is good
+    ResetOldStream();
   }
 }
 
+void TransportLayerIce::ResetOldStream() {
+  if (old_stream_ == nullptr) {
+    return; // no work to do
+  }
+  // ICE Ready on the new stream, we can forget the old stream now
+  MOZ_MTLOG(ML_INFO, LAYER_INFO << "ResetOldStream(" << old_stream_->name()
+                                << ")");
+  old_stream_->SignalReady.disconnect(this);
+  old_stream_->SignalFailed.disconnect(this);
+  old_stream_->SignalPacketReceived.disconnect(this);
+  old_stream_ = nullptr;
+}
+
+void TransportLayerIce::RestoreOldStream() {
+  if (old_stream_ == nullptr) {
+    return; // no work to do
+  }
+  MOZ_MTLOG(ML_INFO, LAYER_INFO << "RestoreOldStream(" << old_stream_->name()
+                                << ")");
+  stream_->SignalReady.disconnect(this);
+  stream_->SignalFailed.disconnect(this);
+  stream_->SignalPacketReceived.disconnect(this);
+  stream_ = old_stream_;
+  old_stream_ = nullptr;
+
+  if (stream_->state() == NrIceMediaStream::ICE_OPEN) {
+    IceReady(stream_);
+  } else if (stream_->state() == NrIceMediaStream::ICE_CLOSED) {
+    IceFailed(stream_);
+  }
+  // No events are fired when the stream is ICE_CONNECTING.  If the
+  // restored stream is ICE_CONNECTING, IceReady/IceFailed will fire
+  // later.
+}
+
 TransportResult TransportLayerIce::SendPacket(const unsigned char *data,
                                               size_t len) {
   CheckThread();
-  nsresult res = stream_->SendPacket(component_, data, len);
+  // use old_stream_ until stream_ is ready
+  nsresult res = (old_stream_?old_stream_:stream_)->SendPacket(component_,
+                                                               data,
+                                                               len);
 
   if (!NS_SUCCEEDED(res)) {
     return (res == NS_BASE_STREAM_WOULD_BLOCK) ?
         TE_WOULDBLOCK : TE_ERROR;
   }
 
   MOZ_MTLOG(ML_DEBUG, LAYER_INFO << " SendPacket(" << len << ") succeeded");
 
@@ -131,21 +189,35 @@ TransportResult TransportLayerIce::SendP
 
 void TransportLayerIce::IceCandidate(NrIceMediaStream *stream,
                                      const std::string&) {
   // NO-OP for now
 }
 
 void TransportLayerIce::IceReady(NrIceMediaStream *stream) {
   CheckThread();
+  // only handle the current stream (not the old stream during restart)
+  if (stream != stream_) {
+    return;
+  }
+  MOZ_MTLOG(ML_INFO, LAYER_INFO << "ICE Ready(" << stream->name() << ","
+    << component_ << ")");
   TL_SET_STATE(TS_OPEN);
+  // Reset old ice stream if new stream is good after ice restart
+  ResetOldStream();
 }
 
 void TransportLayerIce::IceFailed(NrIceMediaStream *stream) {
   CheckThread();
+  // only handle the current stream (not the old stream during restart)
+  if (stream != stream_) {
+    return;
+  }
+  MOZ_MTLOG(ML_INFO, LAYER_INFO << "ICE Failed(" << stream->name() << ","
+    << component_ << ")");
   TL_SET_STATE(TS_ERROR);
 }
 
 void TransportLayerIce::IcePacketReceived(NrIceMediaStream *stream, int component,
                        const unsigned char *data, int len) {
   CheckThread();
   // We get packets for both components, so ignore the ones that aren't
   // for us.
--- a/media/mtransport/transportlayerice.h
+++ b/media/mtransport/transportlayerice.h
@@ -48,17 +48,22 @@ class TransportLayerIce : public Transpo
   void IcePacketReceived(NrIceMediaStream *stream, int component,
                          const unsigned char *data, int len);
 
   TRANSPORT_LAYER_ID("ice")
 
  private:
   DISALLOW_COPY_ASSIGN(TransportLayerIce);
   void PostSetup();
+  void ResetOldStream(); // called after successful ice restart
+  void RestoreOldStream(); // called after unsuccessful ice restart
 
   const std::string name_;
   RefPtr<NrIceCtx> ctx_;
   RefPtr<NrIceMediaStream> stream_;
   int component_;
+
+  // used to hold the old stream
+  RefPtr<NrIceMediaStream> old_stream_;
 };
 
 }  // close namespace
 #endif
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -39,16 +39,17 @@ enum JsepSdpType {
   kJsepSdpRollback
 };
 
 struct JsepOAOptions {};
 struct JsepOfferOptions : public JsepOAOptions {
   Maybe<size_t> mOfferToReceiveAudio;
   Maybe<size_t> mOfferToReceiveVideo;
   Maybe<bool> mDontOfferDataChannel;
+  Maybe<bool> mIceRestart; // currently ignored by JsepSession
 };
 struct JsepAnswerOptions : public JsepOAOptions {};
 
 enum JsepBundlePolicy {
   kBundleBalanced,
   kBundleMaxCompat,
   kBundleMaxBundle
 };
@@ -79,18 +80,21 @@ public:
   GetNegotiations() const
   {
     return mNegotiations;
   }
 
   // Set up the ICE And DTLS data.
   virtual nsresult SetIceCredentials(const std::string& ufrag,
                                      const std::string& pwd) = 0;
+  virtual const std::string& GetUfrag() const = 0;
+  virtual const std::string& GetPwd() const = 0;
   virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0;
   virtual bool RemoteIsIceLite() const = 0;
+  virtual bool RemoteIceIsRestarting() const = 0;
   virtual std::vector<std::string> GetIceOptions() const = 0;
 
   virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
                                       const std::vector<uint8_t>& value) = 0;
 
   virtual nsresult AddAudioRtpExtension(const std::string& extensionName) = 0;
   virtual nsresult AddVideoRtpExtension(const std::string& extensionName) = 0;
 
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -660,17 +660,20 @@ JsepSessionImpl::CreateOffer(const JsepO
 
   // Ensure that we have all the m-sections we need, and disable extras
   rv = SetupOfferMSections(options, sdp.get());
   NS_ENSURE_SUCCESS(rv, rv);
 
   SetupBundle(sdp.get());
 
   if (mCurrentLocalDescription) {
-    rv = CopyPreviousTransportParams(*GetAnswer(), *sdp, sdp.get());
+    rv = CopyPreviousTransportParams(*GetAnswer(),
+                                     *mCurrentLocalDescription,
+                                     *sdp,
+                                     sdp.get());
     NS_ENSURE_SUCCESS(rv,rv);
   }
 
   *offer = sdp->ToString();
   mGeneratedLocalDescription = Move(sdp);
   ++mSessionVersion;
 
   return NS_OK;
@@ -798,17 +801,21 @@ JsepSessionImpl::CreateAnswer(const Jsep
 
   for (size_t i = 0; i < numMsections; ++i) {
     const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
     rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (mCurrentLocalDescription) {
-    rv = CopyPreviousTransportParams(*GetAnswer(), *sdp, sdp.get());
+    // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
+    rv = CopyPreviousTransportParams(*GetAnswer(),
+                                     *mCurrentRemoteDescription,
+                                     offer,
+                                     sdp.get());
     NS_ENSURE_SUCCESS(rv,rv);
   }
 
   *answer = sdp->ToString();
   mGeneratedLocalDescription = Move(sdp);
 
   return NS_OK;
 }
@@ -1203,16 +1210,38 @@ JsepSessionImpl::SetRemoteDescription(Js
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = ValidateRemoteDescription(*parsed);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool iceLite =
       parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
 
+  // check for mismatch ufrag/pwd indicating ice restart
+  // can't just check the first one because it might be disabled
+  bool iceRestarting = false;
+  if (mCurrentRemoteDescription.get()) {
+    for (size_t i = 0;
+         !iceRestarting &&
+           i < mCurrentRemoteDescription->GetMediaSectionCount();
+         ++i) {
+
+      const SdpMediaSection& newMsection = parsed->GetMediaSection(i);
+      const SdpMediaSection& oldMsection =
+        mCurrentRemoteDescription->GetMediaSection(i);
+
+      if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+          mSdpHelper.MsectionIsDisabled(oldMsection)) {
+        continue;
+      }
+
+      iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+    }
+  }
+
   std::vector<std::string> iceOptions;
   if (parsed->GetAttributeList().HasAttribute(
           SdpAttribute::kIceOptionsAttribute)) {
     iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
   }
 
   switch (type) {
     case kJsepSdpOffer:
@@ -1224,16 +1253,17 @@ JsepSessionImpl::SetRemoteDescription(Js
       break;
     case kJsepSdpRollback:
       MOZ_CRASH(); // Handled above
   }
 
   if (NS_SUCCEEDED(rv)) {
     mRemoteIsIceLite = iceLite;
     mIceOptions = iceOptions;
+    mRemoteIceIsRestarting = iceRestarting;
   }
 
   return rv;
 }
 
 nsresult
 JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                          const UniquePtr<Sdp>& remote)
@@ -1515,22 +1545,28 @@ JsepSessionImpl::AddTransportAttributes(
 
   msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole));
 
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::CopyPreviousTransportParams(const Sdp& oldAnswer,
+                                             const Sdp& offerersPreviousSdp,
                                              const Sdp& newOffer,
                                              Sdp* newLocal)
 {
   for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
     if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
-        mSdpHelper.AreOldTransportParamsValid(oldAnswer, newOffer, i)) {
+        mSdpHelper.AreOldTransportParamsValid(oldAnswer,
+                                              offerersPreviousSdp,
+                                              newOffer,
+                                              i) &&
+        !mRemoteIceIsRestarting
+       ) {
       // If newLocal is an offer, this will be the number of components we used
       // last time, and if it is an answer, this will be the number of
       // components we've decided we're using now.
       size_t numComponents = mTransports[i]->mComponents;
       nsresult rv = mSdpHelper.CopyTransportParams(
           numComponents,
           mCurrentLocalDescription->GetMediaSection(i),
           &newLocal->GetMediaSection(i));
@@ -1836,45 +1872,47 @@ JsepSessionImpl::ValidateRemoteDescripti
   SdpHelper::BundledMids bundledMids;
   nsresult rv = GetNegotiatedBundledMids(&bundledMids);
   NS_ENSURE_SUCCESS(rv, rv);
 
   SdpHelper::BundledMids newBundledMids;
   rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // check for partial ice restart, which is not supported
+  Maybe<bool> iceCredsDiffer;
   for (size_t i = 0;
        i < mCurrentRemoteDescription->GetMediaSectionCount();
        ++i) {
-    if (mSdpHelper.MsectionIsDisabled(description.GetMediaSection(i)) ||
-        mSdpHelper.MsectionIsDisabled(mCurrentRemoteDescription->GetMediaSection(i))) {
+
+    const SdpMediaSection& newMsection = description.GetMediaSection(i);
+    const SdpMediaSection& oldMsection =
+      mCurrentRemoteDescription->GetMediaSection(i);
+
+    if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+        mSdpHelper.MsectionIsDisabled(oldMsection)) {
       continue;
     }
 
-    if (mCurrentRemoteDescription->GetMediaSection(i).GetMediaType() !=
-        description.GetMediaSection(i).GetMediaType()) {
+    if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
       JSEP_SET_ERROR("Remote description changes the media type of m-line "
                      << i);
       return NS_ERROR_INVALID_ARG;
     }
 
-    const SdpAttributeList& newAttrs(
-        description.GetMediaSection(i).GetAttributeList());
-    const SdpAttributeList& oldAttrs(
-        mCurrentRemoteDescription->GetMediaSection(i).GetAttributeList());
-
-    if ((newAttrs.GetIceUfrag() != oldAttrs.GetIceUfrag()) ||
-        (newAttrs.GetIcePwd() != oldAttrs.GetIcePwd())) {
-      JSEP_SET_ERROR("ICE restart is unsupported at this time "
+    bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+    // Detect whether all the creds are the same or all are different
+    if (!iceCredsDiffer.isSome()) {
+      // for the first msection capture whether creds are different or same
+      iceCredsDiffer = mozilla::Some(differ);
+    } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) {
+      // subsequent msections must match the first sections
+      JSEP_SET_ERROR("Partial ICE restart is unsupported at this time "
                      "(new remote description changes either the ice-ufrag "
-                     "or ice-pwd)" <<
-                     "ice-ufrag (old): " << oldAttrs.GetIceUfrag() <<
-                     "ice-ufrag (new): " << newAttrs.GetIceUfrag() <<
-                     "ice-pwd (old): " << oldAttrs.GetIcePwd() <<
-                     "ice-pwd (new): " << newAttrs.GetIcePwd());
+                     "or ice-pwd on fewer than all msections)");
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -30,16 +30,17 @@ class JsepSessionImpl : public JsepSessi
 {
 public:
   JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen)
       : JsepSession(name),
         mIsOfferer(false),
         mWasOffererLastTime(false),
         mIceControlling(false),
         mRemoteIsIceLite(false),
+        mRemoteIceIsRestarting(false),
         mBundlePolicy(kBundleBalanced),
         mSessionId(0),
         mSessionVersion(0),
         mUuidGen(Move(uuidgen)),
         mSdpHelper(&mLastError)
   {
   }
 
@@ -48,24 +49,32 @@ public:
 
   virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) override;
 
   virtual nsresult RemoveTrack(const std::string& streamId,
                                const std::string& trackId) override;
 
   virtual nsresult SetIceCredentials(const std::string& ufrag,
                                      const std::string& pwd) override;
+  virtual const std::string& GetUfrag() const override { return mIceUfrag; }
+  virtual const std::string& GetPwd() const override { return mIcePwd; }
   nsresult SetBundlePolicy(JsepBundlePolicy policy) override;
 
   virtual bool
   RemoteIsIceLite() const override
   {
     return mRemoteIsIceLite;
   }
 
+  virtual bool
+  RemoteIceIsRestarting() const override
+  {
+    return mRemoteIceIsRestarting;
+  }
+
   virtual std::vector<std::string>
   GetIceOptions() const override
   {
     return mIceOptions;
   }
 
   virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
                                       const std::vector<uint8_t>& value) override;
@@ -217,16 +226,17 @@ private:
                                 const Sdp& sdp,
                                 const SdpMediaSection& msection,
                                 RefPtr<JsepTrack>* track);
   nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                    const UniquePtr<Sdp>& remote);
   nsresult AddTransportAttributes(SdpMediaSection* msection,
                                   SdpSetupAttribute::Role dtlsRole);
   nsresult CopyPreviousTransportParams(const Sdp& oldAnswer,
+                                       const Sdp& offerersPreviousSdp,
                                        const Sdp& newOffer,
                                        Sdp* newLocal);
   nsresult SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp);
   // Non-const so it can assign m-line index to tracks
   nsresult SetupOfferMSectionsByType(SdpMediaSection::MediaType type,
                                      Maybe<size_t> offerToReceive,
                                      Sdp* sdp);
   nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype,
@@ -297,16 +307,17 @@ private:
   std::vector<JsepTrackPair> mNegotiatedTrackPairs;
 
   bool mIsOfferer;
   bool mWasOffererLastTime;
   bool mIceControlling;
   std::string mIceUfrag;
   std::string mIcePwd;
   bool mRemoteIsIceLite;
+  bool mRemoteIceIsRestarting;
   std::vector<std::string> mIceOptions;
   JsepBundlePolicy mBundlePolicy;
   std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
   uint64_t mSessionId;
   uint64_t mSessionVersion;
   std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions;
   std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions;
   UniquePtr<JsepUuidGenerator> mUuidGen;
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -184,40 +184,69 @@ NegotiatedDetailsToVideoCodecConfigs(con
 static void
 FinalizeTransportFlow_s(RefPtr<PeerConnectionMedia> aPCMedia,
                         RefPtr<TransportFlow> aFlow, size_t aLevel,
                         bool aIsRtcp,
                         nsAutoPtr<PtrVector<TransportLayer> > aLayerList)
 {
   TransportLayerIce* ice =
       static_cast<TransportLayerIce*>(aLayerList->values.front());
-  ice->SetParameters(
-      aPCMedia->ice_ctx(), aPCMedia->ice_media_stream(aLevel), aIsRtcp ? 2 : 1);
+  ice->SetParameters(aPCMedia->ice_ctx(),
+                     aPCMedia->ice_media_stream(aLevel),
+                     aIsRtcp ? 2 : 1);
   nsAutoPtr<std::queue<TransportLayer*> > layerQueue(
       new std::queue<TransportLayer*>);
   for (auto i = aLayerList->values.begin(); i != aLayerList->values.end();
        ++i) {
     layerQueue->push(*i);
   }
   aLayerList->values.clear();
   (void)aFlow->PushLayers(layerQueue); // TODO(bug 854518): Process errors.
 }
 
+static void
+AddNewIceStreamForRestart_s(RefPtr<PeerConnectionMedia> aPCMedia,
+                            RefPtr<TransportFlow> aFlow,
+                            size_t aLevel,
+                            bool aIsRtcp)
+{
+  TransportLayerIce* ice =
+      static_cast<TransportLayerIce*>(aFlow->GetLayer("ice"));
+  ice->SetParameters(aPCMedia->ice_ctx(),
+                     aPCMedia->ice_media_stream(aLevel),
+                     aIsRtcp ? 2 : 1);
+}
+
 nsresult
 MediaPipelineFactory::CreateOrGetTransportFlow(
     size_t aLevel,
     bool aIsRtcp,
     const JsepTransport& aTransport,
     RefPtr<TransportFlow>* aFlowOutparam)
 {
   nsresult rv;
   RefPtr<TransportFlow> flow;
 
   flow = mPCMedia->GetTransportFlow(aLevel, aIsRtcp);
   if (flow) {
+    if (mPCMedia->ice_ctx_hdlr()->IsRestarting()) {
+      MOZ_MTLOG(ML_INFO, "Flow[" << flow->id() << "]: "
+                                 << "detected ICE restart - level: "
+                                 << aLevel << " rtcp: " << aIsRtcp);
+
+      rv = mPCMedia->GetSTSThread()->Dispatch(
+          WrapRunnableNM(AddNewIceStreamForRestart_s,
+                         mPCMedia, flow, aLevel, aIsRtcp),
+          NS_DISPATCH_NORMAL);
+      if (NS_FAILED(rv)) {
+        MOZ_MTLOG(ML_ERROR, "Failed to dispatch AddNewIceStreamForRestart_s");
+        return rv;
+      }
+    }
+
     *aFlowOutparam = flow;
     return NS_OK;
   }
 
   std::ostringstream osId;
   osId << mPC->GetHandle() << ":" << aLevel << ","
        << (aIsRtcp ? "rtcp" : "rtp");
   flow = new TransportFlow(osId.str());
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1500,26 +1500,29 @@ PeerConnectionImpl::NotifyDataChannel(al
 #endif
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions)
 {
   JsepOfferOptions options;
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  // convert the RTCOfferOptions to JsepOfferOptions
   if (aOptions.mOfferToReceiveAudio.WasPassed()) {
     options.mOfferToReceiveAudio =
       mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value()));
   }
 
   if (aOptions.mOfferToReceiveVideo.WasPassed()) {
     options.mOfferToReceiveVideo =
         mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value()));
   }
 
+  options.mIceRestart = mozilla::Some(aOptions.mIceRestart);
+
   if (aOptions.mMozDontOfferDataChannel.WasPassed()) {
     options.mDontOfferDataChannel =
       mozilla::Some(aOptions.mMozDontOfferDataChannel.Value());
   }
 #endif
   return CreateOffer(options);
 }
 
@@ -1536,33 +1539,50 @@ static void DeferredCreateOffer(const st
   }
 }
 
 // Used by unit tests and the IDL CreateOffer.
 NS_IMETHODIMP
 PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions)
 {
   PC_AUTO_ENTER_API_CALL(true);
+  bool restartIce = aOptions.mIceRestart.isSome() && *(aOptions.mIceRestart);
+  if (!restartIce && mMedia->ice_ctx_hdlr()->IsRestarting()) {
+    RollbackIceRestart();
+  }
 
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (!pco) {
     return NS_OK;
   }
 
   if (!PeerConnectionCtx::GetInstance()->isReady()) {
     // Uh oh. We're not ready yet. Enqueue this operation.
     PeerConnectionCtx::GetInstance()->queueJSEPOperation(
         WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions));
     STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)");
     return NS_OK;
   }
 
   CSFLogDebug(logTag, "CreateOffer()");
 
-  nsresult nrv = ConfigureJsepSessionCodecs();
+  nsresult nrv;
+  if (aOptions.mIceRestart.isSome() && *(aOptions.mIceRestart) &&
+      !mMedia->ice_ctx_hdlr()->IsRestarting()) {
+    CSFLogInfo(logTag, "Offerer restarting ice");
+    nrv = SetupIceRestart();
+    if (NS_FAILED(nrv)) {
+      CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u",
+                           __FUNCTION__,
+                           static_cast<unsigned>(nrv));
+      return nrv;
+    }
+  }
+
+  nrv = ConfigureJsepSessionCodecs();
   if (NS_FAILED(nrv)) {
     CSFLogError(logTag, "Failed to configure codecs");
     return nrv;
   }
 
   STAMP_TIMECARD(mTimeCard, "Create Offer");
 
   std::string offer;
@@ -1597,23 +1617,36 @@ PeerConnectionImpl::CreateAnswer()
   PC_AUTO_ENTER_API_CALL(true);
 
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (!pco) {
     return NS_OK;
   }
 
   CSFLogDebug(logTag, "CreateAnswer()");
+
+  nsresult nrv;
+  if (mJsepSession->RemoteIceIsRestarting()) {
+    CSFLogInfo(logTag, "Answerer restarting ice");
+    nrv = SetupIceRestart();
+    if (NS_FAILED(nrv)) {
+      CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u",
+                           __FUNCTION__,
+                           static_cast<unsigned>(nrv));
+      return nrv;
+    }
+  }
+
   STAMP_TIMECARD(mTimeCard, "Create Answer");
   // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to
   // add it as a param to CreateAnswer, and convert it here.
   JsepAnswerOptions options;
   std::string answer;
 
-  nsresult nrv = mJsepSession->CreateAnswer(options, &answer);
+  nrv = mJsepSession->CreateAnswer(options, &answer);
   JSErrorResult rv;
   if (NS_FAILED(nrv)) {
     Error error;
     switch (nrv) {
       case NS_ERROR_UNEXPECTED:
         error = kInvalidState;
         break;
       default:
@@ -1628,16 +1661,69 @@ PeerConnectionImpl::CreateAnswer()
     pco->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
   }
 
   UpdateSignalingState();
 
   return NS_OK;
 }
 
+nsresult
+PeerConnectionImpl::SetupIceRestart()
+{
+  if (mMedia->ice_ctx_hdlr()->IsRestarting()) {
+    CSFLogError(logTag, "%s: ICE already restarting",
+                         __FUNCTION__);
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  std::string ufrag = mMedia->ice_ctx()->GetNewUfrag();
+  std::string pwd = mMedia->ice_ctx()->GetNewPwd();
+  if (ufrag.empty() || pwd.empty()) {
+    CSFLogError(logTag, "%s: Bad ICE credentials (ufrag:'%s'/pwd:'%s')",
+                         __FUNCTION__,
+                         ufrag.c_str(), pwd.c_str());
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // hold on to the current ice creds in case of rollback
+  mPreviousIceUfrag = mJsepSession->GetUfrag();
+  mPreviousIcePwd = mJsepSession->GetPwd();
+  mMedia->BeginIceRestart(ufrag, pwd);
+
+  nsresult nrv = mJsepSession->SetIceCredentials(ufrag, pwd);
+  if (NS_FAILED(nrv)) {
+    CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+                         __FUNCTION__,
+                         static_cast<unsigned>(nrv));
+    return nrv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::RollbackIceRestart()
+{
+  mMedia->RollbackIceRestart();
+  // put back the previous ice creds
+  nsresult nrv = mJsepSession->SetIceCredentials(mPreviousIceUfrag,
+                                                 mPreviousIcePwd);
+  if (NS_FAILED(nrv)) {
+    CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+                         __FUNCTION__,
+                         static_cast<unsigned>(nrv));
+    return nrv;
+  }
+  mPreviousIceUfrag = "";
+  mPreviousIcePwd = "";
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
   if (!aSDP) {
     CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__);
     return NS_ERROR_FAILURE;
@@ -2838,16 +2924,20 @@ PeerConnectionImpl::SetSignalingState_m(
        !rollback)) {
     mMedia->EnsureTransports(*mJsepSession);
   }
 
   mSignalingState = aSignalingState;
 
   bool fireNegotiationNeeded = false;
   if (mSignalingState == PCImplSignalingState::SignalingStable) {
+    if (rollback && mMedia->ice_ctx_hdlr()->IsRestarting()) {
+      RollbackIceRestart();
+    }
+
     // Either negotiation is done, or we've rolled back. In either case, we
     // need to re-evaluate whether further negotiation is required.
     mNegotiationNeeded = false;
     // If we're rolling back a local offer, we might need to remove some
     // transports, but nothing further needs to be done.
     mMedia->ActivateOrRemoveTransports(*mJsepSession);
     if (!rollback) {
       mMedia->UpdateMediaPipelines(*mJsepSession);
@@ -3129,16 +3219,27 @@ void PeerConnectionImpl::IceConnectionSt
           Telemetry::WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE,
           mAddCandidateErrorCount);
     }
   }
 #endif
 
   mIceConnectionState = domState;
 
+  if (mIceConnectionState == PCImplIceConnectionState::Connected ||
+      mIceConnectionState == PCImplIceConnectionState::Completed ||
+      mIceConnectionState == PCImplIceConnectionState::Failed) {
+    if (mMedia->ice_ctx_hdlr()->IsRestarting()) {
+      mMedia->FinalizeIceRestart();
+      // clear the previous ice creds since they are no longer needed
+      mPreviousIceUfrag = "";
+      mPreviousIcePwd = "";
+    }
+  }
+
   // Would be nice if we had a means of converting one of these dom enums
   // to a string that wasn't almost as much text as this switch statement...
   switch (mIceConnectionState) {
     case PCImplIceConnectionState::New:
       STAMP_TIMECARD(mTimeCard, "Ice state: new");
       break;
     case PCImplIceConnectionState::Checking:
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -707,16 +707,19 @@ private:
                                             SdpMediaSection::MediaType type,
                                             const std::string& streamId,
                                             const std::string& trackId);
 
   nsresult AddTrackToJsepSession(SdpMediaSection::MediaType type,
                                  const std::string& streamId,
                                  const std::string& trackId);
 
+  nsresult SetupIceRestart();
+  nsresult RollbackIceRestart();
+
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   static void GetStatsForPCObserver_s(
       const std::string& pcHandle,
       nsAutoPtr<RTCStatsQuery> query);
 
   // Sends an RTCStatsReport to JS. Must run on main thread.
   static void DeliverStatsReportToPCObserver_m(
       const std::string& pcHandle,
@@ -799,16 +802,18 @@ private:
 
   bool mAllowIceLoopback;
   bool mAllowIceLinkLocal;
   RefPtr<PeerConnectionMedia> mMedia;
 
   // The JSEP negotiation session.
   mozilla::UniquePtr<PCUuidGenerator> mUuidGen;
   mozilla::UniquePtr<mozilla::JsepSession> mJsepSession;
+  std::string mPreviousIceUfrag; // used during rollback of ice restart
+  std::string mPreviousIcePwd; // used during rollback of ice restart
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // Start time of ICE, used for telemetry
   mozilla::TimeStamp mIceStartTime;
   // Start time of call used for Telemetry
   mozilla::TimeStamp mStartTime;
 #endif
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -206,17 +206,17 @@ PeerConnectionMedia::ProtocolProxyQueryH
   }
 
   rv = proxyinfo.GetPort(&httpsProxyPort);
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "%s: Failed to get proxy server port", __FUNCTION__);
     return;
   }
 
-  if (pcm_->mIceCtx.get()) {
+  if (pcm_->mIceCtxHdlr.get()) {
     assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16));
     // Note that this could check if PrivacyRequested() is set on the PC and
     // remove "webrtc" from the ALPN list.  But that would only work if the PC
     // was constructed with a peerIdentity constraint, not when isolated
     // streams are added.  If we ever need to signal to the proxy that the
     // media is isolated, then we would need to restructure this code.
     pcm_->mProxyServer.reset(
       new NrIceProxyServer(httpsProxyHost.get(),
@@ -229,17 +229,17 @@ PeerConnectionMedia::ProtocolProxyQueryH
 }
 
 NS_IMPL_ISUPPORTS(PeerConnectionMedia::ProtocolProxyQueryHandler, nsIProtocolProxyCallback)
 
 PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
     : mParent(parent),
       mParentHandle(parent->GetHandle()),
       mParentName(parent->GetName()),
-      mIceCtx(nullptr),
+      mIceCtxHdlr(nullptr),
       mDNSResolver(new NrIceResolver()),
       mUuidGen(MakeUnique<PCUuidGenerator>()),
       mMainThread(mParent->GetMainThread()),
       mSTSThread(mParent->GetSTSThread()),
       mProxyResolveCompleted(false) {
 }
 
 nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
@@ -304,70 +304,63 @@ nsresult PeerConnectionMedia::Init(const
   }
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
   if (!XRE_IsParentProcess()) {
     CSFLogError(logTag, "%s: ICE TCP not support on e10s", __FUNCTION__);
     ice_tcp = false;
   }
-  bool default_address_only = Preferences::GetBool(
-    "media.peerconnection.ice.default_address_only", false);
 #else
   bool ice_tcp = false;
-  bool default_address_only = false;
 #endif
-
+  bool default_address_only = GetPrefDefaultAddressOnly();
 
   // TODO(ekr@rtfm.com): need some way to set not offerer later
   // Looks like a bug in the NrIceCtx API.
-  mIceCtx = NrIceCtxHandler::Create("PC:" + mParentName,
-                                    true, // Offerer
-                                    mParent->GetAllowIceLoopback(),
-                                    ice_tcp,
-                                    mParent->GetAllowIceLinkLocal(),
-                                    default_address_only,
-                                    policy);
-  if(!mIceCtx) {
+  mIceCtxHdlr = NrIceCtxHandler::Create("PC:" + mParentName,
+                                        true, // Offerer
+                                        mParent->GetAllowIceLoopback(),
+                                        ice_tcp,
+                                        mParent->GetAllowIceLinkLocal(),
+                                        default_address_only,
+                                        policy);
+  if(!mIceCtxHdlr) {
     CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
 
-  if (NS_FAILED(rv = mIceCtx->SetStunServers(stun_servers))) {
+  if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetStunServers(stun_servers))) {
     CSFLogError(logTag, "%s: Failed to set stun servers", __FUNCTION__);
     return rv;
   }
   // Give us a way to globally turn off TURN support
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   bool disabled = Preferences::GetBool("media.peerconnection.turn.disable", false);
 #else
   bool disabled = false;
 #endif
   if (!disabled) {
-    if (NS_FAILED(rv = mIceCtx->SetTurnServers(turn_servers))) {
+    if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetTurnServers(turn_servers))) {
       CSFLogError(logTag, "%s: Failed to set turn servers", __FUNCTION__);
       return rv;
     }
   } else if (turn_servers.size() != 0) {
     CSFLogError(logTag, "%s: Setting turn servers disabled", __FUNCTION__);
   }
   if (NS_FAILED(rv = mDNSResolver->Init())) {
     CSFLogError(logTag, "%s: Failed to initialize dns resolver", __FUNCTION__);
     return rv;
   }
-  if (NS_FAILED(rv = mIceCtx->SetResolver(mDNSResolver->AllocateResolver()))) {
+  if (NS_FAILED(rv =
+      mIceCtxHdlr->ctx()->SetResolver(mDNSResolver->AllocateResolver()))) {
     CSFLogError(logTag, "%s: Failed to get dns resolver", __FUNCTION__);
     return rv;
   }
-  mIceCtx->SignalGatheringStateChange.connect(
-      this,
-      &PeerConnectionMedia::IceGatheringStateChange_s);
-  mIceCtx->SignalConnectionStateChange.connect(
-      this,
-      &PeerConnectionMedia::IceConnectionStateChange_s);
+  ConnectSignals(mIceCtxHdlr->ctx().get());
 
   return NS_OK;
 }
 
 void
 PeerConnectionMedia::EnsureTransports(const JsepSession& aSession)
 {
   auto transports = aSession.GetTransports();
@@ -383,38 +376,39 @@ PeerConnectionMedia::EnsureTransports(co
   }
 
   GatherIfReady();
 }
 
 void
 PeerConnectionMedia::EnsureTransport_s(size_t aLevel, size_t aComponentCount)
 {
-  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aLevel));
+  RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aLevel));
   if (!stream) {
     CSFLogDebug(logTag, "%s: Creating ICE media stream=%u components=%u",
                 mParentHandle.c_str(),
                 static_cast<unsigned>(aLevel),
                 static_cast<unsigned>(aComponentCount));
 
     std::ostringstream os;
     os << mParentName << " aLevel=" << aLevel;
-    RefPtr<NrIceMediaStream> stream = mIceCtx->CreateStream(os.str().c_str(),
-                                                            aComponentCount);
+    RefPtr<NrIceMediaStream> stream =
+      mIceCtxHdlr->CreateStream(os.str().c_str(),
+                                aComponentCount);
 
     if (!stream) {
       CSFLogError(logTag, "Failed to create ICE stream.");
       return;
     }
 
     stream->SetLevel(aLevel);
     stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s);
     stream->SignalCandidate.connect(this,
                                     &PeerConnectionMedia::OnCandidateFound_s);
-    mIceCtx->SetStream(aLevel, stream);
+    mIceCtxHdlr->ctx()->SetStream(aLevel, stream);
   }
 }
 
 void
 PeerConnectionMedia::ActivateOrRemoveTransports(const JsepSession& aSession)
 {
   auto transports = aSession.GetTransports();
   for (size_t i = 0; i < transports.size(); ++i) {
@@ -465,21 +459,21 @@ PeerConnectionMedia::ActivateOrRemoveTra
     const std::string& aUfrag,
     const std::string& aPassword,
     const std::vector<std::string>& aCandidateList) {
 
   if (!aComponentCount) {
     CSFLogDebug(logTag, "%s: Removing ICE media stream=%u",
                 mParentHandle.c_str(),
                 static_cast<unsigned>(aMLine));
-    mIceCtx->SetStream(aMLine, nullptr);
+    mIceCtxHdlr->ctx()->SetStream(aMLine, nullptr);
     return;
   }
 
-  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aMLine));
+  RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
   if (!stream) {
     MOZ_ASSERT(false);
     return;
   }
 
   if (!stream->HasParsedAttributes()) {
     CSFLogDebug(logTag, "%s: Activating ICE media stream=%u components=%u",
                 mParentHandle.c_str(),
@@ -504,18 +498,18 @@ PeerConnectionMedia::ActivateOrRemoveTra
       stream->DisableComponent(c + 1);
     }
   }
 }
 
 void
 PeerConnectionMedia::RemoveTransportsAtOrAfter_s(size_t aMLine)
 {
-  for (size_t i = aMLine; i < mIceCtx->GetStreamCount(); ++i) {
-    mIceCtx->SetStream(i, nullptr);
+  for (size_t i = aMLine; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) {
+    mIceCtxHdlr->ctx()->SetStream(i, nullptr);
   }
 }
 
 nsresult PeerConnectionMedia::UpdateMediaPipelines(
     const JsepSession& session) {
   auto trackPairs = session.GetNegotiatedTrackPairs();
   MediaPipelineFactory factory(this);
   nsresult rv;
@@ -575,26 +569,151 @@ PeerConnectionMedia::StartIceChecks_s(
 
   if (!aIceOptionsList.empty()) {
     attributes.push_back("ice-options:");
     for (auto i = aIceOptionsList.begin(); i != aIceOptionsList.end(); ++i) {
       attributes.back() += *i + ' ';
     }
   }
 
-  nsresult rv = mIceCtx->ParseGlobalAttributes(attributes);
+  nsresult rv = mIceCtxHdlr->ctx()->ParseGlobalAttributes(attributes);
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "%s: couldn't parse global parameters", __FUNCTION__ );
   }
 
-  mIceCtx->SetControlling(aIsControlling ?
-                          NrIceCtx::ICE_CONTROLLING :
-                          NrIceCtx::ICE_CONTROLLED);
+  mIceCtxHdlr->ctx()->SetControlling(aIsControlling ?
+                                     NrIceCtx::ICE_CONTROLLING :
+                                     NrIceCtx::ICE_CONTROLLED);
+
+  mIceCtxHdlr->ctx()->StartChecks();
+}
+
+void
+PeerConnectionMedia::BeginIceRestart(const std::string& ufrag,
+                                     const std::string& pwd)
+{
+  ASSERT_ON_THREAD(mMainThread);
+
+  bool default_address_only = GetPrefDefaultAddressOnly();
+  RefPtr<NrIceCtx> new_ctx = mIceCtxHdlr->CreateCtx(ufrag,
+                                                    pwd,
+                                                    default_address_only);
+
+  RUN_ON_THREAD(GetSTSThread(),
+                WrapRunnable(
+                    RefPtr<PeerConnectionMedia>(this),
+                    &PeerConnectionMedia::BeginIceRestart_s,
+                    new_ctx),
+                NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx)
+{
+  ASSERT_ON_THREAD(mSTSThread);
+
+  // hold the original context so we can disconnect signals if needed
+  RefPtr<NrIceCtx> originalCtx = mIceCtxHdlr->ctx();
+
+  mIceCtxHdlr->BeginIceRestart(new_ctx);
+  if (mIceCtxHdlr->IsRestarting()) {
+    ConnectSignals(mIceCtxHdlr->ctx().get(), originalCtx.get());
+  }
+}
+
+void
+PeerConnectionMedia::FinalizeIceRestart()
+{
+  ASSERT_ON_THREAD(mMainThread);
+
+  RUN_ON_THREAD(GetSTSThread(),
+                WrapRunnable(
+                    RefPtr<PeerConnectionMedia>(this),
+                    &PeerConnectionMedia::FinalizeIceRestart_s),
+                NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::FinalizeIceRestart_s()
+{
+  ASSERT_ON_THREAD(mSTSThread);
+
+  mIceCtxHdlr->FinalizeIceRestart();
+}
+
+void
+PeerConnectionMedia::RollbackIceRestart()
+{
+  ASSERT_ON_THREAD(mMainThread);
 
-  mIceCtx->StartChecks();
+  RUN_ON_THREAD(GetSTSThread(),
+                WrapRunnable(
+                    RefPtr<PeerConnectionMedia>(this),
+                    &PeerConnectionMedia::RollbackIceRestart_s),
+                NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::RollbackIceRestart_s()
+{
+  ASSERT_ON_THREAD(mSTSThread);
+  if (!mIceCtxHdlr->IsRestarting()) {
+    return;
+  }
+
+  // hold the restart context so we can disconnect signals
+  RefPtr<NrIceCtx> restartCtx = mIceCtxHdlr->ctx();
+
+  mIceCtxHdlr->RollbackIceRestart();
+  ConnectSignals(mIceCtxHdlr->ctx().get(), restartCtx.get());
+}
+
+bool
+PeerConnectionMedia::GetPrefDefaultAddressOnly() const
+{
+  ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  bool default_address_only = Preferences::GetBool(
+    "media.peerconnection.ice.default_address_only", false);
+#else
+  bool default_address_only = false;
+#endif
+  return default_address_only;
+}
+
+void
+PeerConnectionMedia::ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx)
+{
+  aCtx->SignalGatheringStateChange.connect(
+      this,
+      &PeerConnectionMedia::IceGatheringStateChange_s);
+  aCtx->SignalConnectionStateChange.connect(
+      this,
+      &PeerConnectionMedia::IceConnectionStateChange_s);
+
+  if (aOldCtx) {
+    MOZ_ASSERT(aCtx != aOldCtx);
+    aOldCtx->SignalGatheringStateChange.disconnect(this);
+    aOldCtx->SignalConnectionStateChange.disconnect(this);
+
+    // if the old and new connection state and/or gathering state is
+    // different fire the state update.  Note: we don't fire the update
+    // if the state is *INIT since updates for the INIT state aren't
+    // sent during the normal flow. (mjf)
+    if (aOldCtx->connection_state() != aCtx->connection_state() &&
+        aCtx->connection_state() != NrIceCtx::ICE_CTX_INIT) {
+      aCtx->SignalConnectionStateChange(aCtx, aCtx->connection_state());
+    }
+
+    if (aOldCtx->gathering_state() != aCtx->gathering_state() &&
+        aCtx->gathering_state() != NrIceCtx::ICE_CTX_GATHER_INIT) {
+      aCtx->SignalGatheringStateChange(aCtx, aCtx->gathering_state());
+    }
+  }
 }
 
 void
 PeerConnectionMedia::AddIceCandidate(const std::string& candidate,
                                      const std::string& mid,
                                      uint32_t aMLine) {
   RUN_ON_THREAD(GetSTSThread(),
                 WrapRunnable(
@@ -604,17 +723,17 @@ PeerConnectionMedia::AddIceCandidate(con
                     std::string(mid),
                     aMLine),
                 NS_DISPATCH_NORMAL);
 }
 void
 PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate,
                                        const std::string& aMid,
                                        uint32_t aMLine) {
-  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aMLine));
+  RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
   if (!stream) {
     CSFLogError(logTag, "No ICE stream for candidate at level %u: %s",
                         static_cast<unsigned>(aMLine), aCandidate.c_str());
     return;
   }
 
   nsresult rv = stream->ParseTrickleCandidate(aCandidate);
   if (NS_FAILED(rv)) {
@@ -660,31 +779,32 @@ PeerConnectionMedia::GatherIfReady() {
         &PeerConnectionMedia::EnsureIceGathering_s));
 
   PerformOrEnqueueIceCtxOperation(runnable);
 }
 
 void
 PeerConnectionMedia::EnsureIceGathering_s() {
   if (mProxyServer) {
-    mIceCtx->SetProxyServer(*mProxyServer);
+    mIceCtxHdlr->ctx()->SetProxyServer(*mProxyServer);
   }
 
   // Start gathering, but only if there are streams
-  for (size_t i = 0; i < mIceCtx->GetStreamCount(); ++i) {
-    if (mIceCtx->GetStream(i)) {
-      mIceCtx->StartGathering();
+  for (size_t i = 0; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) {
+    if (mIceCtxHdlr->ctx()->GetStream(i)) {
+      mIceCtxHdlr->ctx()->StartGathering();
       return;
     }
   }
 
   // If there are no streams, we're probably in a situation where we've rolled
   // back while still waiting for our proxy configuration to come back. Make
   // sure content knows that the rollback has stuck wrt gathering.
-  IceGatheringStateChange_s(mIceCtx.get(), NrIceCtx::ICE_CTX_GATHER_COMPLETE);
+  IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(),
+                            NrIceCtx::ICE_CTX_GATHER_COMPLETE);
 }
 
 nsresult
 PeerConnectionMedia::AddTrack(DOMMediaStream& aMediaStream,
                               const std::string& streamId,
                               MediaStreamTrack& aTrack,
                               const std::string& trackId)
 {
@@ -826,17 +946,17 @@ PeerConnectionMedia::ShutdownMediaTransp
   }
 
   for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
     mRemoteSourceStreams[i]->DetachTransport_s();
   }
 
   disconnect_all();
   mTransportFlows.clear();
-  mIceCtx = nullptr;
+  mIceCtxHdlr = nullptr;
 
   mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
                         NS_DISPATCH_NORMAL);
 }
 
 LocalSourceStreamInfo*
 PeerConnectionMedia::GetLocalStreamByIndex(int aIndex)
 {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -290,36 +290,45 @@ class PeerConnectionMedia : public sigsl
 
   PeerConnectionImpl* GetPC() { return mParent; }
   nsresult Init(const std::vector<NrIceStunServer>& stun_servers,
                 const std::vector<NrIceTurnServer>& turn_servers,
                 NrIceCtx::Policy policy);
   // WARNING: This destroys the object!
   void SelfDestruct();
 
-  RefPtr<NrIceCtxHandler> ice_ctx() const { return mIceCtx; }
+  RefPtr<NrIceCtxHandler> ice_ctx_hdlr() const { return mIceCtxHdlr; }
+  RefPtr<NrIceCtx> ice_ctx() const { return mIceCtxHdlr->ctx(); }
 
   RefPtr<NrIceMediaStream> ice_media_stream(size_t i) const {
-    return mIceCtx->GetStream(i);
+    return mIceCtxHdlr->ctx()->GetStream(i);
   }
 
   size_t num_ice_media_streams() const {
-    return mIceCtx->GetStreamCount();
+    return mIceCtxHdlr->ctx()->GetStreamCount();
   }
 
   // Ensure ICE transports exist that we might need when offer/answer concludes
   void EnsureTransports(const JsepSession& aSession);
 
   // Activate or remove ICE transports at the conclusion of offer/answer,
   // or when rollback occurs.
   void ActivateOrRemoveTransports(const JsepSession& aSession);
 
   // Start ICE checks.
   void StartIceChecks(const JsepSession& session);
 
+  // Begin ICE restart
+  void BeginIceRestart(const std::string& ufrag,
+                       const std::string& pwd);
+  // Finalize ICE restart
+  void FinalizeIceRestart();
+  // Abort ICE restart
+  void RollbackIceRestart();
+
   // Process a trickle ICE candidate.
   void AddIceCandidate(const std::string& candidate, const std::string& mid,
                        uint32_t aMLine);
 
   // Handle complete media pipelines.
   nsresult UpdateMediaPipelines(const JsepSession& session);
 
   // Add a track (main thread only)
@@ -502,16 +511,23 @@ class PeerConnectionMedia : public sigsl
   void GatherIfReady();
   void FlushIceCtxOperationQueueIfReady();
   void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
   void EnsureIceGathering_s();
   void StartIceChecks_s(bool aIsControlling,
                         bool aIsIceLite,
                         const std::vector<std::string>& aIceOptionsList);
 
+  void BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx);
+  void FinalizeIceRestart_s();
+  void RollbackIceRestart_s();
+  bool GetPrefDefaultAddressOnly() const;
+
+  void ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx=nullptr);
+
   // Process a trickle ICE candidate.
   void AddIceCandidate_s(const std::string& aCandidate, const std::string& aMid,
                          uint32_t aMLine);
 
 
   // ICE events
   void IceGatheringStateChange_s(NrIceCtx* ctx,
                                NrIceCtx::GatheringState state);
@@ -560,17 +576,17 @@ class PeerConnectionMedia : public sigsl
 
   // A list of streams provided by the other side
   // This is only accessed on the main thread (with one special exception)
   nsTArray<RefPtr<RemoteSourceStreamInfo> > mRemoteSourceStreams;
 
   std::map<size_t, std::pair<bool, RefPtr<MediaSessionConduit>>> mConduits;
 
   // ICE objects
-  RefPtr<NrIceCtxHandler> mIceCtx;
+  RefPtr<NrIceCtxHandler> mIceCtxHdlr;
 
   // DNS
   RefPtr<NrIceResolver> mDNSResolver;
 
   // Transport flows: even is RTP, odd is RTCP
   std::map<int, RefPtr<TransportFlow> > mTransportFlows;
 
   // UUID Generator
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -62,16 +62,17 @@ SdpHelper::CopyTransportParams(size_t nu
     newLocalAttrs.SetAttribute(new SdpRtcpAttribute(oldLocalAttrs.GetRtcp()));
   }
 
   return NS_OK;
 }
 
 bool
 SdpHelper::AreOldTransportParamsValid(const Sdp& oldAnswer,
+                                      const Sdp& offerersPreviousSdp,
                                       const Sdp& newOffer,
                                       size_t level)
 {
   if (MsectionIsDisabled(oldAnswer.GetMediaSection(level)) ||
       MsectionIsDisabled(newOffer.GetMediaSection(level))) {
     // Obvious
     return false;
   }
@@ -85,22 +86,39 @@ SdpHelper::AreOldTransportParamsValid(co
   if (newOffer.GetMediaSection(level).GetAttributeList().HasAttribute(
         SdpAttribute::kBundleOnlyAttribute) &&
       IsBundleSlave(newOffer, level)) {
     // It never makes sense to put transport attributes in a bundle-only
     // m-section
     return false;
   }
 
-  // TODO(bug 906986): Check for ICE restart (will need to pass the offerer's
-  // old SDP to compare it against |newOffer|)
+  if (IceCredentialsDiffer(newOffer.GetMediaSection(level),
+                           offerersPreviousSdp.GetMediaSection(level))) {
+    return false;
+  }
 
   return true;
 }
 
+bool
+SdpHelper::IceCredentialsDiffer(const SdpMediaSection& msection1,
+                                const SdpMediaSection& msection2)
+{
+  const SdpAttributeList& attrs1(msection1.GetAttributeList());
+  const SdpAttributeList& attrs2(msection2.GetAttributeList());
+
+  if ((attrs1.GetIceUfrag() != attrs2.GetIceUfrag()) ||
+      (attrs1.GetIcePwd() != attrs2.GetIcePwd())) {
+    return true;
+  }
+
+  return false;
+}
+
 nsresult
 SdpHelper::GetComponent(const std::string& candidate, size_t* component)
 {
   unsigned int temp;
   int32_t result = PR_sscanf(candidate.c_str(), "%*s %u", &temp);
   if (result == 1) {
     *component = temp;
     return NS_OK;
--- a/media/webrtc/signaling/src/sdp/SdpHelper.h
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.h
@@ -33,18 +33,21 @@ class SdpHelper {
     explicit SdpHelper(std::string* errorDest) : mLastError(*errorDest) {}
     ~SdpHelper() {}
 
     nsresult GetComponent(const std::string& candidate, size_t* component);
     nsresult CopyTransportParams(size_t numComponents,
                                  const SdpMediaSection& source,
                                  SdpMediaSection* dest);
     bool AreOldTransportParamsValid(const Sdp& oldAnswer,
+                                    const Sdp& offerersPreviousSdp,
                                     const Sdp& newOffer,
                                     size_t level);
+    bool IceCredentialsDiffer(const SdpMediaSection& msection1,
+                              const SdpMediaSection& msection2);
 
     bool MsectionIsDisabled(const SdpMediaSection& msection) const;
     static void DisableMsection(Sdp* sdp, SdpMediaSection* msection);
 
     // Maps each mid to the m-section that is the master of its bundle.
     // Mids that do not appear in an a=group:BUNDLE do not appear here.
     typedef std::map<std::string, const SdpMediaSection*> BundledMids;
 
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -86,16 +86,21 @@ class OfferOptions : public mozilla::Jse
 public:
   void setInt32Option(const char *namePtr, size_t value) {
     if (!strcmp(namePtr, "OfferToReceiveAudio")) {
       mOfferToReceiveAudio = mozilla::Some(value);
     } else if (!strcmp(namePtr, "OfferToReceiveVideo")) {
       mOfferToReceiveVideo = mozilla::Some(value);
     }
   }
+  void setBoolOption(const char* namePtr, bool value) {
+    if (!strcmp(namePtr, "IceRestart")) {
+      mIceRestart = mozilla::Some(value);
+    }
+  }
 private:
 };
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // XXX Workaround for bug 998092 to maintain the existing broken semantics
 template<>
@@ -532,17 +537,17 @@ class ParsedSDP {
     }
   }
 
   void DeleteLine(const std::string &objType)
   {
     DeleteLines(objType, 1);
   }
 
-  // Replaces the first instance of objType in the SDP with
+  // Replaces the index-th instance of objType in the SDP with
   // a new string.
   // If content is an empty string then the line will be removed
   void ReplaceLine(const std::string &objType,
                    const std::string &content,
                    size_t index = 0)
   {
     auto it = FindLine(objType, index);
     if(it != sdp_lines_.end()) {
@@ -2528,22 +2533,16 @@ TEST_P(SignalingTest, RenegotiationAnswe
   // ANSWER_AUDIO causes a new audio track to be added
   OfferAnswer(options, ANSWER_AUDIO);
 
   CloseStreams();
 }
 
 TEST_P(SignalingTest, BundleRenegotiation)
 {
-  if (UseBundle()) {
-    // We don't support ICE restart, which is a prereq for renegotiating bundle
-    // off.
-    return;
-  }
-
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV);
 
   // If we did bundle before, turn it off, if not, turn it on
   if (a1_->mBundleEnabled && a2_->mBundleEnabled) {
     a1_->SetBundleEnabled(false);
   } else {
     a1_->SetBundleEnabled(true);
@@ -3437,16 +3436,27 @@ TEST_P(SignalingTest, AudioOnlyG722Rejec
   ASSERT_EQ(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
 
   CheckPipelines();
   CheckStreams();
 
   CloseStreams();
 }
 
+TEST_P(SignalingTest, RestartIce)
+{
+  OfferOptions options;
+  OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+  options.setBoolOption("IceRestart", true);
+  OfferAnswer(options, OFFER_NONE);
+
+  CloseStreams();
+}
+
 TEST_P(SignalingTest, FullCallAudioNoMuxVideoMux)
 {
   if (UseBundle()) {
     // This test doesn't make sense for bundle
     return;
   }
 
   EnsureInit();