Bug 1203503 - part 2. replace proxy tunnel with new ipc-based tunnel r+bwc, r+mayhemer
authorPaul Vitale <paul.m.vitale@gmail.com>
Tue, 05 Jun 2018 12:10:16 -0500
changeset 446591 d87037beffaabb3af5cc2fb4780e0cbadab6e979
parent 446590 457ec4c616e332f5d28d5cce2c1a86f0ecd4cf54
child 446592 45d797322196ce27cd46dae55f0194e6a637ce1b
push id35045
push userncsoregi@mozilla.com
push dateThu, 15 Nov 2018 22:10:12 +0000
treeherdermozilla-central@0051c8d339a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1203503
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1203503 - part 2. replace proxy tunnel with new ipc-based tunnel r+bwc, r+mayhemer This replaces the tunnel using a new nr_socket implementation. Proxy detection code is still done in the peer connction. However, the result is only used to detect a proxy. The result is unused. Address resolution is done by necko code in the parent process. The new socket wraps a WebrtcProxyChannel which uses necko to handle proxy negotiation. This has a happy side effect of enabling all authentication modes that necko already supports for http proxies. This adds a protocol for Necko to manage, WebrtcProxyChannel. This new protocol serves as a pipe for a CONNECT tunnel. It is only used in WebRtc and not built in no WebRtc builds.
media/mtransport/WebrtcProxyChannelWrapper.cpp
media/mtransport/WebrtcProxyChannelWrapper.h
media/mtransport/build/moz.build
media/mtransport/common.build
media/mtransport/ipc/PWebrtcProxyChannel.ipdl
media/mtransport/ipc/WebrtcProxyChannel.cpp
media/mtransport/ipc/WebrtcProxyChannel.h
media/mtransport/ipc/WebrtcProxyChannelCallback.h
media/mtransport/ipc/WebrtcProxyChannelChild.cpp
media/mtransport/ipc/WebrtcProxyChannelChild.h
media/mtransport/ipc/WebrtcProxyChannelParent.cpp
media/mtransport/ipc/WebrtcProxyChannelParent.h
media/mtransport/ipc/WebrtcProxyLog.cpp
media/mtransport/ipc/WebrtcProxyLog.h
media/mtransport/ipc/moz.build
media/mtransport/nr_socket_proxy.cpp
media/mtransport/nr_socket_proxy.h
media/mtransport/nr_socket_proxy_config.cpp
media/mtransport/nr_socket_proxy_config.h
media/mtransport/nr_socket_prsock.cpp
media/mtransport/nr_socket_prsock.h
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/test/moz.build
media/mtransport/test/proxy_tunnel_socket_unittest.cpp
media/mtransport/test/webrtcproxychannel_unittest.cpp
media/mtransport/test_nr_socket.cpp
media/mtransport/third_party/nICEr/nicer.gyp
media/mtransport/third_party/nICEr/src/net/nr_proxy_tunnel.c
media/mtransport/third_party/nICEr/src/net/nr_proxy_tunnel.h
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/ipc/moz.build
new file mode 100644
--- /dev/null
+++ b/media/mtransport/WebrtcProxyChannelWrapper.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcProxyChannelWrapper.h"
+
+#include "mozilla/net/WebrtcProxyChannelChild.h"
+
+#include "nsIEventTarget.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+
+#include "nr_socket_proxy_config.h"
+
+namespace mozilla {
+namespace net {
+
+using std::shared_ptr;
+
+WebrtcProxyChannelWrapper::WebrtcProxyChannelWrapper(
+  WebrtcProxyChannelCallback* aCallbacks)
+  : mProxyCallbacks(aCallbacks)
+  , mWebrtcProxyChannel(nullptr)
+  , mMainThread(nullptr)
+  , mSocketThread(nullptr)
+{
+  mMainThread = GetMainThreadEventTarget();
+  mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+  MOZ_RELEASE_ASSERT(mMainThread, "no main thread");
+  MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread");
+}
+
+WebrtcProxyChannelWrapper::~WebrtcProxyChannelWrapper()
+{
+  MOZ_ASSERT(!mWebrtcProxyChannel, "webrtc proxy channel non-null");
+
+  // If we're never opened then we never get an OnClose from our parent process.
+  // We need to release our callbacks here safely.
+  NS_ProxyRelease("WebrtcProxyChannelWrapper::CleanUpCallbacks",
+                  mSocketThread,
+                  mProxyCallbacks.forget());
+}
+
+void
+WebrtcProxyChannelWrapper::AsyncOpen(
+  const nsCString& aHost,
+  const int& aPort,
+  const shared_ptr<NrSocketProxyConfig>& aConfig)
+{
+  if (!NS_IsMainThread()) {
+    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+      NewRunnableMethod<const nsCString,
+                        const int,
+                        const shared_ptr<NrSocketProxyConfig>>(
+        "WebrtcProxyChannelWrapper::AsyncOpen",
+        this,
+        &WebrtcProxyChannelWrapper::AsyncOpen,
+        aHost,
+        aPort,
+        aConfig)));
+    return;
+  }
+
+  MOZ_ASSERT(!mWebrtcProxyChannel, "wrapper already open");
+  mWebrtcProxyChannel = new WebrtcProxyChannelChild(this);
+  mWebrtcProxyChannel->AsyncOpen(aHost,
+                                 aPort,
+                                 aConfig->GetBrowser(),
+                                 nsContentUtils::GetSystemPrincipal(),
+                                 aConfig->GetAlpn());
+}
+
+void
+WebrtcProxyChannelWrapper::SendWrite(nsTArray<uint8_t>&& aReadData)
+{
+  if (!NS_IsMainThread()) {
+    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+      NewRunnableMethod<nsTArray<uint8_t>&&>(
+        "WebrtcProxyChannelWrapper::SendWrite",
+        this,
+        &WebrtcProxyChannelWrapper::SendWrite,
+        std::move(aReadData))));
+    return;
+  }
+
+  MOZ_ASSERT(mWebrtcProxyChannel, "webrtc proxy channel should be non-null");
+  mWebrtcProxyChannel->SendWrite(aReadData);
+}
+
+void
+WebrtcProxyChannelWrapper::Close()
+{
+  if (!NS_IsMainThread()) {
+    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+      NewRunnableMethod("WebrtcProxyChannelWrapper::Close",
+                        this,
+                        &WebrtcProxyChannelWrapper::Close)));
+    return;
+  }
+
+  // We're only open if we have a channel. Also Close() should be idempotent.
+  if (mWebrtcProxyChannel) {
+    RefPtr<WebrtcProxyChannelChild> child = mWebrtcProxyChannel;
+    mWebrtcProxyChannel = nullptr;
+    child->SendClose();
+  }
+}
+
+void
+WebrtcProxyChannelWrapper::OnClose(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
+
+  MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(
+    NewRunnableMethod<nsresult>("WebrtcProxyChannelWrapper::OnClose",
+                                mProxyCallbacks,
+                                &WebrtcProxyChannelCallback::OnClose,
+                                aReason)));
+
+  NS_ProxyRelease("WebrtcProxyChannelWrapper::CleanUpCallbacks",
+                  mSocketThread,
+                  mProxyCallbacks.forget());
+}
+
+void
+WebrtcProxyChannelWrapper::OnRead(nsTArray<uint8_t>&& aReadData)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
+
+  MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(
+    NewRunnableMethod<nsTArray<uint8_t>&&>(
+      "WebrtcProxyChannelWrapper::OnRead",
+      mProxyCallbacks,
+      &WebrtcProxyChannelCallback::OnRead,
+      std::move(aReadData))));
+}
+
+void
+WebrtcProxyChannelWrapper::OnConnected()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
+
+  MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(
+    NewRunnableMethod("WebrtcProxyChannelWrapper::OnConnected",
+                      mProxyCallbacks,
+                      &WebrtcProxyChannelCallback::OnConnected)));
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/WebrtcProxyChannelWrapper.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_proxy_channel_wrapper__
+#define webrtc_proxy_channel_wrapper__
+
+#include <memory>
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#include "mozilla/net/WebrtcProxyChannelCallback.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+
+class NrSocketProxyConfig;
+
+namespace net {
+
+class WebrtcProxyChannelChild;
+
+/**
+ * WebrtcProxyChannelWrapper is a protector class for mtransport and IPDL.
+ * mtransport and IPDL cannot include headers from each other due to conflicting
+ * typedefs. Also it helps users by dispatching calls to the appropriate thread
+ * based on mtransport's and IPDL's threading requirements.
+ *
+ * WebrtcProxyChannelWrapper is only used in the child process.
+ * WebrtcProxyChannelWrapper does not dispatch for the parent process.
+ * WebrtcProxyChannelCallback calls are dispatched to the STS thread.
+ * IPDL calls are dispatched to the main thread.
+ */
+class WebrtcProxyChannelWrapper : public WebrtcProxyChannelCallback
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcProxyChannelWrapper, override)
+
+  explicit WebrtcProxyChannelWrapper(WebrtcProxyChannelCallback* aCallbacks);
+
+  virtual void AsyncOpen(const nsCString& aHost,
+                         const int& aPort,
+                         const std::shared_ptr<NrSocketProxyConfig>& aConfig);
+  virtual void SendWrite(nsTArray<uint8_t>&& aReadData);
+  virtual void Close();
+
+  // WebrtcProxyChannelCallback
+  virtual void OnClose(nsresult aReason) override;
+  virtual void OnConnected() override;
+  virtual void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+protected:
+  RefPtr<WebrtcProxyChannelCallback> mProxyCallbacks;
+  RefPtr<WebrtcProxyChannelChild> mWebrtcProxyChannel;
+
+  nsCOMPtr<nsIEventTarget>      mMainThread;
+  nsCOMPtr<nsIEventTarget>      mSocketThread;
+
+  virtual ~WebrtcProxyChannelWrapper();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // webrtc_proxy_channel_wrapper__
--- a/media/mtransport/build/moz.build
+++ b/media/mtransport/build/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include("/ipc/chromium/chromium-config.mozbuild")
 
 EXPORTS.mtransport += [
     '../dtlsidentity.h',
     '../m_cpp_utils.h',
     '../mediapacket.h',
+    '../nr_socket_proxy_config.h',
     '../nricectx.h',
     '../nricemediastream.h',
     '../nriceresolverfake.h',
     '../nricestunaddr.h',
     '../rlogconnector.h',
     '../runnable_utils.h',
     '../sigslot.h',
     '../simpletokenbucket.h',
@@ -31,12 +32,13 @@ EXPORTS.mtransport += [
 
 include('../common.build')
 
 # Add libFuzzer configuration directives
 include('/tools/fuzzing/libfuzzer-config.mozbuild')
 
 # These files cannot be built in unified mode because of the redefinition of
 # getLogModule, UNIMPLEMENTED, nr_socket_long_term_violation_time,
-# nr_socket_short_term_violation_time.
+# nr_socket_short_term_violation_time, nrappkit/IPDL typedef conflicts in
+# PBrowserOrId and WebrtcProxyChannelChild.
 SOURCES += mtransport_cppsrcs
 
 FINAL_LIBRARY = 'xul'
--- a/media/mtransport/common.build
+++ b/media/mtransport/common.build
@@ -2,16 +2,18 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 mtransport_lcppsrcs = [
     'dtlsidentity.cpp',
     'mediapacket.cpp',
+    'nr_socket_proxy.cpp',
+    'nr_socket_proxy_config.cpp',
     'nr_socket_prsock.cpp',
     'nr_timer.cpp',
     'nricectx.cpp',
     'nricemediastream.cpp',
     'nriceresolver.cpp',
     'nriceresolverfake.cpp',
     'nricestunaddr.cpp',
     'nrinterfaceprioritizer.cpp',
@@ -22,16 +24,17 @@ mtransport_lcppsrcs = [
     'test_nr_socket.cpp',
     'transportflow.cpp',
     'transportlayer.cpp',
     'transportlayerdtls.cpp',
     'transportlayerice.cpp',
     'transportlayerlog.cpp',
     'transportlayerloopback.cpp',
     'transportlayersrtp.cpp',
+    'WebrtcProxyChannelWrapper.cpp',
 ]
 
 mtransport_cppsrcs = [
     '/media/mtransport/%s' % s for s in sorted(mtransport_lcppsrcs)
 ]
 
 LOCAL_INCLUDES += [
     '/media/mtransport/',
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/PWebrtcProxyChannel.ipdl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+async protocol PWebrtcProxyChannel
+{
+  manager PNecko;
+
+parent:
+  async AsyncOpen(nsCString aHost,
+                  int32_t aPort,
+                  OptionalLoadInfoArgs aLoadInfoArgs,
+                  nsCString aAlpn);
+  async Write(uint8_t[] aWriteData);
+  async Close();
+
+child:
+  async OnClose(nsresult aReason);
+  async OnConnected();
+  async OnRead(uint8_t[] aReadData);
+
+  async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannel.cpp
@@ -0,0 +1,549 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcProxyChannel.h"
+
+#include "nsHttpChannel.h"
+#include "nsIChannel.h"
+#include "nsIClassOfService.h"
+#include "nsIContentPolicy.h"
+#include "nsIEventTarget.h"
+#include "nsIIOService.h"
+#include "nsILoadInfo.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIURIMutator.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+
+#include "WebrtcProxyChannelCallback.h"
+#include "WebrtcProxyLog.h"
+
+namespace mozilla {
+namespace net {
+
+class WebrtcProxyData
+{
+public:
+  explicit WebrtcProxyData(nsTArray<uint8_t>&& aData)
+    : mData(aData)
+  {
+    MOZ_COUNT_CTOR(WebrtcProxyData);
+  }
+
+  ~WebrtcProxyData()
+  {
+    MOZ_COUNT_DTOR(WebrtcProxyData);
+  }
+
+  const nsTArray<uint8_t>& GetData() const
+  {
+    return mData;
+  }
+
+private:
+  nsTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcProxyChannel,
+                  nsIAuthPromptProvider,
+                  nsIHttpUpgradeListener,
+                  nsIInputStreamCallback,
+                  nsIInterfaceRequestor,
+                  nsIOutputStreamCallback,
+                  nsIRequestObserver,
+                  nsIStreamListener)
+
+WebrtcProxyChannel::WebrtcProxyChannel(nsIAuthPromptProvider* aAuthProvider,
+                                       WebrtcProxyChannelCallback* aCallbacks)
+    : mProxyCallbacks(aCallbacks)
+    , mClosed(false)
+    , mOpened(false)
+    , mWriteOffset(0)
+    , mAuthProvider(aAuthProvider)
+    , mTransport(nullptr)
+    , mSocketIn(nullptr)
+    , mSocketOut(nullptr)
+{
+  LOG(("WebrtcProxyChannel::WebrtcProxyChannel %p\n", this));
+  mMainThread = GetMainThreadEventTarget();
+  mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+  MOZ_RELEASE_ASSERT(mMainThread, "no main thread");
+  MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread");
+}
+
+WebrtcProxyChannel::~WebrtcProxyChannel()
+{
+  LOG(("WebrtcProxyChannel::~WebrtcProxyChannel %p\n", this));
+
+  NS_ProxyRelease("WebrtcProxyChannel::CleanUpAuthProvider",
+                  mMainThread,
+                  mAuthProvider.forget());
+}
+
+nsresult
+WebrtcProxyChannel::Write(nsTArray<uint8_t>&& aWriteData)
+{
+  LOG(("WebrtcProxyChannel::Write %p\n", this));
+  MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(
+    NewRunnableMethod<nsTArray<uint8_t>&&>("WebrtcProxyChannel::Write",
+                                           this,
+                                           &WebrtcProxyChannel::EnqueueWrite_s,
+                                           std::move(aWriteData))));
+
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannel::Close()
+{
+  LOG(("WebrtcProxyChannel::Close %p\n", this));
+
+  CloseWithReason(NS_OK);
+
+  return NS_OK;
+}
+
+void
+WebrtcProxyChannel::CloseWithReason(nsresult aReason)
+{
+  LOG(("WebrtcProxyChannel::CloseWithReason %p reason=%u\n",
+       this,
+       static_cast<uint32_t>(aReason)));
+
+  if (!OnSocketThread()) {
+    MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+
+    // Let's pretend we got an open even if we didn't to prevent an Open later.
+    mOpened = true;
+
+    MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(
+      NewRunnableMethod<nsresult>("WebrtcProxyChannel::CloseWithReason",
+                                  this,
+                                  &WebrtcProxyChannel::CloseWithReason,
+                                  aReason)));
+    return;
+  }
+
+  if (mClosed) {
+    return;
+  }
+
+  mClosed = true;
+
+  if (mSocketIn) {
+    mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+    mSocketIn = nullptr;
+  }
+
+  if (mSocketOut) {
+    mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+    mSocketOut = nullptr;
+  }
+
+  if (mTransport) {
+    mTransport->Close(NS_BASE_STREAM_CLOSED);
+    mTransport = nullptr;
+  }
+
+  NS_ProxyRelease("WebrtcProxyChannel::CleanUpAuthProvider",
+                  mMainThread,
+                  mAuthProvider.forget());
+  InvokeOnClose(aReason);
+}
+
+nsresult WebrtcProxyChannel::Open(const nsCString& aHost,
+                                  const int& aPort,
+                                  nsILoadInfo* aLoadInfo,
+                                  const nsCString& aAlpn)
+{
+  LOG(("WebrtcProxyChannel::AsyncOpen %p\n", this));
+
+  if (mOpened) {
+    LOG(("WebrtcProxyChannel %p: proxy channel already open\n", this));
+    CloseWithReason(NS_ERROR_FAILURE);
+    return NS_ERROR_FAILURE;
+  }
+
+  mOpened = true;
+
+  nsresult rv;
+  nsCString spec = NS_LITERAL_CSTRING("http://") + aHost;
+
+  nsCOMPtr<nsIURI> uri;
+  rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+         .SetSpec(spec)
+         .SetPort(aPort)
+         .Finalize(uri);
+
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel %p: bad proxy connect uri set\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+
+  nsCOMPtr<nsIIOService> ioService;
+  ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel %p: io service missing\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+
+  // -need to always tunnel since we're using a proxy
+  // -there shouldn't be an opportunity to send cookies, but explicitly disallow
+  // them anyway.
+  // -the previous proxy tunnel didn't support redirects e.g. 307. don't need to
+  // introduce new behavior. can't follow redirects on connect anyway.
+  nsCOMPtr<nsIChannel> localChannel;
+  rv = ioService->NewChannelFromURIWithProxyFlags2(
+         uri,
+         nullptr,
+         // Proxy flags are overridden by SetConnectOnly()
+         0,
+         aLoadInfo->LoadingNode(),
+         aLoadInfo->LoadingPrincipal(),
+         aLoadInfo->TriggeringPrincipal(),
+         nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS |
+         nsILoadInfo::SEC_COOKIES_OMIT |
+         // We need this flag to allow loads from any origin since this channel
+         // is being used to CONNECT to an HTTP proxy.
+         nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+         nsIContentPolicy::TYPE_OTHER,
+         getter_AddRefs(localChannel));
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel %p: bad open channel\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+
+  RefPtr<nsHttpChannel> httpChannel;
+  CallQueryInterface(localChannel, httpChannel.StartAssignment());
+
+  if (!httpChannel) {
+    LOG(("WebrtcProxyChannel %p: not an http channel\n", this));
+    CloseWithReason(NS_ERROR_FAILURE);
+    return NS_ERROR_FAILURE;
+  }
+
+  httpChannel->SetNotificationCallbacks(this);
+
+  // don't block webrtc proxy setup with other requests
+  // often more than one of these channels will be created all at once by ICE
+  nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(localChannel);
+  if (cos) {
+    cos->AddClassFlags(nsIClassOfService::Unblocked |
+                       nsIClassOfService::DontThrottle);
+  } else {
+    LOG(("WebrtcProxyChannel %p: could not set class of service\n", this));
+    CloseWithReason(NS_ERROR_FAILURE);
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = httpChannel->HTTPUpgrade(aAlpn, this);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = httpChannel->SetConnectOnly();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = NS_MaybeOpenChannelUsingAsyncOpen2(httpChannel, this);
+
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel %p: cannot async open\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+WebrtcProxyChannel::EnqueueWrite_s(nsTArray<uint8_t>&& aWriteData)
+{
+  LOG(("WebrtcProxyChannel::EnqueueWrite %p\n", this));
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(!mClosed, "webrtc proxy channel closed");
+
+  mWriteQueue.emplace_back(std::move(aWriteData));
+
+  if (mSocketOut) {
+    mSocketOut->AsyncWait(this, 0, 0, nullptr);
+  }
+}
+
+void
+WebrtcProxyChannel::InvokeOnClose(nsresult aReason)
+{
+  LOG(("WebrtcProxyChannel::InvokeOnClose %p\n", this));
+
+  if (!NS_IsMainThread()) {
+    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+      NewRunnableMethod<nsresult>("WebrtcProxyChannel::InvokeOnClose",
+                                  this,
+                                  &WebrtcProxyChannel::InvokeOnClose,
+                                  aReason)));
+    return;
+  }
+
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callback should be non-null");
+
+  mProxyCallbacks->OnClose(aReason);
+  mProxyCallbacks = nullptr;
+}
+
+void
+WebrtcProxyChannel::InvokeOnConnected()
+{
+  LOG(("WebrtcProxyChannel::InvokeOnConnected %p\n", this));
+
+  if (!NS_IsMainThread()) {
+    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+      NewRunnableMethod("WebrtcProxyChannel::InvokeOnConnected",
+                        this,
+                        &WebrtcProxyChannel::InvokeOnConnected)));
+    return;
+  }
+
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callback should be non-null");
+
+  mProxyCallbacks->OnConnected();
+}
+
+void
+WebrtcProxyChannel::InvokeOnRead(nsTArray<uint8_t>&& aReadData)
+{
+  LOG(("WebrtcProxyChannel::InvokeOnRead %p count=%zu\n",
+       this,
+       aReadData.Length()));
+
+  if (!NS_IsMainThread()) {
+    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+      NewRunnableMethod<nsTArray<uint8_t>&&>(
+        "WebrtcProxyChannel::InvokeOnRead",
+         this,
+         &WebrtcProxyChannel::InvokeOnRead,
+         std::move(aReadData))));
+    return;
+  }
+
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callback should be non-null");
+
+  mProxyCallbacks->OnRead(std::move(aReadData));
+}
+
+// nsIHttpUpgradeListener
+NS_IMETHODIMP
+WebrtcProxyChannel::OnTransportAvailable(nsISocketTransport* aTransport,
+                                         nsIAsyncInputStream* aSocketIn,
+                                         nsIAsyncOutputStream* aSocketOut)
+{
+  LOG(("WebrtcProxyChannel::OnTransportAvailable %p\n", this));
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(!mTransport, "already called transport available on webrtc proxy");
+
+  // Cancel any pending callbacks. The caller doesn't always cancel these
+  // awaits. We need to make sure they don't get them.
+  aSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+  aSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+
+  if (mClosed) {
+    LOG(("WebrtcProxyChannel::OnTransportAvailable %p closed\n", this));
+    return NS_OK;
+  }
+
+  mTransport = aTransport;
+  mSocketIn = aSocketIn;
+  mSocketOut = aSocketOut;
+
+  // pulled from nr_socket_prsock.cpp
+  uint32_t minBufferSize = 256 * 1024;
+  nsresult rv = mTransport->SetSendBufferSize(minBufferSize);
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel::OnTransportAvailable %p send failed\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+  rv = mTransport->SetRecvBufferSize(minBufferSize);
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel::OnTransportAvailable %p recv failed\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+
+  mSocketIn->AsyncWait(this, 0, 0, nullptr);
+
+  InvokeOnConnected();
+
+  return NS_OK;
+}
+
+// nsIRequestObserver (from nsIStreamListener)
+NS_IMETHODIMP
+WebrtcProxyChannel::OnStartRequest(nsIRequest* aRequest,
+                                   nsISupports* aContext)
+{
+  LOG(("WebrtcProxyChannel::OnStartRequest %p\n", this));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebrtcProxyChannel::OnStopRequest(nsIRequest* aRequest,
+                                  nsISupports* aContext,
+                                  nsresult aStatusCode)
+{
+  LOG(("WebrtcProxyChannel::OnStopRequest %p status=%u\n",
+       this,
+       static_cast<uint32_t>(aStatusCode)));
+
+  // see nsHttpChannel::ProcessFailedProxyConnect for most error codes
+  if (NS_FAILED(aStatusCode)) {
+    CloseWithReason(aStatusCode);
+    return aStatusCode;
+  }
+
+  return NS_OK;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+WebrtcProxyChannel::OnDataAvailable(nsIRequest* aRequest,
+                                    nsISupports* aContext,
+                                    nsIInputStream* aInputStream,
+                                    uint64_t aOffset,
+                                    uint32_t aCount)
+{
+  LOG(("WebrtcProxyChannel::OnDataAvailable %p count=%u\n", this, aCount));
+  MOZ_ASSERT(0, "unreachable data available");
+  return NS_OK;
+}
+
+// nsIInputStreamCallback
+NS_IMETHODIMP
+WebrtcProxyChannel::OnInputStreamReady(nsIAsyncInputStream* in)
+{
+  LOG(("WebrtcProxyChannel::OnInputStreamReady %p unwritten=%zu\n",
+       this,
+       CountUnwrittenBytes()));
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(!mClosed, "webrtc proxy channel closed");
+  MOZ_ASSERT(mTransport, "webrtc proxy channel not connected");
+  MOZ_ASSERT(mSocketIn == in, "wrong input stream");
+
+  char      buffer[9216];
+  uint32_t  remainingCapacity = sizeof(buffer);
+  uint32_t  read = 0;
+
+  while(remainingCapacity > 0) {
+    uint32_t count = 0;
+    nsresult rv = mSocketIn->Read(buffer + read, remainingCapacity, &count);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      break;
+    }
+
+    if (NS_FAILED(rv)) {
+      LOG(("WebrtcProxyChannel::OnInputStreamReady %p failed %u\n",
+           this,
+           static_cast<uint32_t>(rv)));
+      CloseWithReason(rv);
+      return rv;
+    }
+
+    // base stream closed
+    if (count == 0) {
+      LOG(("WebrtcProxyChannel::OnInputStreamReady %p connection closed\n",
+           this));
+      CloseWithReason(NS_ERROR_FAILURE);
+      return NS_OK;
+    }
+
+    remainingCapacity -= count;
+    read += count;
+  }
+
+  if (read > 0) {
+    nsTArray<uint8_t> array(read);
+    array.AppendElements(buffer, read);
+
+    InvokeOnRead(std::move(array));
+  }
+
+  mSocketIn->AsyncWait(this, 0, 0, nullptr);
+
+  return NS_OK;
+}
+
+// nsIOutputStreamCallback
+NS_IMETHODIMP
+WebrtcProxyChannel::OnOutputStreamReady(nsIAsyncOutputStream* out)
+{
+  LOG(("WebrtcProxyChannel::OnOutputStreamReady %p unwritten=%zu\n",
+       this,
+       CountUnwrittenBytes()));
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(!mClosed, "webrtc proxy channel closed");
+  MOZ_ASSERT(mTransport, "webrtc proxy channel not connected");
+  MOZ_ASSERT(mSocketOut == out, "wrong output stream");
+
+  while(!mWriteQueue.empty()) {
+    const WebrtcProxyData& data = mWriteQueue.front();
+
+    char* buffer = reinterpret_cast<char*>(
+      const_cast<uint8_t*>(data.GetData().Elements())) + mWriteOffset;
+    uint32_t toWrite = data.GetData().Length() - mWriteOffset;
+
+    uint32_t wrote = 0;
+    nsresult rv = mSocketOut->Write(buffer, toWrite, &wrote);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      mSocketOut->AsyncWait(this, 0, 0, nullptr);
+      return NS_OK;
+    }
+
+    if(NS_FAILED(rv)) {
+      LOG(("WebrtcProxyChannel::OnOutputStreamReady %p failed %u\n",
+           this,
+           static_cast<uint32_t>(rv)));
+      CloseWithReason(rv);
+      return NS_OK;
+    }
+
+    mWriteOffset += wrote;
+
+    if (toWrite == wrote) {
+      mWriteOffset = 0;
+      mWriteQueue.pop_front();
+    }
+  }
+
+  return NS_OK;
+}
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+WebrtcProxyChannel::GetInterface(const nsIID& iid, void** result)
+{
+  LOG(("WebrtcProxyChannel::GetInterface %p\n", this));
+
+  return QueryInterface(iid, result);
+}
+
+size_t
+WebrtcProxyChannel::CountUnwrittenBytes() const
+{
+  size_t count = 0;
+
+  for(const WebrtcProxyData& data : mWriteQueue) {
+    count += data.GetData().Length();
+  }
+
+  MOZ_ASSERT(count >= mWriteOffset, "offset exceeds write buffer length");
+
+  count -= mWriteOffset;
+
+  return count;
+}
+
+} // namespace net
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannel.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_proxy_channel_h__
+#define webrtc_proxy_channel_h__
+
+#include <list>
+
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+class nsILoadInfo;
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class WebrtcProxyChannelCallback;
+class WebrtcProxyData;
+
+class WebrtcProxyChannel : public nsIHttpUpgradeListener,
+                           public nsIStreamListener,
+                           public nsIInputStreamCallback,
+                           public nsIOutputStreamCallback,
+                           public nsIInterfaceRequestor,
+                           public nsIAuthPromptProvider
+{
+public:
+  NS_DECL_NSIHTTPUPGRADELISTENER
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSIINTERFACEREQUESTOR
+  NS_DECL_NSIOUTPUTSTREAMCALLBACK
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_FORWARD_SAFE_NSIAUTHPROMPTPROVIDER(mAuthProvider)
+
+  WebrtcProxyChannel(nsIAuthPromptProvider* aAuthProvider,
+                     WebrtcProxyChannelCallback* aProxyCallbacks);
+
+  nsresult Open(const nsCString& aHost,
+                const int& aPort,
+                nsILoadInfo* aLoadInfo,
+                const nsCString& aAlpn);
+  nsresult Write(nsTArray<uint8_t>&& aBytes);
+  nsresult Close();
+
+  size_t CountUnwrittenBytes() const;
+
+protected:
+  virtual ~WebrtcProxyChannel();
+
+  // protected for gtests
+  virtual void InvokeOnClose(nsresult aReason);
+  virtual void InvokeOnConnected();
+  virtual void InvokeOnRead(nsTArray<uint8_t>&& aReadData);
+
+  RefPtr<WebrtcProxyChannelCallback> mProxyCallbacks;
+
+private:
+  bool mClosed;
+  bool mOpened;
+
+  void EnqueueWrite_s(nsTArray<uint8_t>&& aWriteData);
+
+  void CloseWithReason(nsresult aReason);
+
+  size_t                          mWriteOffset;
+  std::list<WebrtcProxyData>      mWriteQueue;
+  nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
+
+  // Indicates that the channel is CONNECTed
+  nsCOMPtr<nsISocketTransport>    mTransport;
+  nsCOMPtr<nsIAsyncInputStream>   mSocketIn;
+  nsCOMPtr<nsIAsyncOutputStream>  mSocketOut;
+  nsCOMPtr<nsIEventTarget>        mMainThread;
+  nsCOMPtr<nsIEventTarget>        mSocketThread;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // webrtc_proxy_channel_h__
+
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannelCallback.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_proxy_channel_callback_h__
+#define webrtc_proxy_channel_callback_h__
+
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+class WebrtcProxyChannelCallback
+{
+public:
+  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+  virtual void OnClose(nsresult aReason) = 0;
+  virtual void OnConnected() = 0;
+  virtual void OnRead(nsTArray<uint8_t>&& aReadData) = 0;
+
+protected:
+  virtual ~WebrtcProxyChannelCallback() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // webrtc_proxy_channel_callback_h__
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannelChild.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcProxyChannelChild.h"
+
+#include "mozilla/dom/PBrowserOrId.h"
+#include "mozilla/net/NeckoChild.h"
+
+#include "LoadInfo.h"
+
+#include "WebrtcProxyLog.h"
+#include "WebrtcProxyChannelCallback.h"
+
+using namespace mozilla::ipc;
+
+using mozilla::dom::PBrowserOrId;
+
+namespace mozilla {
+namespace net {
+
+mozilla::ipc::IPCResult
+WebrtcProxyChannelChild::RecvOnClose(const nsresult& aReason)
+{
+  LOG(("WebrtcProxyChannelChild::RecvOnClose %p\n", this));
+
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
+  mProxyCallbacks->OnClose(aReason);
+  mProxyCallbacks = nullptr;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebrtcProxyChannelChild::RecvOnConnected()
+{
+  LOG(("WebrtcProxyChannelChild::RecvOnConnected %p\n", this));
+
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
+  mProxyCallbacks->OnConnected();
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebrtcProxyChannelChild::RecvOnRead(nsTArray<uint8_t>&& aReadData)
+{
+  LOG(("WebrtcProxyChannelChild::RecvOnRead %p\n", this));
+
+  MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
+  mProxyCallbacks->OnRead(std::move(aReadData));
+
+  return IPC_OK();
+}
+
+WebrtcProxyChannelChild::WebrtcProxyChannelChild(
+  WebrtcProxyChannelCallback* aProxyCallbacks)
+  : mProxyCallbacks(aProxyCallbacks)
+{
+  MOZ_COUNT_CTOR(WebrtcProxyChannelChild);
+
+  LOG(("WebrtcProxyChannelChild::WebrtcProxyChannelChild %p\n", this));
+}
+
+WebrtcProxyChannelChild::~WebrtcProxyChannelChild()
+{
+  MOZ_COUNT_DTOR(WebrtcProxyChannelChild);
+
+  LOG(("WebrtcProxyChannelChild::~WebrtcProxyChannelChild %p\n", this));
+}
+
+void
+WebrtcProxyChannelChild::AsyncOpen(const nsCString& aHost,
+                                   const int& aPort,
+                                   const PBrowserOrId& aBrowser,
+                                   nsIPrincipal* aLoadingPrincipal,
+                                   const nsCString& aAlpn)
+{
+  LOG(("WebrtcProxyChannelChild::AsyncOpen %p %s:%d\n",
+       this,
+       aHost.get(),
+       aPort));
+
+  MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+  AddIPDLReference();
+
+  gNeckoChild->SetEventTargetForActor(this, GetMainThreadEventTarget());
+  gNeckoChild->SendPWebrtcProxyChannelConstructor(this, aBrowser);
+
+  nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(aLoadingPrincipal,
+                                                nullptr,
+                                                nullptr,
+                                                0,
+                                                0);
+
+  OptionalLoadInfoArgs loadInfoArgs;
+  MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
+
+  SendAsyncOpen(aHost, aPort, loadInfoArgs, aAlpn);
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannelChild.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebrtcProxyChannelChild_h
+#define mozilla_net_WebrtcProxyChannelChild_h
+
+#include "mozilla/net/PWebrtcProxyChannelChild.h"
+
+namespace mozilla {
+
+namespace dom {
+class PBrowserOrId;
+} // namespace dom
+
+namespace net {
+
+class WebrtcProxyChannelCallback;
+
+class WebrtcProxyChannelChild : public PWebrtcProxyChannelChild
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcProxyChannelChild)
+
+  mozilla::ipc::IPCResult
+  RecvOnClose(const nsresult& aReason) override;
+
+  mozilla::ipc::IPCResult
+  RecvOnConnected() override;
+
+  mozilla::ipc::IPCResult
+  RecvOnRead(nsTArray<uint8_t>&& aReadData) override;
+
+  explicit WebrtcProxyChannelChild(WebrtcProxyChannelCallback* aProxyCallbacks);
+
+  void AsyncOpen(const nsCString& aHost,
+                 const int& aPort,
+                 const dom::PBrowserOrId& aBrowser,
+                 nsIPrincipal* aLoadingPrincipal,
+                 const nsCString& aAlpn);
+
+  void AddIPDLReference() { AddRef(); }
+  void ReleaseIPDLReference() { Release(); }
+
+protected:
+  virtual ~WebrtcProxyChannelChild();
+
+  RefPtr<WebrtcProxyChannelCallback> mProxyCallbacks;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebrtcProxyChannelChild_h
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannelParent.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcProxyChannelParent.h"
+
+#include "mozilla/net/NeckoParent.h"
+
+#include "WebrtcProxyChannel.h"
+#include "WebrtcProxyLog.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+mozilla::ipc::IPCResult
+WebrtcProxyChannelParent::RecvAsyncOpen(
+        const nsCString& aHost,
+        const int& aPort,
+        const OptionalLoadInfoArgs& aLoadInfoArgs,
+        const nsCString& aAlpn)
+{
+  LOG(("WebrtcProxyChannelParent::RecvAsyncOpen %p to %s:%d\n",
+      this,
+      aHost.get(),
+      aPort));
+
+  nsresult rv;
+
+  nsCOMPtr<nsILoadInfo> loadInfo;
+
+  rv = LoadInfoArgsToLoadInfo(aLoadInfoArgs, getter_AddRefs(loadInfo));
+  if (NS_FAILED(rv)) {
+    IProtocol* mgr = Manager();
+    OnClose(rv);
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+
+  MOZ_ASSERT(mChannel, "webrtc proxy channel should be non-null");
+  mChannel->Open(aHost, aPort, loadInfo, aAlpn);
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebrtcProxyChannelParent::RecvWrite(nsTArray<uint8_t>&& aWriteData)
+{
+  LOG(("WebrtcProxyChannelParent::RecvWrite %p for %zu\n",
+      this,
+      aWriteData.Length()));
+
+  // Need to check this here in case there are Writes in the queue after OnClose
+  if (mChannel) {
+    mChannel->Write(std::move(aWriteData));
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebrtcProxyChannelParent::RecvClose()
+{
+  LOG(("WebrtcProxyChannelParent::RecvClose %p\n", this));
+
+  CleanupChannel();
+
+  IProtocol* mgr = Manager();
+  if (!Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+
+  return IPC_OK();
+}
+
+void
+WebrtcProxyChannelParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  LOG(("WebrtcProxyChannelParent::ActorDestroy %p for %d\n", this, aWhy));
+
+  CleanupChannel();
+}
+
+WebrtcProxyChannelParent::WebrtcProxyChannelParent(
+  nsIAuthPromptProvider* aAuthProvider)
+{
+  MOZ_COUNT_CTOR(WebrtcProxyChannelParent);
+
+  LOG(("WebrtcProxyChannelParent::WebrtcProxyChannelParent %p\n", this));
+
+  mChannel = new WebrtcProxyChannel(aAuthProvider, this);
+}
+
+WebrtcProxyChannelParent::~WebrtcProxyChannelParent()
+{
+  MOZ_COUNT_DTOR(WebrtcProxyChannelParent);
+
+  LOG(("WebrtcProxyChannelParent::~WebrtcProxyChannelParent %p\n", this));
+
+  CleanupChannel();
+}
+
+// WebrtcProxyChannelCallback
+void
+WebrtcProxyChannelParent::OnClose(nsresult aReason)
+{
+  LOG(("WebrtcProxyChannelParent::OnClose %p\n", this));
+
+  if (mChannel) {
+    Unused << SendOnClose(aReason);
+  }
+
+  CleanupChannel();
+}
+
+void
+WebrtcProxyChannelParent::OnRead(nsTArray<uint8_t>&& aReadData)
+{
+  LOG(("WebrtcProxyChannelParent::OnRead %p %zu\n", this, aReadData.Length()));
+
+  if(mChannel && !SendOnRead(std::move(aReadData))) {
+    CleanupChannel();
+  }
+}
+
+void
+WebrtcProxyChannelParent::OnConnected()
+{
+  LOG(("WebrtcProxyChannelParent::OnConnected %p\n", this));
+
+  if(mChannel && !SendOnConnected()) {
+    CleanupChannel();
+  }
+}
+
+void
+WebrtcProxyChannelParent::CleanupChannel()
+{
+  if (mChannel) {
+    mChannel->Close();
+    mChannel = nullptr;
+  }
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyChannelParent.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebrtcProxyChannelParent_h
+#define mozilla_net_WebrtcProxyChannelParent_h
+
+#include "mozilla/net/PWebrtcProxyChannelParent.h"
+
+#include "WebrtcProxyChannelCallback.h"
+
+class nsIAuthPromptProvider;
+
+namespace mozilla {
+namespace net {
+
+class WebrtcProxyChannel;
+
+class WebrtcProxyChannelParent : public PWebrtcProxyChannelParent,
+                                 public WebrtcProxyChannelCallback
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcProxyChannelParent, override)
+
+  mozilla::ipc::IPCResult
+  RecvAsyncOpen(const nsCString& aHost,
+                const int& aPort,
+                const OptionalLoadInfoArgs& aLoadInfoArgs,
+                const nsCString& aAlpn) override;
+
+  mozilla::ipc::IPCResult
+  RecvWrite(nsTArray<uint8_t>&& aWriteData) override;
+
+  mozilla::ipc::IPCResult
+  RecvClose() override;
+
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  explicit WebrtcProxyChannelParent(nsIAuthPromptProvider* aAuthProvider);
+
+  // WebrtcProxyChannelCallback
+  void OnClose(nsresult aReason) override;
+  void OnConnected() override;
+  void OnRead(nsTArray<uint8_t>&& bytes) override;
+
+  void AddIPDLReference() { AddRef(); }
+  void ReleaseIPDLReference() { Release(); }
+
+protected:
+  virtual ~WebrtcProxyChannelParent();
+
+private:
+  void CleanupChannel();
+
+  // Indicates that IPC is open.
+  RefPtr<WebrtcProxyChannel> mChannel;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebrtcProxyChannelParent_h
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyLog.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcProxyLog.h"
+
+namespace mozilla {
+namespace net {
+LazyLogModule webrtcProxyLog("webrtcProxy");
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/ipc/WebrtcProxyLog.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_proxy_log_h__
+#define webrtc_proxy_log_h__
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace net {
+extern LazyLogModule webrtcProxyLog;
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
+#define LOG(args) MOZ_LOG(mozilla::net::webrtcProxyLog, mozilla::LogLevel::Debug, args)
+
+#endif // webrtc_proxy_log_h__
--- a/media/mtransport/ipc/moz.build
+++ b/media/mtransport/ipc/moz.build
@@ -2,23 +2,36 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla.net += [
     'NrIceStunAddrMessageUtils.h',
     'PStunAddrsParams.h',
     'StunAddrsRequestChild.h',
     'StunAddrsRequestParent.h',
+    'WebrtcProxyChannel.h',
+    'WebrtcProxyChannelCallback.h',
+    'WebrtcProxyChannelChild.h',
+    'WebrtcProxyChannelParent.h',
 ]
 
 UNIFIED_SOURCES += [
     'StunAddrsRequestChild.cpp',
     'StunAddrsRequestParent.cpp',
+    'WebrtcProxyChannel.cpp',
+    'WebrtcProxyChannelChild.cpp',
+    'WebrtcProxyChannelParent.cpp',
+    'WebrtcProxyLog.cpp',
 ]
 
 IPDL_SOURCES += [
     'PStunAddrsRequest.ipdl',
+    'PWebrtcProxyChannel.ipdl',
 ]
 
 include("/ipc/chromium/chromium-config.mozbuild")
 
 FINAL_LIBRARY = 'xul'
 
+LOCAL_INCLUDES += [
+    '/netwerk/base',
+    '/netwerk/protocol/http',
+]
new file mode 100644
--- /dev/null
+++ b/media/mtransport/nr_socket_proxy.cpp
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance, Mozilla nor
+  the names of its contributors may be used to endorse or promote
+  products derived from this software without specific prior written
+  permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "nr_socket_proxy.h"
+
+#include "mozilla/ErrorNames.h"
+
+#include "WebrtcProxyChannelWrapper.h"
+
+namespace mozilla
+{
+using namespace net;
+
+using std::shared_ptr;
+
+class NrSocketProxyData
+{
+public:
+  explicit NrSocketProxyData(nsTArray<uint8_t>&& aData)
+    : mData(aData)
+  {
+    MOZ_COUNT_CTOR(NrSocketProxyData);
+  }
+
+  ~NrSocketProxyData()
+  {
+    MOZ_COUNT_DTOR(NrSocketProxyData);
+  }
+
+  const nsTArray<uint8_t>& GetData() const
+  {
+    return mData;
+  }
+
+private:
+  nsTArray<uint8_t> mData;
+};
+
+NrSocketProxy::NrSocketProxy(const shared_ptr<NrSocketProxyConfig>& aConfig)
+  : mClosed(false)
+  , mReadOffset(0)
+  , mConfig(aConfig)
+  , mWebrtcProxyChannel(nullptr)
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::NrSocketProxy %p\n", this);
+  MOZ_ASSERT(mConfig, "config should not be null");
+}
+
+NrSocketProxy::~NrSocketProxy()
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::~NrSocketProxy %p\n", this);
+  MOZ_ASSERT(!mWebrtcProxyChannel, "webrtc proxy channel not null");
+}
+
+int
+NrSocketProxy::create(nr_transport_addr* aAddr)
+{
+  int32_t port;
+  nsCString host;
+
+  // Sanity check
+  if (nr_transport_addr_get_addrstring_and_port(aAddr, &host, &port)) {
+    return R_FAILED;
+  }
+
+  if (nr_transport_addr_copy(&my_addr_, aAddr)) {
+    return R_FAILED;
+  }
+
+  return 0;
+}
+
+int
+NrSocketProxy::connect(nr_transport_addr* aAddr)
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::Connect %p\n", this);
+
+  nsCString host;
+  int port;
+
+  if (nr_transport_addr_get_addrstring_and_port(aAddr, &host, &port)) {
+    return R_FAILED;
+  }
+
+  mWebrtcProxyChannel = new WebrtcProxyChannelWrapper(this);
+
+  mWebrtcProxyChannel->AsyncOpen(host, port, mConfig);
+
+  // trigger nr_socket_buffered to set write/read callback
+  return R_WOULDBLOCK;
+}
+
+void
+NrSocketProxy::close()
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::Close %p\n", this);
+
+  if (mClosed) {
+    return;
+  }
+
+  mClosed = true;
+
+  // We're not always open at this point.
+  if (mWebrtcProxyChannel) {
+    mWebrtcProxyChannel->Close();
+    mWebrtcProxyChannel = nullptr;
+  }
+}
+
+int
+NrSocketProxy::write(const void* aBuffer, size_t aCount, size_t* aWrote)
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,
+        "NrSocketProxy::Write %p count=%zu\n",
+        this,
+        aCount);
+
+  if (mClosed) {
+    return R_FAILED;
+  }
+
+  if (!aWrote) {
+    return R_FAILED;
+  }
+
+  *aWrote = aCount;
+
+  if (aCount > 0) {
+    nsTArray<uint8_t> writeData;
+    writeData.SetLength(aCount);
+    memcpy(writeData.Elements(), aBuffer, aCount);
+
+    mWebrtcProxyChannel->SendWrite(std::move(writeData));
+  }
+
+  return 0;
+}
+
+int
+NrSocketProxy::read(void* aBuffer, size_t aCount, size_t* aRead)
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::Read %p\n", this);
+
+  if (mClosed) {
+    return R_FAILED;
+  }
+
+  if (!aRead) {
+    return R_FAILED;
+  }
+
+  *aRead = 0;
+
+  if (mReadQueue.empty()) {
+    return R_WOULDBLOCK;
+  }
+
+  while(aCount > 0 && !mReadQueue.empty()) {
+    const NrSocketProxyData& data = mReadQueue.front();
+
+    size_t remainingCount = data.GetData().Length() - mReadOffset;
+    size_t amountToCopy = std::min(aCount, remainingCount);
+
+    char* buffer = static_cast<char*>(aBuffer) + (*aRead);
+
+    memcpy(buffer, data.GetData().Elements() + mReadOffset, amountToCopy);
+
+    mReadOffset += amountToCopy;
+    *aRead += amountToCopy;
+    aCount -= amountToCopy;
+
+    if (remainingCount == amountToCopy) {
+      mReadOffset = 0;
+      mReadQueue.pop_front();
+    }
+  }
+
+  return 0;
+}
+
+int
+NrSocketProxy::getaddr(nr_transport_addr* aAddr)
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::GetAddr %p\n", this);
+  return nr_transport_addr_copy(aAddr, &my_addr_);
+}
+
+int
+NrSocketProxy::sendto(const void* aBuffer,
+                      size_t aCount,
+                      int aFlags,
+                      nr_transport_addr* aAddr)
+{
+  // never call this
+  MOZ_ASSERT(0);
+  return R_FAILED;
+}
+
+int
+NrSocketProxy::recvfrom(void* aBuffer,
+                        size_t aCount,
+                        size_t* aRead,
+                        int aFlags,
+                        nr_transport_addr* aAddr)
+{
+  // never call this
+  MOZ_ASSERT(0);
+  return R_FAILED;
+}
+
+int
+NrSocketProxy::listen(int aBacklog)
+{
+  return R_INTERNAL;
+}
+
+int
+NrSocketProxy::accept(nr_transport_addr* aAddr, nr_socket** aSocket)
+{
+  return R_INTERNAL;
+}
+
+// WebrtcProxyChannelCallback
+void
+NrSocketProxy::OnClose(nsresult aReason)
+{
+  nsCString errorName;
+  GetErrorName(aReason, errorName);
+
+  r_log(LOG_GENERIC,LOG_ERR,
+        "NrSocketProxy::OnClose %p reason=%u name=%s\n",
+        this,
+        static_cast<uint32_t>(aReason),
+        errorName.get());
+
+  close();
+
+  DoCallbacks();
+}
+
+void
+NrSocketProxy::OnConnected()
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,"NrSocketProxy::OnConnected %p\n", this);
+
+  DoCallbacks();
+}
+
+void
+NrSocketProxy::OnRead(nsTArray<uint8_t>&& aReadData)
+{
+  r_log(LOG_GENERIC,LOG_DEBUG,
+        "NrSocketProxy::OnRead %p read=%zu\n",
+        this,
+        aReadData.Length());
+
+  mReadQueue.emplace_back(std::move(aReadData));
+
+  DoCallbacks();
+}
+
+void
+NrSocketProxy::DoCallbacks()
+{
+  size_t lastCount = -1;
+  size_t currentCount = 0;
+  while ((poll_flags() & PR_POLL_READ) != 0 &&
+         // Make sure whatever is reading knows we're closed. This doesn't need
+         // to happen for writes since ICE doesn't like a failing writable.
+         (mClosed || (currentCount = CountUnreadBytes()) > 0) &&
+         lastCount != currentCount) {
+    fire_callback(NR_ASYNC_WAIT_READ);
+    lastCount = currentCount;
+  }
+
+  // We're always ready to write after we're connected. The parent process will
+  // buffer writes for us.
+  if (!mClosed && mWebrtcProxyChannel && (poll_flags() & PR_POLL_WRITE) != 0) {
+    fire_callback(NR_ASYNC_WAIT_WRITE);
+  }
+}
+
+size_t
+NrSocketProxy::CountUnreadBytes() const
+{
+  size_t count = 0;
+
+  for(const NrSocketProxyData& data : mReadQueue) {
+    count += data.GetData().Length();
+  }
+
+  MOZ_ASSERT(count >= mReadOffset, "offset exceeds read buffer length");
+
+  count -= mReadOffset;
+
+  return count;
+}
+
+void
+NrSocketProxy::AssignChannel_DoNotUse(WebrtcProxyChannelWrapper* aWrapper) {
+  mWebrtcProxyChannel = aWrapper;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/nr_socket_proxy.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance, Mozilla nor
+  the names of its contributors may be used to endorse or promote
+  products derived from this software without specific prior written
+  permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef nr_socket_proxy_h__
+#define nr_socket_proxy_h__
+
+#include <list>
+
+#include "mozilla/net/WebrtcProxyChannelCallback.h"
+
+#include "nsTArray.h"
+
+extern "C" {
+#include "nr_api.h"
+#include "nr_socket.h"
+#include "transport_addr.h"
+}
+
+#include "nr_socket_prsock.h"
+
+namespace mozilla
+{
+using namespace net;
+
+namespace net
+{
+class WebrtcProxyChannelWrapper;
+} // namespace net
+
+class NrSocketProxyData;
+class NrSocketProxyConfig;
+
+class NrSocketProxy : public NrSocketBase,
+                      public WebrtcProxyChannelCallback
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrSocketProxy, override)
+
+  explicit NrSocketProxy(const std::shared_ptr<NrSocketProxyConfig>& aConfig);
+
+  // NrSocketBase
+  int create(nr_transport_addr *aAddr) override;
+  int connect(nr_transport_addr *aAddr) override;
+  void close() override;
+  int write(const void* aBuffer,
+            size_t aCount,
+            size_t* aWrote) override;
+  int read(void* aBuffer, size_t aCount, size_t* aRead) override;
+  int getaddr(nr_transport_addr* aAddr) override;
+  int sendto(const void* aBuffer,
+             size_t aCount,
+             int aFlags,
+             nr_transport_addr* aAddr) override;
+  int recvfrom(void* aBuffer,
+               size_t aCount,
+               size_t* aRead,
+               int aFlags,
+               nr_transport_addr* aAddr) override;
+  int listen(int aBacklog) override;
+  int accept(nr_transport_addr* aAddr, nr_socket** aSocket) override;
+
+  // WebrtcProxyChannelCallback
+  void OnClose(nsresult aReason) override;
+  void OnConnected() override;
+  void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+  size_t CountUnreadBytes() const;
+
+  // for gtests
+  void AssignChannel_DoNotUse(WebrtcProxyChannelWrapper* aWrapper);
+
+protected:
+  virtual ~NrSocketProxy();
+
+private:
+  void DoCallbacks();
+
+  bool mClosed;
+
+  size_t                        mReadOffset;
+  std::list<NrSocketProxyData>  mReadQueue;
+
+  std::shared_ptr<NrSocketProxyConfig> mConfig;
+
+  RefPtr<WebrtcProxyChannelWrapper> mWebrtcProxyChannel;
+};
+
+} // namespace mozilla
+
+#endif // nr_socket_proxy_h__
new file mode 100644
--- /dev/null
+++ b/media/mtransport/nr_socket_proxy_config.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nr_socket_proxy_config.h"
+
+#include "mozilla/dom/PBrowserOrId.h"
+
+namespace mozilla
+{
+
+class NrSocketProxyConfig::Private {
+public:
+  dom::PBrowserOrId mBrowser;
+  nsCString mAlpn;
+};
+
+NrSocketProxyConfig::NrSocketProxyConfig(const dom::PBrowserOrId& aBrowser,
+                                         const nsCString& aAlpn)
+    : mPrivate(new Private({aBrowser, aAlpn}))
+{}
+
+NrSocketProxyConfig::NrSocketProxyConfig(NrSocketProxyConfig&& aOrig)
+    : mPrivate(std::move(aOrig.mPrivate))
+{}
+
+NrSocketProxyConfig::~NrSocketProxyConfig()
+{}
+
+const dom::PBrowserOrId&
+NrSocketProxyConfig::GetBrowser() const
+{
+  return mPrivate->mBrowser;
+}
+
+const nsCString&
+NrSocketProxyConfig::GetAlpn() const
+{
+  return mPrivate->mAlpn;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/mtransport/nr_socket_proxy_config.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nr_socket_proxy_config__
+#define nr_socket_proxy_config__
+
+#include <memory>
+#include "nsString.h"
+
+class nsIPrincipal;
+
+namespace mozilla
+{
+namespace dom
+{
+class PBrowserOrId;
+}
+
+class NrSocketProxyConfig
+{
+public:
+  NrSocketProxyConfig(const dom::PBrowserOrId& aBrowser,
+                      const nsCString& aAlpn);
+  // We need to actually write the default impl ourselves, because the compiler
+  // needs to know how to destroy mPrivate in case an exception is thrown, even
+  // though we disable exceptions in our build.
+  NrSocketProxyConfig(NrSocketProxyConfig&& aOrig);
+
+  ~NrSocketProxyConfig();
+
+  const dom::PBrowserOrId& GetBrowser() const;
+  const nsCString& GetAlpn() const;
+
+private:
+  // PBrowserOrId includes stuff that conflicts with nICEr includes.
+  // Make it possible to include this header file without tripping over this
+  // problem.
+  class Private;
+  std::unique_ptr<Private> mPrivate;
+};
+
+} // namespace mozilla
+
+#endif // nr_socket_proxy_config__
--- a/media/mtransport/nr_socket_prsock.cpp
+++ b/media/mtransport/nr_socket_prsock.cpp
@@ -134,16 +134,17 @@ nrappkit copyright:
 #if defined(LOG_DEBUG)
 #define LOG_TEMP_DEBUG LOG_DEBUG
 #undef LOG_DEBUG
 #endif
 #undef strlcpy
 
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
+#include "nr_socket_proxy.h"
 
 #ifdef LOG_TEMP_INFO
 #define LOG_INFO LOG_TEMP_INFO
 #endif
 #ifdef LOG_TEMP_WARNING
 #define LOG_WARNING LOG_TEMP_WARNING
 #endif
 
@@ -2148,34 +2149,38 @@ static nr_socket_vtbl nr_socket_local_vt
   nr_socket_local_read,
   nr_socket_local_close,
   nr_socket_local_listen,
   nr_socket_local_accept
 };
 
 /* static */
 int
-NrSocketBase::CreateSocket(nr_transport_addr *addr, RefPtr<NrSocketBase> *sock)
+NrSocketBase::CreateSocket(nr_transport_addr *addr,
+                           RefPtr<NrSocketBase> *sock,
+                           const std::shared_ptr<NrSocketProxyConfig>& config)
 {
   int r, _status;
 
   // create IPC bridge for content process
   if (XRE_IsParentProcess()) {
     *sock = new NrSocket();
   } else {
     switch (addr->protocol) {
       case IPPROTO_UDP:
         *sock = new NrUdpSocketIpc();
         break;
       case IPPROTO_TCP:
 #if defined(MOZILLA_INTERNAL_API)
-        {
+        if (!config) {
           nsCOMPtr<nsIThread> main_thread;
           NS_GetMainThread(getter_AddRefs(main_thread));
           *sock = new NrTcpSocketIpc(main_thread.get());
+        } else {
+          *sock = new NrSocketProxy(config);
         }
 #else
         ABORT(R_REJECTED);
 #endif
         break;
     }
   }
 
@@ -2186,43 +2191,16 @@ NrSocketBase::CreateSocket(nr_transport_
   _status = 0;
 abort:
   if (_status) {
     *sock = nullptr;
   }
   return _status;
 }
 
-int nr_socket_local_create(void *obj, nr_transport_addr *addr, nr_socket **sockp) {
-  RefPtr<NrSocketBase> sock;
-  int r, _status;
-
-  r = NrSocketBase::CreateSocket(addr, &sock);
-  if (r) {
-    ABORT(r);
-  }
-
-  r = nr_socket_create_int(static_cast<void *>(sock),
-                           sock->vtbl(), sockp);
-  if (r)
-    ABORT(r);
-
-  _status = 0;
-
-  {
-    // We will release this reference in destroy(), not exactly the normal
-    // ownership model, but it is what it is.
-    NrSocketBase* dummy = sock.forget().take();
-    (void)dummy;
-  }
-
-abort:
-  return _status;
-}
-
 
 static int nr_socket_local_destroy(void **objp) {
   if(!objp || !*objp)
     return 0;
 
   NrSocketBase *sock = static_cast<NrSocketBase *>(*objp);
   *objp = nullptr;
 
--- a/media/mtransport/nr_socket_prsock.h
+++ b/media/mtransport/nr_socket_prsock.h
@@ -41,16 +41,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
 
 // Implementation of nICEr/nr_socket that is tied to the Gecko
 // SocketTransportService.
 
 #ifndef nr_socket_prsock__
 #define nr_socket_prsock__
 
+#include <memory>
 #include <queue>
 
 #include "nspr.h"
 #include "prio.h"
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsASocketHandler.h"
@@ -70,16 +71,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 #include "mozilla/ClearOnShutdown.h"
 
 // Stub declaration for nICEr type
 typedef struct nr_socket_vtbl_ nr_socket_vtbl;
 typedef struct nr_socket_ nr_socket;
 
 #if defined(MOZILLA_INTERNAL_API)
 namespace mozilla {
+class NrSocketProxyConfig;
 namespace dom {
 class TCPSocketChild;
 }
 }
 #endif
 
 namespace mozilla {
 
@@ -93,17 +95,19 @@ public:
     memset(cbs_, 0, sizeof(cbs_));
     memset(cb_args_, 0, sizeof(cb_args_));
     memset(&my_addr_, 0, sizeof(my_addr_));
   }
   virtual ~NrSocketBase() {}
 
   // Factory method; will create either an NrSocket, NrUdpSocketIpc, or
   // NrTcpSocketIpc as appropriate.
-  static int CreateSocket(nr_transport_addr *addr, RefPtr<NrSocketBase> *sock);
+  static int CreateSocket(nr_transport_addr *addr,
+                          RefPtr<NrSocketBase> *sock,
+                          const std::shared_ptr<NrSocketProxyConfig>& config);
 
   // the nr_socket APIs
   virtual int create(nr_transport_addr *addr) = 0;
   virtual int sendto(const void *msg, size_t len,
                      int flags, nr_transport_addr *to) = 0;
   virtual int recvfrom(void * buf, size_t maxlen,
                        size_t *len, int flags,
                        nr_transport_addr *from) = 0;
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -39,16 +39,17 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTI
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 #include <string>
 #include <vector>
 
+#include "nr_socket_proxy_config.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
 
 #include "logging.h"
 #include "nspr.h"
 #include "nss.h"
 #include "pk11pub.h"
@@ -74,17 +75,16 @@ extern "C" {
 #include "r_crc32.h"
 #include "r_memory.h"
 #include "ice_reg.h"
 #include "ice_util.h"
 #include "transport_addr.h"
 #include "nr_crypto.h"
 #include "nr_socket.h"
 #include "nr_socket_local.h"
-#include "nr_proxy_tunnel.h"
 #include "stun_client_ctx.h"
 #include "stun_reg.h"
 #include "stun_server_ctx.h"
 #include "stun_util.h"
 #include "ice_codeword.h"
 #include "ice_ctx.h"
 #include "ice_candidate.h"
 #include "ice_handler.h"
@@ -95,32 +95,43 @@ extern "C" {
 #include "nricemediastream.h"
 #include "nr_socket_prsock.h"
 #include "nrinterfaceprioritizer.h"
 #include "rlogconnector.h"
 #include "test_nr_socket.h"
 
 namespace mozilla {
 
+using std::shared_ptr;
+
 TimeStamp nr_socket_short_term_violation_time() {
   return NrSocketBase::short_term_violation_time();
 }
 
 TimeStamp nr_socket_long_term_violation_time() {
   return NrSocketBase::long_term_violation_time();
 }
 
 MOZ_MTLOG_MODULE("mtransport")
 
 const char kNrIceTransportUdp[] = "udp";
 const char kNrIceTransportTcp[] = "tcp";
 const char kNrIceTransportTls[] = "tls";
 
 static bool initialized = false;
 
+static int noop(void** obj) {
+  return 0;
+}
+
+static nr_socket_factory_vtbl ctx_socket_factory_vtbl = {
+  nr_socket_local_create,
+  noop
+};
+
 // Implement NSPR-based crypto algorithms
 static int nr_crypto_nss_random_bytes(UCHAR *buf, size_t len) {
   UniquePK11SlotInfo slot(PK11_GetInternalSlot());
   if (!slot)
     return R_INTERNAL;
 
   SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), buf, len);
   if (rv != SECSuccess)
@@ -283,17 +294,18 @@ NrIceCtx::NrIceCtx(const std::string& na
     ice_controlling_set_(false),
     streams_(),
     ctx_(nullptr),
     peer_(nullptr),
     ice_handler_vtbl_(nullptr),
     ice_handler_(nullptr),
     trickle_(true),
     policy_(policy),
-    nat_ (nullptr) {
+    nat_ (nullptr),
+    proxy_config_(nullptr) {
 }
 
 /* static */
 RefPtr<NrIceCtx>
 NrIceCtx::Create(const std::string& name,
                  bool allow_loopback,
                  bool tcp_enabled,
                  bool allow_link_local,
@@ -606,16 +618,29 @@ NrIceCtx::Initialize()
 
   r = nr_ice_ctx_create(const_cast<char *>(name_.c_str()), flags, &ctx_);
 
   if (r) {
     MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << name_ << "'");
     return false;
   }
 
+  // override default factory to capture optional proxy config when creating
+  // sockets.
+  nr_socket_factory* factory;
+  r = nr_socket_factory_create_int(this,
+                                   &ctx_socket_factory_vtbl,
+                                   &factory);
+
+  if (r) {
+    MOZ_MTLOG(LogLevel::Error, "Couldn't create ctx socket factory.");
+    return false;
+  }
+  nr_ice_ctx_set_socket_factory(ctx_, factory);
+
   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_, prioritizer);
   if (r) {
@@ -775,16 +800,17 @@ NrIceStats NrIceCtx::Destroy() {
     nr_ice_ctx_destroy(&ctx_);
   }
 
   delete ice_handler_vtbl_;
   delete ice_handler_;
 
   ice_handler_vtbl_ = nullptr;
   ice_handler_ = nullptr;
+  proxy_config_ = nullptr;
   streams_.clear();
 
   return stats;
 }
 
 NrIceCtx::~NrIceCtx() {
 }
 
@@ -870,53 +896,18 @@ nsresult NrIceCtx::SetResolver(nr_resolv
   if (r) {
     MOZ_MTLOG(ML_ERROR, "Couldn't set resolver for '" << name_ << "'");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
-nsresult NrIceCtx::SetProxyServer(const NrIceProxyServer& proxy_server) {
-  int r,_status;
-  nr_proxy_tunnel_config *config = nullptr;
-  nr_socket_wrapper_factory *wrapper = nullptr;
-
-  if ((r = nr_proxy_tunnel_config_create(&config))) {
-    ABORT(r);
-  }
-
-  if ((r = nr_proxy_tunnel_config_set_proxy(config,
-                                            proxy_server.host().c_str(),
-                                            proxy_server.port()))) {
-    ABORT(r);
-  }
-
-  if ((r = nr_proxy_tunnel_config_set_resolver(config, ctx_->resolver))) {
-    ABORT(r);
-  }
-
-  if ((r = nr_socket_wrapper_factory_proxy_tunnel_create(config, &wrapper))) {
-    MOZ_MTLOG(LogLevel::Error, "Couldn't create proxy tunnel wrapper.");
-    ABORT(r);
-  }
-
-  // nr_ice_ctx will own the wrapper after this call
-  if ((r = nr_ice_ctx_set_turn_tcp_socket_wrapper(ctx_, wrapper))) {
-    MOZ_MTLOG(ML_ERROR, "Couldn't set proxy for '" << name_ << "': " << r);
-    ABORT(r);
-  }
-
-  _status = 0;
-abort:
-  nr_proxy_tunnel_config_destroy(&config);
-  if (_status) {
-    nr_socket_wrapper_factory_destroy(&wrapper);
-    return NS_ERROR_FAILURE;
-  }
+nsresult NrIceCtx::SetProxyServer(NrSocketProxyConfig&& config) {
+  proxy_config_.reset(new NrSocketProxyConfig(std::move(config)));
   return NS_OK;
 }
 
 void NrIceCtx::SetCtxFlags(bool default_route_only, bool proxy_only) {
   ASSERT_ON_THREAD(sts_target_);
 
   if (default_route_only) {
     nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS);
@@ -1165,8 +1156,45 @@ void NrIceCtx::SetGatheringState(Gatheri
 void nr_ice_compute_codeword(char *buf, int len,char *codeword) {
     UINT4 c;
 
     r_crc32(buf,len,&c);
 
     PL_Base64Encode(reinterpret_cast<char*>(&c), 3, codeword);
     codeword[4] = 0;
 }
+
+int nr_socket_local_create(void *obj,
+                            nr_transport_addr *addr,
+                            nr_socket **sockp)
+{
+  using namespace mozilla;
+
+  RefPtr<NrSocketBase> sock;
+  int r, _status;
+  shared_ptr<NrSocketProxyConfig> config = nullptr;
+
+  if (obj) {
+    config = static_cast<NrIceCtx*>(obj)->GetProxyConfig();
+  }
+
+  r = NrSocketBase::CreateSocket(addr, &sock, config);
+  if (r) {
+    ABORT(r);
+  }
+
+  r = nr_socket_create_int(static_cast<void *>(sock),
+                           sock->vtbl(), sockp);
+  if (r)
+    ABORT(r);
+
+  _status = 0;
+
+  {
+    // We will release this reference in destroy(), not exactly the normal
+    // ownership model, but it is what it is.
+    NrSocketBase* dummy = sock.forget().take();
+    (void)dummy;
+  }
+
+abort:
+  return _status;
+}
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -45,16 +45,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Original author: ekr@rtfm.com
 
 // This is a wrapper around the nICEr ICE stack
 #ifndef nricectx_h__
 #define nricectx_h__
 
+#include <memory>
 #include <string>
 #include <vector>
 #include <map>
 
 #include "sigslot.h"
 
 #include "prnetdb.h"
 
@@ -81,16 +82,18 @@ typedef struct nr_ice_stun_server_ nr_ic
 typedef struct nr_ice_turn_server_ nr_ice_turn_server;
 typedef struct nr_resolver_ nr_resolver;
 typedef struct nr_proxy_tunnel_config_ nr_proxy_tunnel_config;
 
 typedef void* NR_SOCKET;
 
 namespace mozilla {
 
+class NrSocketProxyConfig;
+
 // Timestamps set whenever a packet is dropped due to global rate limiting
 // (see nr_socket_prsock.cpp)
 TimeStamp nr_socket_short_term_violation_time();
 TimeStamp nr_socket_long_term_violation_time();
 
 class NrIceMediaStream;
 
 extern const char kNrIceTransportUdp[];
@@ -170,35 +173,16 @@ class NrIceTurnServer : public NrIceStun
                   const std::vector<unsigned char>& password,
                   const char *transport) :
       NrIceStunServer(transport), username_(username), password_(password) {}
 
   std::string username_;
   std::vector<unsigned char> password_;
 };
 
-class NrIceProxyServer {
- public:
-  NrIceProxyServer(const std::string& host, uint16_t port,
-                   const std::string& alpn) :
-    host_(host), port_(port), alpn_(alpn) {
-  }
-
-  NrIceProxyServer() : NrIceProxyServer("", 0, "") {}
-
-  const std::string& host() const { return host_; }
-  uint16_t port() const { return port_; }
-  const std::string& alpn() const { return alpn_; }
-
- private:
-  std::string host_;
-  uint16_t port_;
-  std::string alpn_;
-};
-
 class TestNat;
 
 class NrIceStats {
  public:
   uint16_t stun_retransmits = 0;
   uint16_t turn_401s = 0;
   uint16_t turn_403s = 0;
   uint16_t turn_438s = 0;
@@ -326,17 +310,20 @@ class NrIceCtx {
   nsresult SetTurnServers(const std::vector<NrIceTurnServer>& turn_servers);
 
   // Provide the resolution provider. Must be called before
   // StartGathering.
   nsresult SetResolver(nr_resolver *resolver);
 
   // Provide the proxy address. Must be called before
   // StartGathering.
-  nsresult SetProxyServer(const NrIceProxyServer& proxy_server);
+  nsresult SetProxyServer(NrSocketProxyConfig&& config);
+
+  const std::shared_ptr<NrSocketProxyConfig>& GetProxyConfig()
+    { return proxy_config_; }
 
   void SetCtxFlags(bool default_route_only, bool proxy_only);
 
   // Start ICE gathering
   nsresult StartGathering(bool default_route_only, bool proxy_only);
 
   // Start checking
   nsresult StartChecks(bool offerer);
@@ -410,13 +397,14 @@ private:
   nr_ice_ctx *ctx_;
   nr_ice_peer_ctx *peer_;
   nr_ice_handler_vtbl* ice_handler_vtbl_;  // Must be pointer
   nr_ice_handler* ice_handler_;  // Must be pointer
   bool trickle_;
   nsCOMPtr<nsIEventTarget> sts_target_; // The thread to run on
   Policy policy_;
   RefPtr<TestNat> nat_;
+  std::shared_ptr<NrSocketProxyConfig> proxy_config_;
 };
 
 
 }  // close namespace
 #endif
--- a/media/mtransport/test/moz.build
+++ b/media/mtransport/test/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+include("/ipc/chromium/chromium-config.mozbuild")
+
 if CONFIG['OS_TARGET'] != 'WINNT':
     SOURCES += [
         'buffered_stun_socket_unittest.cpp',
         'ice_unittest.cpp',
         'multi_tcp_socket_unittest.cpp',
         'nrappkit_unittest.cpp',
         'proxy_tunnel_socket_unittest.cpp',
         'rlogconnector_unittest.cpp',
@@ -16,16 +18,17 @@ if CONFIG['OS_TARGET'] != 'WINNT':
         'simpletokenbucket_unittest.cpp',
         'sockettransportservice_unittest.cpp',
         'stunserver.cpp',
         'test_nr_socket_ice_unittest.cpp',
         'test_nr_socket_unittest.cpp',
         'TestSyncRunnable.cpp',
         'transport_unittests.cpp',
         'turn_unittest.cpp',
+        'webrtcproxychannel_unittest.cpp',
     ]
 
     if CONFIG['MOZ_SCTP']:
         SOURCES += [
             'sctp_unittest.cpp',
         ]
 
 
--- a/media/mtransport/test/proxy_tunnel_socket_unittest.cpp
+++ b/media/mtransport/test/proxy_tunnel_socket_unittest.cpp
@@ -1,339 +1,304 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Original authors: ekr@rtfm.com; ryan@tokbox.com
 
-#include <iostream>
+#include <vector>
+#include <numeric>
 
-extern "C" {
-#include "nr_api.h"
-#include "nr_socket.h"
-#include "nr_proxy_tunnel.h"
-#include "transport_addr.h"
-#include "stun.h"
-}
+#include "mozilla/dom/PBrowserOrId.h"
 
-#include "dummysocket.h"
-
-#include "nr_socket_prsock.h"
-#include "nriceresolverfake.h"
+#include "nr_socket_proxy.h"
+#include "nr_socket_proxy_config.h"
+#include "WebrtcProxyChannelWrapper.h"
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
 #include "gtest_utils.h"
 
 using namespace mozilla;
 
-const std::string kRemoteAddr = "192.0.2.133";
-const uint16_t kRemotePort = 3333;
+// update TestReadMultipleSizes if you change this
+const std::string kHelloMessage = "HELLO IS IT ME YOU'RE LOOKING FOR?";
 
-const std::string kProxyHost = "example.com";
-const std::string kProxyAddr = "192.0.2.134";
-const uint16_t kProxyPort = 9999;
+typedef mozilla::dom::PBrowserOrId PBrowserOrId;
 
-const std::string kHelloMessage = "HELLO";
-const std::string kGarbageMessage = "xxxxxxxxxx";
+class NrSocketProxyTest : public MtransportTest {
+ public:
+  NrSocketProxyTest()
+    : mSProxy(nullptr)
+    , nr_socket_(nullptr)
+    , mEmptyArray(0)
+    , mReadChunkSize(0)
+    , mReadChunkSizeIncrement(1)
+    , mReadAllowance(-1)
+    , mConnected(false) {}
 
-std::string connect_message(const std::string &host, uint16_t port, const std::string &alpn, const std::string &tail) {
-  std::stringstream ss;
-  ss << "CONNECT " << host << ":" << port << " HTTP/1.0\r\n";
-  if (!alpn.empty()) {
-    ss << "ALPN: " << alpn << "\r\n";
+  void SetUp() override {
+    nsCString alpn = NS_LITERAL_CSTRING("webrtc");
+    std::shared_ptr<NrSocketProxyConfig> config;
+    config.reset(new NrSocketProxyConfig(PBrowserOrId(), alpn));
+    // config is never used but must be non-null
+    mSProxy = new NrSocketProxy(config);
+    int r = nr_socket_create_int((void*)mSProxy.get(),
+                                 mSProxy->vtbl(),
+                                 &nr_socket_);
+    ASSERT_EQ(0, r);
+
+    // fake calling AsyncOpen() due to IPC calls. must be non-null
+    mSProxy->AssignChannel_DoNotUse(new WebrtcProxyChannelWrapper(nullptr));
   }
-  ss << "\r\n" << tail;
-  return ss.str();
-}
 
-std::string connect_response(int code, const std::string &tail = "") {
-  std::stringstream ss;
-  ss << "HTTP/1.0 " << code << "\r\n\r\n" << tail;
-  return ss.str();
-}
-
-class DummyResolver {
- public:
-  DummyResolver() {
-    vtbl_ = new nr_resolver_vtbl;
-    vtbl_->destroy = &DummyResolver::destroy;
-    vtbl_->resolve = &DummyResolver::resolve;
-    vtbl_->cancel = &DummyResolver::cancel;
-    nr_resolver_create_int((void *)this, vtbl_, &resolver_);
+  void TearDown() override {
+    mSProxy->close();
   }
 
-  ~DummyResolver() {
-    nr_resolver_destroy(&resolver_);
-    delete vtbl_;
-  }
+  static void readable_cb(NR_SOCKET s, int how, void *cb_arg) {
+    NrSocketProxyTest* test = (NrSocketProxyTest*) cb_arg;
+    size_t capacity = std::min(test->mReadChunkSize, test->mReadAllowance);
+    nsTArray<uint8_t> array(capacity);
+    size_t read;
+
+    nr_socket_read(test->nr_socket_,
+                   (char*)array.Elements(),
+                   array.Capacity(),
+                   &read,
+                   0);
 
-  static int destroy(void **objp) {
-    return 0;
+    ASSERT_TRUE(read <= array.Capacity());
+    ASSERT_TRUE(test->mReadAllowance >= read);
+
+    array.SetLength(read);
+    test->mData.AppendElements(array);
+    test->mReadAllowance -= read;
+
+    // We may read more bytes each time we're called. This way we can ensure we
+    // consume buffers partially and across multiple buffers.
+    test->mReadChunkSize += test->mReadChunkSizeIncrement;
+
+    if (test->mReadAllowance > 0) {
+      NR_ASYNC_WAIT(s, how, &NrSocketProxyTest::readable_cb, cb_arg);
+    }
   }
 
-  static int resolve(void *obj,
-                     nr_resolver_resource *resource,
-                     int (*cb)(void *cb_arg, nr_transport_addr *addr),
-                     void *cb_arg,
-                     void **handle) {
-    nr_transport_addr addr;
+  static void writable_cb(NR_SOCKET s, int how, void *cb_arg) {
+    NrSocketProxyTest* test = (NrSocketProxyTest*) cb_arg;
+    test->mConnected = true;
+  }
 
-    nr_str_port_to_transport_addr(
-        (char *)kProxyAddr.c_str(), kProxyPort, IPPROTO_TCP, &addr);
-
-    cb(cb_arg, &addr);
-    return 0;
+  const std::string DataString() {
+    return std::string((char*)mData.Elements(), mData.Length());
   }
 
-  static int cancel(void *obj, void *handle) {
-    return 0;
-  }
+ protected:
+  RefPtr<NrSocketProxy> mSProxy;
+  nr_socket *nr_socket_;
 
-  nr_resolver *get_nr_resolver() {
-    return resolver_;
-  }
+  nsTArray<uint8_t> mData;
+  nsTArray<uint8_t> mEmptyArray;
 
- private:
-  nr_resolver_vtbl *vtbl_;
-  nr_resolver *resolver_;
+  uint32_t mReadChunkSize;
+  uint32_t mReadChunkSizeIncrement;
+  uint32_t mReadAllowance;
+
+  bool mConnected;
 };
 
-class ProxyTunnelSocketTest : public MtransportTest {
- public:
-  ProxyTunnelSocketTest()
-      : socket_impl_(nullptr),
-        nr_socket_(nullptr) {}
-
-  ~ProxyTunnelSocketTest() {
-    nr_socket_destroy(&nr_socket_);
-    nr_proxy_tunnel_config_destroy(&config_);
-  }
-
-  void SetUp() override {
-    MtransportTest::SetUp();
+TEST_F(NrSocketProxyTest, TestCreate) {
+}
 
-    nr_resolver_ = resolver_impl_.get_nr_resolver();
-
-    int r = nr_str_port_to_transport_addr(
-        (char *)kRemoteAddr.c_str(), kRemotePort, IPPROTO_TCP, &remote_addr_);
-    ASSERT_EQ(0, r);
-
-    r = nr_str_port_to_transport_addr(
-        (char *)kProxyAddr.c_str(), kProxyPort, IPPROTO_TCP, &proxy_addr_);
-    ASSERT_EQ(0, r);
-
-    nr_proxy_tunnel_config_create(&config_);
-    nr_proxy_tunnel_config_set_resolver(config_, nr_resolver_);
-    nr_proxy_tunnel_config_set_proxy(config_, kProxyAddr.c_str(), kProxyPort);
-
-    Configure();
-  }
+TEST_F(NrSocketProxyTest, TestConnected) {
+  ASSERT_TRUE(!mConnected);
 
-  // This reconfigures the socket with the updated information in config_.
-  void Configure() {
-    if (nr_socket_) {
-      EXPECT_EQ(0, nr_socket_destroy(&nr_socket_));
-      EXPECT_EQ(nullptr, nr_socket_);
-    }
-
-    RefPtr<DummySocket> dummy(new DummySocket());
-    int r = nr_socket_proxy_tunnel_create(
-        config_,
-        dummy->get_nr_socket(),
-        &nr_socket_);
-    ASSERT_EQ(0, r);
-
-    socket_impl_ = dummy.forget();  // Now owned by nr_socket_.
-  }
-
-  void Connect(int expectedReturn = 0) {
-    int r = nr_socket_connect(nr_socket_, &remote_addr_);
-    EXPECT_EQ(expectedReturn, r);
+  NR_ASYNC_WAIT(mSProxy,
+                NR_ASYNC_WAIT_WRITE,
+                &NrSocketProxyTest::writable_cb,
+                this);
 
-    size_t written = 0;
-    r = nr_socket_write(nr_socket_, kHelloMessage.c_str(), kHelloMessage.size(), &written, 0);
-    EXPECT_EQ(0, r);
-    EXPECT_EQ(kHelloMessage.size(), written);
-  }
-
-  nr_socket *socket() { return nr_socket_; }
+  // still not connected just registered for writes...
+  ASSERT_TRUE(!mConnected);
 
- protected:
-  RefPtr<DummySocket> socket_impl_;
-  DummyResolver resolver_impl_;
-  nr_socket *nr_socket_;
-  nr_resolver *nr_resolver_;
-  nr_proxy_tunnel_config *config_;
-  nr_transport_addr proxy_addr_;
-  nr_transport_addr remote_addr_;
-};
+  mSProxy->OnConnected();
 
-
-TEST_F(ProxyTunnelSocketTest, TestCreate) {
+  ASSERT_TRUE(mConnected);
 }
 
-TEST_F(ProxyTunnelSocketTest, TestConnectProxyAddress) {
-  int r = nr_socket_connect(nr_socket_, &remote_addr_);
-  ASSERT_EQ(0, r);
+TEST_F(NrSocketProxyTest, TestRead) {
+  nsTArray<uint8_t> array;
+  array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
 
-  ASSERT_EQ(0, nr_transport_addr_cmp(socket_impl_->get_connect_addr(), &proxy_addr_,
-        NR_TRANSPORT_ADDR_CMP_MODE_ALL));
+  NR_ASYNC_WAIT(mSProxy,
+                NR_ASYNC_WAIT_READ,
+                &NrSocketProxyTest::readable_cb,
+                this);
+  // this will read 0 bytes here
+  mSProxy->OnRead(std::move(array));
+
+  ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
+
+  // callback is still set but terminated due to 0 byte read
+  // start callbacks again (first read is 0 then 1,2,3,...)
+  mSProxy->OnRead(std::move(mEmptyArray));
+
+  ASSERT_EQ(kHelloMessage.length(), mData.Length());
+  ASSERT_EQ(kHelloMessage, DataString());
 }
 
-// TODO: Reenable once bug 1251212 is fixed
-TEST_F(ProxyTunnelSocketTest, DISABLED_TestConnectProxyRequest) {
-  Connect();
+TEST_F(NrSocketProxyTest, TestReadConstantConsumeSize) {
+  std::string data;
+
+  // triangle number
+  const int kCount = 32;
 
-  std::string msg = connect_message(kRemoteAddr, kRemotePort, "", kHelloMessage);
-  socket_impl_->CheckWriteBuffer(reinterpret_cast<const uint8_t *>(msg.c_str()), msg.size());
-}
+  //  ~17kb
+  // triangle number formula n*(n+1)/2
+  for(int i = 0; i < kCount * (kCount + 1) / 2; ++i) {
+    data += kHelloMessage;
+  }
 
-// TODO: Reenable once bug 1251212 is fixed
-TEST_F(ProxyTunnelSocketTest, DISABLED_TestAlpnConnect) {
-  const std::string alpn = "this,is,alpn";
-  int r = nr_proxy_tunnel_config_set_alpn(config_, alpn.c_str());
-  EXPECT_EQ(0, r);
+  // decreasing buffer sizes
+  for(int i = 0, start = 0; i < kCount; ++i) {
+    int length = (kCount - i) * kHelloMessage.length();
+
+    nsTArray<uint8_t> array;
+    array.AppendElements(data.c_str() + start, length);
+    start += length;
 
-  Configure();
-  Connect();
+    mSProxy->OnRead(std::move(array));
+  }
 
-  std::string msg = connect_message(kRemoteAddr, kRemotePort, alpn, kHelloMessage);
-  socket_impl_->CheckWriteBuffer(reinterpret_cast<const uint8_t *>(msg.c_str()), msg.size());
-}
+  ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
 
-// TODO: Reenable once bug 1251212 is fixed
-TEST_F(ProxyTunnelSocketTest, DISABLED_TestNullAlpnConnect) {
-  int r = nr_proxy_tunnel_config_set_alpn(config_, nullptr);
-  EXPECT_EQ(0, r);
+  // read same amount each callback
+  mReadChunkSize = 128;
+  mReadChunkSizeIncrement = 0;
+  NR_ASYNC_WAIT(mSProxy,
+                NR_ASYNC_WAIT_READ,
+                &NrSocketProxyTest::readable_cb,
+                this);
 
-  Configure();
-  Connect();
+  ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
 
-  std::string msg = connect_message(kRemoteAddr, kRemotePort, "", kHelloMessage);
-  socket_impl_->CheckWriteBuffer(reinterpret_cast<const uint8_t *>(msg.c_str()), msg.size());
+  // start callbacks
+  mSProxy->OnRead(std::move(mEmptyArray));
+
+  ASSERT_EQ(data.length(), mData.Length());
+  ASSERT_EQ(data, DataString());
 }
 
-// TODO: Reenable once bug 1251212 is fixed
-TEST_F(ProxyTunnelSocketTest, DISABLED_TestConnectProxyHostRequest) {
-  nr_proxy_tunnel_config_set_proxy(config_, kProxyHost.c_str(), kProxyPort);
-  Configure();
-  // Because kProxyHost is a domain name and not an IP address,
-  // nr_socket_connect will need to resolve an IP address before continuing.  It
-  // does that, and assumes that resolving the IP will take some time, so it
-  // returns R_WOULDBLOCK.
-  //
-  // However, In this test setup, the resolution happens inline immediately, so
-  // nr_socket_connect is called recursively on the inner socket in
-  // nr_socket_proxy_tunnel_resolved_cb.  That also completes.  Thus, the socket
-  // is actually successfully connected after this call, even though
-  // nr_socket_connect reports an error.
-  //
-  // Arguably nr_socket_proxy_tunnel_connect() is busted, because it shouldn't
-  // report an error when it doesn't need any further assistance from the
-  // calling code, but that's pretty minor.
-  Connect(R_WOULDBLOCK);
+TEST_F(NrSocketProxyTest, TestReadNone) {
+  char buf[4096];
+  size_t read = 0;
+  int r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
+
+  ASSERT_EQ(R_WOULDBLOCK, r);
 
-  std::string msg = connect_message(kRemoteAddr, kRemotePort, "", kHelloMessage);
-  socket_impl_->CheckWriteBuffer(reinterpret_cast<const uint8_t *>(msg.c_str()), msg.size());
-}
+  nsTArray<uint8_t> array;
+  array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+  mSProxy->OnRead(std::move(array));
 
-// TODO: Reenable once bug 1251212 is fixed
-TEST_F(ProxyTunnelSocketTest, DISABLED_TestConnectProxyWrite) {
-  Connect();
+  ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
 
-  socket_impl_->ClearWriteBuffer();
+  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
 
-  size_t written = 0;
-  int r = nr_socket_write(nr_socket_, kHelloMessage.c_str(), kHelloMessage.size(), &written, 0);
-  EXPECT_EQ(0, r);
-  EXPECT_EQ(kHelloMessage.size(), written);
-
-  socket_impl_->CheckWriteBuffer(reinterpret_cast<const uint8_t *>(kHelloMessage.c_str()),
-      kHelloMessage.size());
+  ASSERT_EQ(0, r);
+  ASSERT_EQ(kHelloMessage.length(), read);
+  ASSERT_EQ(kHelloMessage, std::string(buf, read));
 }
 
-TEST_F(ProxyTunnelSocketTest, TestConnectProxySuccessResponse) {
-  int r = nr_socket_connect(nr_socket_, &remote_addr_);
-  ASSERT_EQ(0, r);
-
-  std::string resp = connect_response(200, kHelloMessage);
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(resp.c_str()), resp.size());
+TEST_F(NrSocketProxyTest, TestReadMultipleSizes) {
+  using namespace std;
 
-  char buf[4096];
-  size_t read = 0;
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_EQ(0, r);
+  string data;
+  // 515 * kHelloMessage.length() == 17510
+  const size_t kCount = 515;
+  // randomly generated numbers, sums to 17510, 20 numbers
+  vector<int> varyingSizes = { 404, 622, 1463, 1597, 1676, 389, 389, 1272, 781,
+    81, 1030, 1450, 256, 812, 1571, 29, 1045, 911, 643, 1089 };
 
-  ASSERT_EQ(kHelloMessage.size(), read);
-  ASSERT_EQ(0, memcmp(buf, kHelloMessage.c_str(), kHelloMessage.size()));
-}
+  // changing varyingSizes or the test message breaks this so check here
+  ASSERT_EQ(kCount, 17510 / kHelloMessage.length());
+  ASSERT_EQ(17510, accumulate(varyingSizes.begin(), varyingSizes.end(), 0));
+
+  // ~17kb
+  for(size_t i = 0; i < kCount; ++i) {
+    data += kHelloMessage;
+  }
 
-TEST_F(ProxyTunnelSocketTest, TestConnectProxyRead) {
-  int r = nr_socket_connect(nr_socket_, &remote_addr_);
-  ASSERT_EQ(0, r);
+  nsTArray<uint8_t> array;
+  array.AppendElements(data.c_str(), data.length());
 
-  std::string resp = connect_response(200, kHelloMessage);
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(resp.c_str()), resp.size());
+  for(int amountToRead : varyingSizes) {
+    nsTArray<uint8_t> buffer;
+    buffer.AppendElements(array.Elements(), amountToRead);
+    array.RemoveElementsAt(0, amountToRead);
+    mSProxy->OnRead(std::move(buffer));
+  }
+
+  ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
 
-  char buf[4096];
-  size_t read = 0;
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_EQ(0, r);
+  // don't need to read 0 on the first read, so start at 1 and keep going
+  mReadChunkSize = 1;
+  NR_ASYNC_WAIT(mSProxy,
+                NR_ASYNC_WAIT_READ,
+                &NrSocketProxyTest::readable_cb,
+                this);
+  // start callbacks
+  mSProxy->OnRead(std::move(mEmptyArray));
 
-  socket_impl_->ClearReadBuffer();
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(kHelloMessage.c_str()),
-      kHelloMessage.size());
-
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_EQ(0, r);
-
-  ASSERT_EQ(kHelloMessage.size(), read);
-  ASSERT_EQ(0, memcmp(buf, kHelloMessage.c_str(), kHelloMessage.size()));
+  ASSERT_EQ(data.length(), mData.Length());
+  ASSERT_EQ(data, DataString());
 }
 
-TEST_F(ProxyTunnelSocketTest, TestConnectProxyReadNone) {
-  int r = nr_socket_connect(nr_socket_, &remote_addr_);
-  ASSERT_EQ(0, r);
+TEST_F(NrSocketProxyTest, TestReadConsumeReadDrain) {
+  std::string data;
+  // ~26kb total; should be even
+  const int kCount = 512;
 
-  std::string resp = connect_response(200);
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(resp.c_str()), resp.size());
+  // there's some division by 2 here so check that kCount is even
+  ASSERT_EQ(0, kCount % 2);
 
-  char buf[4096];
-  size_t read = 0;
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_EQ(R_WOULDBLOCK, r);
+  for(int i = 0; i < kCount; ++i) {
+    data += kHelloMessage;
+    nsTArray<uint8_t> array;
+    array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+    mSProxy->OnRead(std::move(array));
+  }
 
-  socket_impl_->ClearReadBuffer();
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(kHelloMessage.c_str()),
-      kHelloMessage.size());
-
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_EQ(0, r);
-}
+  // read half at first
+  mReadAllowance = kCount / 2 * kHelloMessage.length();
+  // start by reading 1 byte
+  mReadChunkSize = 1;
+  NR_ASYNC_WAIT(mSProxy,
+                NR_ASYNC_WAIT_READ,
+                &NrSocketProxyTest::readable_cb,
+                this);
+  mSProxy->OnRead(std::move(mEmptyArray));
 
-TEST_F(ProxyTunnelSocketTest, TestConnectProxyFailResponse) {
-  int r = nr_socket_connect(nr_socket_, &remote_addr_);
-  ASSERT_EQ(0, r);
+  ASSERT_EQ(data.length() / 2, mSProxy->CountUnreadBytes());
+  ASSERT_EQ(data.length() / 2, mData.Length());
 
-  std::string resp = connect_response(500, kHelloMessage);
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(resp.c_str()), resp.size());
-
-  char buf[4096];
-  size_t read = 0;
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_NE(0, r);
-}
+  // fill read buffer back up
+  for(int i = 0; i < kCount / 2; ++i) {
+    data += kHelloMessage;
+    nsTArray<uint8_t> array;
+    array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+    mSProxy->OnRead(std::move(array));
+  }
 
-TEST_F(ProxyTunnelSocketTest, TestConnectProxyGarbageResponse) {
-  int r = nr_socket_connect(nr_socket_, &remote_addr_);
-  ASSERT_EQ(0, r);
+  // remove read limit
+  mReadAllowance = -1;
+  // used entire read allowance so we need to setup a new await
+  NR_ASYNC_WAIT(mSProxy,
+                NR_ASYNC_WAIT_READ,
+                &NrSocketProxyTest::readable_cb,
+                this);
+  // start callbacks
+  mSProxy->OnRead(std::move(mEmptyArray));
 
-  socket_impl_->SetReadBuffer(reinterpret_cast<const uint8_t *>(kGarbageMessage.c_str()),
-      kGarbageMessage.size());
-
-  char buf[4096];
-  size_t read = 0;
-  r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
-  ASSERT_EQ(0ul, read);
+  ASSERT_EQ(data.length(), mData.Length());
+  ASSERT_EQ(data, DataString());
 }
new file mode 100644
--- /dev/null
+++ b/media/mtransport/test/webrtcproxychannel_unittest.cpp
@@ -0,0 +1,707 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include <mutex>
+
+#include "mozilla/net/WebrtcProxyChannel.h"
+#include "mozilla/net/WebrtcProxyChannelCallback.h"
+
+#include "nsISocketTransport.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest/Helpers.h"
+#include "gtest_utils.h"
+
+static const uint32_t kDefaultTestTimeout = 2000;
+static const char kReadData[] = "Hello, World!";
+static const size_t kReadDataLength = sizeof(kReadData) - 1;
+static const std::string kReadDataString = std::string(kReadData,
+                                                       kReadDataLength);
+static int kDataLargeOuterLoopCount = 128;
+static int kDataLargeInnerLoopCount = 1024;
+
+namespace mozilla {
+
+using namespace net;
+using namespace testing;
+
+class WebrtcProxyChannelTestCallback;
+
+class FakeSocketTransportProvider : public nsISocketTransport
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  // nsISocketTransport
+  NS_IMETHOD GetHost(nsACString & aHost) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetPort(int32_t *aPort) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetScriptableOriginAttributes(JSContext* cx, JS::MutableHandleValue aOriginAttributes) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetScriptableOriginAttributes(JSContext* cx, JS::HandleValue aOriginAttributes) override { MOZ_ASSERT(false); return NS_OK; }
+  virtual nsresult GetOriginAttributes(mozilla::OriginAttributes *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  virtual nsresult SetOriginAttributes(const mozilla::OriginAttributes & aOriginAttrs) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetPeerAddr(mozilla::net::NetAddr *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetSelfAddr(mozilla::net::NetAddr *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD Bind(mozilla::net::NetAddr *aLocalAddr) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetScriptablePeerAddr(nsINetAddr * *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetScriptableSelfAddr(nsINetAddr * *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetSecurityInfo(nsISupports * *aSecurityInfo) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetSecurityCallbacks(nsIInterfaceRequestor * *aSecurityCallbacks) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetSecurityCallbacks(nsIInterfaceRequestor *aSecurityCallbacks) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD IsAlive(bool *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetTimeout(uint32_t aType, uint32_t *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetTimeout(uint32_t aType, uint32_t aValue) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetReuseAddrPort(bool reuseAddrPort) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetConnectionFlags(uint32_t *aConnectionFlags) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetConnectionFlags(uint32_t aConnectionFlags) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetTlsFlags(uint32_t *aTlsFlags) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetQoSBits(uint8_t *aQoSBits) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetQoSBits(uint8_t aQoSBits) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetRecvBufferSize(uint32_t *aRecvBufferSize) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetSendBufferSize(uint32_t *aSendBufferSize) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetKeepaliveEnabled(bool *aKeepaliveEnabled) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetKeepaliveEnabled(bool aKeepaliveEnabled) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetKeepaliveVals(int32_t keepaliveIdleTime, int32_t keepaliveRetryInterval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetFastOpenCallback(mozilla::net::TCPFastOpen *aFastOpen) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetFirstRetryError(nsresult *aFirstRetryError) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetResetIPFamilyPreference(bool *aResetIPFamilyPreference) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD GetEsniUsed(bool *aEsniUsed) override { MOZ_ASSERT(false); return NS_OK; }
+
+  // nsITransport
+  NS_IMETHOD OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize, uint32_t aSegmentCount, nsIInputStream * *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize, uint32_t aSegmentCount, nsIOutputStream * *_retval) override { MOZ_ASSERT(false); return NS_OK; }
+  NS_IMETHOD SetEventSink(nsITransportEventSink *aSink, nsIEventTarget *aEventTarget) override { MOZ_ASSERT(false); return NS_OK; }
+
+  // fake except for these methods which are OK to call
+  // nsISocketTransport
+  NS_IMETHOD SetRecvBufferSize(uint32_t aRecvBufferSize) override
+    { return NS_OK; }
+  NS_IMETHOD SetSendBufferSize(uint32_t aSendBufferSize) override
+    { return NS_OK; }
+  // nsITransport
+  NS_IMETHOD Close(nsresult aReason) override { return NS_OK; }
+
+protected:
+  virtual ~FakeSocketTransportProvider() = default;
+};
+
+NS_IMPL_ISUPPORTS(FakeSocketTransportProvider,
+                  nsISocketTransport,
+                  nsITransport)
+
+// Implements some common elements to WebrtcProxyChannelTestOutputStream and
+// WebrtcProxyChannelTestInputStream.
+class WebrtcProxyChannelTestStream
+{
+public:
+  WebrtcProxyChannelTestStream();
+
+  void Fail() { mMustFail = true; }
+
+  size_t DataLength();
+  template <typename T> void AppendElements(const T* aBuffer, size_t aLength);
+
+protected:
+  virtual ~WebrtcProxyChannelTestStream() = default;
+
+  nsTArray<uint8_t> mData;
+  std::mutex mDataMutex;
+
+  bool mMustFail;
+};
+
+WebrtcProxyChannelTestStream::WebrtcProxyChannelTestStream()
+  : mMustFail(false) {}
+
+template <typename T>
+void
+WebrtcProxyChannelTestStream::AppendElements(const T* aBuffer, size_t aLength)
+{
+  std::lock_guard<std::mutex> guard(mDataMutex);
+  mData.AppendElements(aBuffer, aLength);
+}
+
+size_t
+WebrtcProxyChannelTestStream::DataLength()
+{
+  std::lock_guard<std::mutex> guard(mDataMutex);
+  return mData.Length();
+}
+
+class WebrtcProxyChannelTestInputStream : public nsIAsyncInputStream,
+                                          public WebrtcProxyChannelTestStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIASYNCINPUTSTREAM
+  NS_DECL_NSIINPUTSTREAM
+
+  WebrtcProxyChannelTestInputStream()
+    : mMaxReadSize(1024 * 1024)
+    , mAllowCallbacks(false) {}
+
+  void DoCallback();
+  void CallCallback(const nsCOMPtr<nsIInputStreamCallback>& aCallback);
+  void AllowCallbacks() { mAllowCallbacks = true; }
+
+  size_t mMaxReadSize;
+
+protected:
+  virtual ~WebrtcProxyChannelTestInputStream() = default;
+
+private:
+  nsCOMPtr<nsIInputStreamCallback> mCallback;
+  nsCOMPtr<nsIEventTarget> mCallbackTarget;
+
+  bool mAllowCallbacks;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcProxyChannelTestInputStream,
+                  nsIAsyncInputStream,
+                  nsIInputStream)
+
+nsresult
+WebrtcProxyChannelTestInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
+                                             uint32_t aFlags,
+                                             uint32_t aRequestedCount,
+                                             nsIEventTarget *aEventTarget)
+{
+  MOZ_ASSERT(!aEventTarget, "no event target should be set");
+
+  mCallback = aCallback;
+  mCallbackTarget = NS_GetCurrentThread();
+
+  if (mAllowCallbacks && DataLength() > 0) {
+    DoCallback();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestInputStream::CloseWithStatus(nsresult aStatus)
+{
+  return Close();
+}
+
+nsresult
+WebrtcProxyChannelTestInputStream::Close()
+{
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestInputStream::Available(uint64_t* aAvailable)
+{
+  *aAvailable = DataLength();
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestInputStream::Read(char* aBuffer,
+                                        uint32_t aCount,
+                                        uint32_t* aRead)
+{
+  std::lock_guard<std::mutex> guard(mDataMutex);
+  if (mMustFail) {
+    return NS_ERROR_FAILURE;
+  }
+  *aRead = std::min({ (size_t)aCount, mData.Length(), mMaxReadSize });
+  memcpy(aBuffer, mData.Elements(), *aRead);
+  mData.RemoveElementsAt(0, *aRead);
+  return *aRead > 0 ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+nsresult
+WebrtcProxyChannelTestInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+                                                void *aClosure,
+                                                uint32_t aCount,
+                                                uint32_t *_retval)
+{
+  MOZ_ASSERT(false);
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestInputStream::IsNonBlocking(bool* aIsNonBlocking)
+{
+  *aIsNonBlocking = true;
+  return NS_OK;
+}
+
+void
+WebrtcProxyChannelTestInputStream::CallCallback(
+  const nsCOMPtr<nsIInputStreamCallback>& aCallback)
+{
+  aCallback->OnInputStreamReady(this);
+}
+
+void
+WebrtcProxyChannelTestInputStream::DoCallback()
+{
+  if (mCallback) {
+    mCallbackTarget->Dispatch(
+      NewRunnableMethod<const nsCOMPtr<nsIInputStreamCallback>&>(
+        "WebrtcProxyChannelTestInputStream::DoCallback",
+        this,
+        &WebrtcProxyChannelTestInputStream::CallCallback,
+        std::move(mCallback)));
+
+    mCallbackTarget = nullptr;
+  }
+}
+
+class WebrtcProxyChannelTestOutputStream : public nsIAsyncOutputStream,
+                                           public WebrtcProxyChannelTestStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIASYNCOUTPUTSTREAM
+  NS_DECL_NSIOUTPUTSTREAM
+
+  WebrtcProxyChannelTestOutputStream()
+    : mMaxWriteSize(1024 * 1024) {}
+
+  void DoCallback();
+  void CallCallback(const nsCOMPtr<nsIOutputStreamCallback>& aCallback);
+
+  const std::string DataString();
+
+  uint32_t mMaxWriteSize;
+
+protected:
+  virtual ~WebrtcProxyChannelTestOutputStream() = default;
+
+private:
+  nsCOMPtr<nsIOutputStreamCallback> mCallback;
+  nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcProxyChannelTestOutputStream,
+                  nsIAsyncOutputStream,
+                  nsIOutputStream)
+
+nsresult
+WebrtcProxyChannelTestOutputStream::AsyncWait(
+  nsIOutputStreamCallback *aCallback,
+  uint32_t aFlags,
+  uint32_t aRequestedCount,
+  nsIEventTarget *aEventTarget)
+{
+  MOZ_ASSERT(!aEventTarget, "no event target should be set");
+
+  mCallback = aCallback;
+  mCallbackTarget = NS_GetCurrentThread();
+
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::CloseWithStatus(nsresult aStatus)
+{
+  return Close();
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::Close()
+{
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::Flush()
+{
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::Write(const char* aBuffer,
+                                          uint32_t aCount,
+                                          uint32_t* aWrote)
+{
+  if (mMustFail) {
+    return NS_ERROR_FAILURE;
+  }
+  *aWrote = std::min(aCount, mMaxWriteSize);
+  AppendElements(aBuffer, *aWrote);
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::WriteSegments(nsReadSegmentFun aReader,
+                                                  void *aClosure,
+                                                  uint32_t aCount,
+                                                  uint32_t *_retval)
+{
+  MOZ_ASSERT(false);
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::WriteFrom(
+  nsIInputStream *aFromStream,
+  uint32_t aCount,
+  uint32_t *_retval)
+{
+  MOZ_ASSERT(false);
+  return NS_OK;
+}
+
+nsresult
+WebrtcProxyChannelTestOutputStream::IsNonBlocking(bool* aIsNonBlocking)
+{
+  *aIsNonBlocking = true;
+  return NS_OK;
+}
+
+void
+WebrtcProxyChannelTestOutputStream::CallCallback(
+  const nsCOMPtr<nsIOutputStreamCallback>& aCallback)
+{
+  aCallback->OnOutputStreamReady(this);
+}
+
+void
+WebrtcProxyChannelTestOutputStream::DoCallback()
+{
+  if (mCallback) {
+    mCallbackTarget->Dispatch(
+      NewRunnableMethod<const nsCOMPtr<nsIOutputStreamCallback>&>(
+        "WebrtcProxyChannelTestOutputStream::CallCallback",
+        this,
+        &WebrtcProxyChannelTestOutputStream::CallCallback,
+        std::move(mCallback)));
+
+    mCallbackTarget = nullptr;
+  }
+}
+
+const std::string
+WebrtcProxyChannelTestOutputStream::DataString()
+{
+  std::lock_guard<std::mutex> guard(mDataMutex);
+  return std::string((char*)mData.Elements(), mData.Length());
+}
+
+// Fake as in not the real WebrtcProxyChannel but real enough
+class FakeWebrtcProxyChannel : public WebrtcProxyChannel
+{
+public:
+  explicit FakeWebrtcProxyChannel(WebrtcProxyChannelCallback* aCallback)
+    : WebrtcProxyChannel(nullptr, aCallback) {}
+
+protected:
+  virtual ~FakeWebrtcProxyChannel() = default;
+
+  void InvokeOnClose(nsresult aReason) override;
+  void InvokeOnConnected() override;
+  void InvokeOnRead(nsTArray<uint8_t>&& aReadData) override;
+};
+
+void
+FakeWebrtcProxyChannel::InvokeOnClose(nsresult aReason)
+{
+  mProxyCallbacks->OnClose(aReason);
+}
+
+void
+FakeWebrtcProxyChannel::InvokeOnConnected()
+{
+  mProxyCallbacks->OnConnected();
+}
+
+void
+FakeWebrtcProxyChannel::InvokeOnRead(nsTArray<uint8_t>&& aReadData)
+{
+  mProxyCallbacks->OnRead(std::move(aReadData));
+}
+
+class WebrtcProxyChannelTest : public MtransportTest
+{
+public:
+  WebrtcProxyChannelTest()
+    : MtransportTest()
+    , mSocketThread(nullptr)
+    , mSocketTransport(nullptr)
+    , mInputStream(nullptr)
+    , mOutputStream(nullptr)
+    , mChannel(nullptr)
+    , mCallback(nullptr)
+    , mOnCloseCalled(false)
+    , mOnConnectedCalled(false) {}
+
+  // WebrtcProxyChannelCallback forwards from mCallback
+  void OnClose(nsresult aReason);
+  void OnConnected();
+  void OnRead(nsTArray<uint8_t>&& aReadData);
+
+  void SetUp() override;
+  void TearDown() override;
+
+  void DoTransportAvailable();
+
+  const std::string ReadDataAsString();
+  const std::string GetDataLarge();
+
+  nsCOMPtr<nsIEventTarget> mSocketThread;
+
+  nsCOMPtr<nsISocketTransport>               mSocketTransport;
+  RefPtr<WebrtcProxyChannelTestInputStream>  mInputStream;
+  RefPtr<WebrtcProxyChannelTestOutputStream> mOutputStream;
+  RefPtr<FakeWebrtcProxyChannel>             mChannel;
+  RefPtr<WebrtcProxyChannelTestCallback>     mCallback;
+
+  bool mOnCloseCalled;
+  bool mOnConnectedCalled;
+
+  size_t ReadDataLength();
+  template<typename T> void AppendReadData(const T* aBuffer, size_t aLength);
+
+private:
+  nsTArray<uint8_t> mReadData;
+  std::mutex mReadDataMutex;
+};
+
+class WebrtcProxyChannelTestCallback : public WebrtcProxyChannelCallback
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcProxyChannelTestCallback,
+                                        override)
+
+  explicit WebrtcProxyChannelTestCallback(WebrtcProxyChannelTest* aTest)
+    : mTest(aTest) {}
+
+  // WebrtcProxyChannelCallback
+  void OnClose(nsresult aReason) override;
+  void OnConnected() override;
+  void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+protected:
+  virtual ~WebrtcProxyChannelTestCallback() = default;
+
+private:
+  WebrtcProxyChannelTest* mTest;
+};
+
+void
+WebrtcProxyChannelTest::SetUp()
+{
+  nsresult rv;
+  // WebrtcProxyChannel's threading model is the same as mtransport
+  // all socket operations are done on the socket thread
+  // callbacks are invoked on the main thread
+  mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  mSocketTransport = new FakeSocketTransportProvider();
+  mInputStream = new WebrtcProxyChannelTestInputStream();
+  mOutputStream = new WebrtcProxyChannelTestOutputStream();
+  mCallback = new WebrtcProxyChannelTestCallback(this);
+  mChannel = new FakeWebrtcProxyChannel(mCallback.get());
+}
+
+void
+WebrtcProxyChannelTest::TearDown()
+{
+}
+
+// WebrtcProxyChannelCallback
+void
+WebrtcProxyChannelTest::OnRead(nsTArray<uint8_t>&& aReadData)
+{
+  AppendReadData(aReadData.Elements(), aReadData.Length());
+}
+
+void
+WebrtcProxyChannelTest::OnConnected()
+{
+  mOnConnectedCalled = true;
+}
+
+void
+WebrtcProxyChannelTest::OnClose(nsresult aReason)
+{
+  mOnCloseCalled = true;
+}
+
+void
+WebrtcProxyChannelTest::DoTransportAvailable()
+{
+  if (!mSocketThread->IsOnCurrentThread()) {
+    mSocketThread->Dispatch(
+      NS_NewRunnableFunction(
+        "DoTransportAvailable",
+        [this]() -> void {
+          nsresult rv;
+          rv = mChannel->OnTransportAvailable(mSocketTransport,
+                                              mInputStream,
+                                              mOutputStream);
+          ASSERT_EQ(NS_OK, rv);
+        }));
+  } else {
+    // should always be called on the main thread
+    MOZ_ASSERT(0);
+  }
+}
+
+const std::string
+WebrtcProxyChannelTest::ReadDataAsString()
+{
+  std::lock_guard<std::mutex> guard(mReadDataMutex);
+  return std::string((char*)mReadData.Elements(), mReadData.Length());
+}
+
+const std::string
+WebrtcProxyChannelTest::GetDataLarge()
+{
+  std::string data;
+  for(int i = 0; i < kDataLargeOuterLoopCount * kDataLargeInnerLoopCount; ++i) {
+    data += kReadData;
+  }
+  return data;
+}
+
+template<typename T>
+void
+WebrtcProxyChannelTest::AppendReadData(const T* aBuffer, size_t aLength)
+{
+  std::lock_guard<std::mutex> guard(mReadDataMutex);
+  mReadData.AppendElements(aBuffer, aLength);
+}
+
+size_t
+WebrtcProxyChannelTest::ReadDataLength()
+{
+  std::lock_guard<std::mutex> guard(mReadDataMutex);
+  return mReadData.Length();
+}
+
+void
+WebrtcProxyChannelTestCallback::OnClose(nsresult aReason)
+{
+  mTest->OnClose(aReason);
+}
+
+void
+WebrtcProxyChannelTestCallback::OnConnected()
+{
+  mTest->OnConnected();
+}
+
+void
+WebrtcProxyChannelTestCallback::OnRead(nsTArray<uint8_t>&& aReadData)
+{
+  mTest->OnRead(std::move(aReadData));
+}
+
+} // namespace mozilla
+
+typedef mozilla::WebrtcProxyChannelTest WebrtcProxyChannelTest;
+
+TEST_F(WebrtcProxyChannelTest, SetUp) {}
+
+TEST_F(WebrtcProxyChannelTest, TransportAvailable) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcProxyChannelTest, Read) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+  mInputStream->AppendElements(kReadData, kReadDataLength);
+  mInputStream->DoCallback();
+
+  ASSERT_TRUE_WAIT(ReadDataAsString() == kReadDataString, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcProxyChannelTest, Write) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+  nsTArray<uint8_t> data;
+  data.AppendElements(kReadData, kReadDataLength);
+  mChannel->Write(std::move(data));
+
+  ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == kReadDataLength,
+                   kDefaultTestTimeout);
+
+  mOutputStream->DoCallback();
+
+  ASSERT_TRUE_WAIT(mOutputStream->DataString() == kReadDataString,
+                   kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcProxyChannelTest, ReadFail) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+  mInputStream->AppendElements(kReadData, kReadDataLength);
+  mInputStream->Fail();
+  mInputStream->DoCallback();
+
+  ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
+  ASSERT_EQ(0U, ReadDataLength());
+}
+
+TEST_F(WebrtcProxyChannelTest, WriteFail) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+  nsTArray<uint8_t> array;
+  array.AppendElements(kReadData, kReadDataLength);
+  mChannel->Write(std::move(array));
+
+  ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == kReadDataLength,
+                   kDefaultTestTimeout);
+
+  mOutputStream->Fail();
+  mOutputStream->DoCallback();
+
+  ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
+  ASSERT_EQ(0U, mOutputStream->DataLength());
+}
+
+TEST_F(WebrtcProxyChannelTest, ReadLarge) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+  const std::string data = GetDataLarge();
+
+  mInputStream->AppendElements(data.c_str(), data.length());
+  // make sure reading loops more than once
+  mInputStream->mMaxReadSize = 3072;
+  mInputStream->AllowCallbacks();
+  mInputStream->DoCallback();
+
+  ASSERT_TRUE_WAIT(ReadDataAsString() == data, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcProxyChannelTest, WriteLarge) {
+  DoTransportAvailable();
+  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+  const std::string data = GetDataLarge();
+
+  for(int i = 0; i < kDataLargeOuterLoopCount; ++i) {
+    nsTArray<uint8_t> array;
+    int chunkSize = kReadDataString.length() * kDataLargeInnerLoopCount;
+    int offset = i * chunkSize;
+    array.AppendElements(data.c_str() + offset, chunkSize);
+    mChannel->Write(std::move(array));
+  }
+
+  ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == data.length(),
+                   kDefaultTestTimeout);
+
+  // make sure writing loops more than once per write request
+  mOutputStream->mMaxWriteSize = 1024;
+  mOutputStream->DoCallback();
+
+  ASSERT_TRUE_WAIT(mOutputStream->DataString() == data, kDefaultTestTimeout);
+}
--- a/media/mtransport/test_nr_socket.cpp
+++ b/media/mtransport/test_nr_socket.cpp
@@ -232,33 +232,33 @@ RefPtr<NrSocketBase> TestNrSocket::creat
 
   if ((r = nr_transport_addr_set_port(&nat_external_addr, 0))) {
     r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_set_port: %d",
                                 __FUNCTION__, r);
     return nullptr;
   }
 
   RefPtr<NrSocketBase> external_socket;
-  r = NrSocketBase::CreateSocket(&nat_external_addr, &external_socket);
+  r = NrSocketBase::CreateSocket(&nat_external_addr, &external_socket, nullptr);
 
   if (r) {
     r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in NrSocket::create: %d",
                                 __FUNCTION__, r);
     return nullptr;
   }
 
   return external_socket;
 }
 
 int TestNrSocket::create(nr_transport_addr *addr) {
   if (addr->tls_host[0] != '\0') {
     tls_ = true;
   }
 
-  return NrSocketBase::CreateSocket(addr, &internal_socket_);
+  return NrSocketBase::CreateSocket(addr, &internal_socket_, nullptr);
 }
 
 int TestNrSocket::getaddr(nr_transport_addr *addrp) {
   return internal_socket_->getaddr(addrp);
 }
 
 void TestNrSocket::close() {
   if (timer_handle_) {
--- a/media/mtransport/third_party/nICEr/nicer.gyp
+++ b/media/mtransport/third_party/nICEr/nicer.gyp
@@ -62,18 +62,16 @@
                 "./src/ice/ice_socket.c",
                 "./src/ice/ice_socket.h",
 
                 # Net
                 "./src/net/nr_resolver.c",
                 "./src/net/nr_resolver.h",
                 "./src/net/nr_socket_wrapper.c",
                 "./src/net/nr_socket_wrapper.h",
-                "./src/net/nr_proxy_tunnel.c",
-                "./src/net/nr_proxy_tunnel.h",
                 "./src/net/nr_socket.c",
                 "./src/net/nr_socket.h",
                 #"./src/net/nr_socket_local.c",
                 "./src/net/nr_socket_local.h",
                 "./src/net/nr_socket_multi_tcp.c",
                 "./src/net/nr_socket_multi_tcp.h",
                 "./src/net/transport_addr.c",
                 "./src/net/transport_addr.h",
deleted file mode 100644
--- a/media/mtransport/third_party/nICEr/src/net/nr_proxy_tunnel.c
+++ /dev/null
@@ -1,686 +0,0 @@
-/*
-Copyright (c) 2007, Adobe Systems, Incorporated
-Copyright (c) 2013, Mozilla
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-* Neither the name of Adobe Systems, Network Resonance, Mozilla nor
-  the names of its contributors may be used to endorse or promote
-  products derived from this software without specific prior written
-  permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include <nr_api.h>
-
-#include <assert.h>
-
-#include "nr_proxy_tunnel.h"
-
-#define MAX_HTTP_CONNECT_ADDR_SIZE 256
-#define MAX_HTTP_CONNECT_BUFFER_SIZE 1024
-#define MAX_ALPN_LENGTH 64
-#ifndef CRLF
-#define CRLF "\r\n"
-#endif
-#define END_HEADERS CRLF CRLF
-
-typedef enum {
-  PROXY_TUNNEL_NONE=0,
-  PROXY_TUNNEL_REQUESTED,
-  PROXY_TUNNEL_CONNECTED,
-  PROXY_TUNNEL_CLOSED,
-  PROXY_TUNNEL_FAILED
-} nr_socket_proxy_tunnel_state;
-
-typedef struct nr_socket_proxy_tunnel_ {
-  nr_proxy_tunnel_config *config;
-  nr_socket *inner;
-  nr_transport_addr remote_addr;
-  nr_socket_proxy_tunnel_state state;
-  char buffer[MAX_HTTP_CONNECT_BUFFER_SIZE];
-  size_t buffered_bytes;
-  void *resolver_handle;
-} nr_socket_proxy_tunnel;
-
-typedef struct nr_socket_wrapper_factory_proxy_tunnel_ {
-  nr_proxy_tunnel_config *config;
-} nr_socket_wrapper_factory_proxy_tunnel;
-
-static int nr_socket_proxy_tunnel_destroy(void **objpp);
-static int nr_socket_proxy_tunnel_getfd(void *obj, NR_SOCKET *fd);
-static int nr_socket_proxy_tunnel_getaddr(void *obj, nr_transport_addr *addrp);
-static int nr_socket_proxy_tunnel_connect(void *sock, nr_transport_addr *addr);
-static int nr_socket_proxy_tunnel_write(void *obj, const void *msg, size_t len, size_t *written);
-static int nr_socket_proxy_tunnel_read(void *obj, void * restrict buf, size_t maxlen, size_t *len);
-static int nr_socket_proxy_tunnel_close(void *obj);
-
-int nr_proxy_tunnel_config_copy(nr_proxy_tunnel_config *config, nr_proxy_tunnel_config **copypp);
-
-int nr_socket_wrapper_factory_proxy_tunnel_wrap(void *obj,
-                                                nr_socket *inner,
-                                                nr_socket **socketpp);
-
-int nr_socket_wrapper_factory_proxy_tunnel_destroy(void **objpp);
-
-static nr_socket_vtbl nr_socket_proxy_tunnel_vtbl={
-  1,
-  nr_socket_proxy_tunnel_destroy,
-  0,
-  0,
-  nr_socket_proxy_tunnel_getfd,
-  nr_socket_proxy_tunnel_getaddr,
-  nr_socket_proxy_tunnel_connect,
-  nr_socket_proxy_tunnel_write,
-  nr_socket_proxy_tunnel_read,
-  nr_socket_proxy_tunnel_close
-};
-
-static int send_http_connect(nr_socket_proxy_tunnel *sock)
-{
-  int r, _status;
-  int port;
-  int printed;
-  char addr[MAX_HTTP_CONNECT_ADDR_SIZE];
-  char mesg[MAX_HTTP_CONNECT_ADDR_SIZE + MAX_ALPN_LENGTH + 128];
-  size_t offset = 0;
-  size_t bytes_sent;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"send_http_connect");
-
-  if ((r=nr_transport_addr_get_port(&sock->remote_addr, &port))) {
-    ABORT(r);
-  }
-
-  if ((r=nr_transport_addr_get_addrstring(&sock->remote_addr, addr, sizeof(addr)))) {
-    ABORT(r);
-  }
-
-  printed = snprintf(mesg + offset, sizeof(mesg) - offset,
-                     "CONNECT %s:%d HTTP/1.0", addr, port);
-  offset += printed;
-  if (printed < 0 || (offset >= sizeof(mesg))) {
-    ABORT(R_FAILED);
-  }
-
-  if (sock->config->alpn) {
-    printed = snprintf(mesg + offset, sizeof(mesg) - offset,
-                       CRLF "ALPN: %s", sock->config->alpn);
-    offset += printed;
-    if (printed < 0 || (offset >= sizeof(mesg))) {
-      ABORT(R_FAILED);
-    }
-  }
-  if (offset + sizeof(END_HEADERS) >= sizeof(mesg)) {
-    ABORT(R_FAILED);
-  }
-  memcpy(mesg + offset, END_HEADERS, strlen(END_HEADERS));
-  offset += strlen(END_HEADERS);
-
-  if ((r=nr_socket_write(sock->inner, mesg, offset, &bytes_sent, 0))) {
-    ABORT(r);
-  }
-
-  if (bytes_sent < offset) {
-    /* TODO(bug 1116583): buffering and wait for */
-    r_log(LOG_GENERIC,LOG_DEBUG,"send_http_connect should be buffering %lu", (unsigned long)bytes_sent);
-    ABORT(R_IO_ERROR);
-  }
-
-  sock->state = PROXY_TUNNEL_REQUESTED;
-
-  _status = 0;
-abort:
-  return(_status);
-}
-
-static char *find_http_terminator(char *response, size_t len)
-{
-  char *term = response;
-  char *end = response + len;
-  int N = strlen(END_HEADERS);
-
-  for (; term = memchr(term, '\r', end - term); ++term) {
-    if (end - term >= N && memcmp(term, END_HEADERS, N) == 0) {
-      return term;
-    }
-  }
-
-  return NULL;
-}
-
-static int parse_http_response(char *begin, char *end, unsigned int *status)
-{
-  size_t len = end - begin;
-  char response[MAX_HTTP_CONNECT_BUFFER_SIZE + 1];
-
-  // len should *never* be greater than nr_socket_proxy_tunnel::buffered_bytes.
-  // Which in turn should never be greater nr_socket_proxy_tunnel::buffer size.
-  assert(len <= MAX_HTTP_CONNECT_BUFFER_SIZE);
-  if (len > MAX_HTTP_CONNECT_BUFFER_SIZE) {
-    return R_BAD_DATA;
-  }
-
-  memcpy(response, begin, len);
-  response[len] = '\0';
-
-  // http://www.rfc-editor.org/rfc/rfc7230.txt
-  // status-line    = HTTP-version SP status-code SP reason-phrase CRLF
-  // HTTP-version   = HTTP-name "/" DIGIT "." DIGIT
-  // HTTP-name      = "HTTP" ; "HTTP", case-sensitive
-  // status-code    = 3DIGIT
-
-  if (sscanf(response, "HTTP/%*u.%*u %u", status) != 1) {
-    r_log(LOG_GENERIC,LOG_WARNING,"parse_http_response failed to find status (%s)", response);
-    return R_BAD_DATA;
-  }
-
-  return 0;
-}
-
-static int nr_socket_proxy_tunnel_destroy(void **objpp)
-{
-  nr_socket_proxy_tunnel *sock;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_destroy");
-
-  if (!objpp || !*objpp)
-    return 0;
-
-  sock = (nr_socket_proxy_tunnel *)*objpp;
-  *objpp = 0;
-
-  if (sock->resolver_handle) {
-    nr_resolver_cancel(sock->config->resolver, sock->resolver_handle);
-  }
-
-  nr_proxy_tunnel_config_destroy(&sock->config);
-  nr_socket_destroy(&sock->inner);
-  RFREE(sock);
-
-  return 0;
-}
-
-static int nr_socket_proxy_tunnel_getfd(void *obj, NR_SOCKET *fd)
-{
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel *)obj;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_getfd");
-
-  return nr_socket_getfd(sock->inner, fd);
-}
-
-static int nr_socket_proxy_tunnel_getaddr(void *obj, nr_transport_addr *addrp)
-{
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel *)obj;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_getaddr");
-
-  return nr_socket_getaddr(sock->inner, addrp);
-}
-
-static int nr_socket_proxy_tunnel_resolved_cb(void *obj, nr_transport_addr *proxy_addr)
-{
-  int r, _status;
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel*)obj;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_resolved_cb");
-
-  /* Mark the socket resolver as completed */
-  sock->resolver_handle = 0;
-
-  if (proxy_addr) {
-    r_log(LOG_GENERIC,LOG_DEBUG,"Resolved proxy address %s -> %s",
-        sock->config->proxy_host, proxy_addr->as_string);
-  }
-  else {
-    r_log(LOG_GENERIC,LOG_WARNING,"Failed to resolve proxy %s",
-        sock->config->proxy_host);
-    /* TODO: Mozilla bug 1241758: because of the callback the return value goes
-     * nowhere, so we can't mark the candidate as failed, so everything depends
-     * on the overall timeouts in this case. */
-    sock->state = PROXY_TUNNEL_FAILED;
-    ABORT(R_NOT_FOUND);
-  }
-
-  if ((r=nr_socket_connect(sock->inner, proxy_addr))) {
-    ABORT(r);
-  }
-
-  _status = 0;
-abort:
-  return(_status);
-}
-
-int nr_socket_proxy_tunnel_connect(void *obj, nr_transport_addr *addr)
-{
-  int r, _status;
-  int has_addr;
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel*)obj;
-  nr_proxy_tunnel_config *config = sock->config;
-  nr_transport_addr proxy_addr, local_addr;
-  nr_resolver_resource resource;
-
-  if ((r=nr_transport_addr_copy(&sock->remote_addr, addr))) {
-    ABORT(r);
-  }
-
-  assert(config->proxy_host);
-
-  /* Check if the proxy_host is already an IP address */
-  has_addr = !nr_str_port_to_transport_addr(config->proxy_host,
-      config->proxy_port, IPPROTO_TCP, &proxy_addr);
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_connect: %s", config->proxy_host);
-
-  if (!has_addr && !config->resolver) {
-    r_log(LOG_GENERIC,LOG_ERR,"nr_socket_proxy_tunnel_connect name resolver not configured");
-    ABORT(R_NOT_FOUND);
-  }
-
-  if (!has_addr) {
-    resource.domain_name=config->proxy_host;
-    resource.port=config->proxy_port;
-    resource.stun_turn=NR_RESOLVE_PROTOCOL_TURN;
-    resource.transport_protocol=IPPROTO_TCP;
-
-    if ((r=nr_socket_getaddr(sock->inner, &local_addr))) {
-      r_log(LOG_GENERIC,LOG_ERR,"nr_socket_proxy_tunnel_connect failed to get local address");
-      ABORT(r);
-    }
-
-    switch(local_addr.ip_version) {
-      case NR_IPV4:
-        resource.address_family=AF_INET;
-        break;
-      case NR_IPV6:
-        resource.address_family=AF_INET6;
-        break;
-      default:
-        ABORT(R_BAD_ARGS);
-    }
-
-    r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_connect: nr_resolver_resolve");
-    if ((r=nr_resolver_resolve(config->resolver, &resource,
-            nr_socket_proxy_tunnel_resolved_cb, (void *)sock, &sock->resolver_handle))) {
-      r_log(LOG_GENERIC,LOG_ERR,"Could not invoke DNS resolver");
-      ABORT(r);
-    }
-
-    ABORT(R_WOULDBLOCK);
-  }
-
-  if ((r=nr_socket_connect(sock->inner, &proxy_addr))) {
-    ABORT(r);
-  }
-
-  _status=0;
-abort:
-  return(_status);
-}
-
-int nr_socket_proxy_tunnel_write(void *obj, const void *msg, size_t len,
-                                 size_t *written)
-{
-  int r, _status;
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel*)obj;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_write");
-
-  if (sock->state >= PROXY_TUNNEL_CLOSED) {
-    return R_FAILED;
-  }
-
-  if (sock->state == PROXY_TUNNEL_NONE) {
-    if ((r=send_http_connect(sock))) {
-      ABORT(r);
-    }
-  }
-
-  if (sock->state != PROXY_TUNNEL_CONNECTED) {
-    return R_WOULDBLOCK;
-  }
-
-  if ((r=nr_socket_write(sock->inner, msg, len, written, 0))) {
-    ABORT(r);
-  }
-
-  _status=0;
-abort:
-  return(_status);
-}
-
-int nr_socket_proxy_tunnel_read(void *obj, void * restrict buf, size_t maxlen,
-                                size_t *len)
-{
-  int r, _status;
-  char *ptr, *http_term;
-  size_t bytes_read, available_buffer_len, maxlen_int;
-  size_t pending;
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel*)obj;
-  unsigned int http_status;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_read");
-
-  *len = 0;
-
-  if (sock->state >= PROXY_TUNNEL_CLOSED) {
-    return R_FAILED;
-  }
-
-  if (sock->state == PROXY_TUNNEL_CONNECTED) {
-    return nr_socket_read(sock->inner, buf, maxlen, len, 0);
-  }
-
-  if (sock->buffered_bytes >= sizeof(sock->buffer)) {
-    r_log(LOG_GENERIC,LOG_ERR,"buffer filled waiting for CONNECT response");
-    assert(sock->buffered_bytes == sizeof(sock->buffer));
-    ABORT(R_INTERNAL);
-  }
-
-  /* Do not read more than maxlen bytes */
-  available_buffer_len = sizeof(sock->buffer) - sock->buffered_bytes;
-  maxlen_int = maxlen < available_buffer_len ? maxlen : available_buffer_len;
-  if ((r=nr_socket_read(sock->inner, sock->buffer + sock->buffered_bytes,
-          maxlen_int, &bytes_read, 0))) {
-    ABORT(r);
-  }
-
-  sock->buffered_bytes += bytes_read;
-
-  if (http_term = find_http_terminator(sock->buffer, sock->buffered_bytes)) {
-    if ((r = parse_http_response(sock->buffer, http_term, &http_status))) {
-      ABORT(r);
-    }
-
-    /* TODO (bug 1115934): Handle authentication challenges. */
-    if (http_status < 200 || http_status >= 300) {
-      r_log(LOG_GENERIC,LOG_ERR,"nr_socket_proxy_tunnel_read unable to connect %u",
-            http_status);
-      ABORT(R_FAILED);
-    }
-
-    sock->state = PROXY_TUNNEL_CONNECTED;
-
-    ptr = http_term + strlen(END_HEADERS);
-    pending = sock->buffered_bytes - (ptr - sock->buffer);
-
-    if (pending == 0) {
-      ABORT(R_WOULDBLOCK);
-    }
-
-    assert(pending <= maxlen);
-    *len = pending;
-
-    memcpy(buf, ptr, *len);
-  }
-
-  _status=0;
-abort:
-  if (_status && _status != R_WOULDBLOCK) {
-      sock->state = PROXY_TUNNEL_FAILED;
-  }
-  return(_status);
-}
-
-int nr_socket_proxy_tunnel_close(void *obj)
-{
-  nr_socket_proxy_tunnel *sock = (nr_socket_proxy_tunnel*)obj;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_close");
-
-  if (sock->resolver_handle) {
-    nr_resolver_cancel(sock->config->resolver, sock->resolver_handle);
-    sock->resolver_handle = 0;
-  }
-
-  sock->state = PROXY_TUNNEL_CLOSED;
-
-  return nr_socket_close(sock->inner);
-}
-
-int nr_proxy_tunnel_config_create(nr_proxy_tunnel_config **configpp)
-{
-  int _status;
-  nr_proxy_tunnel_config *configp=0;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_proxy_tunnel_config_create");
-
-  if (!(configp=RCALLOC(sizeof(nr_proxy_tunnel_config))))
-    ABORT(R_NO_MEMORY);
-
-  *configpp=configp;
-  _status=0;
-abort:
-  return(_status);
-}
-
-int nr_proxy_tunnel_config_destroy(nr_proxy_tunnel_config **configpp)
-{
-  nr_proxy_tunnel_config *configp;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_proxy_tunnel_config_destroy");
-
-  if (!configpp || !*configpp)
-    return 0;
-
-  configp = *configpp;
-  *configpp = 0;
-
-  RFREE(configp->proxy_host);
-  RFREE(configp->alpn);
-  RFREE(configp);
-
-  return 0;
-}
-
-int nr_proxy_tunnel_config_set_proxy(nr_proxy_tunnel_config *config,
-                                     const char *host, UINT2 port)
-{
-  char *hostdup;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_proxy_tunnel_config_set_proxy %s %d", host, port);
-
-  if (!host) {
-    return R_BAD_ARGS;
-  }
-
-  if (!(hostdup = r_strdup(host))) {
-    return R_NO_MEMORY;
-  }
-
-  if (config->proxy_host) {
-    RFREE(config->proxy_host);
-  }
-
-  config->proxy_host = hostdup;
-  config->proxy_port = port;
-
-  return 0;
-}
-
-int nr_proxy_tunnel_config_set_resolver(nr_proxy_tunnel_config *config,
-                                        nr_resolver *resolver)
-{
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_proxy_tunnel_config_set_resolver");
-
-  config->resolver = resolver;
-
-  return 0;
-}
-
-int nr_proxy_tunnel_config_set_alpn(nr_proxy_tunnel_config *config,
-                                    const char *alpn)
-{
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_proxy_tunnel_config_set_alpn");
-
-  if (alpn && (strlen(alpn) > MAX_ALPN_LENGTH)) {
-    return R_BAD_ARGS;
-  }
-
-  if (config->alpn) {
-    RFREE(config->alpn);
-  }
-
-  config->alpn = NULL;
-
-  if (alpn) {
-    char *alpndup = r_strdup(alpn);
-
-    if (!alpndup) {
-      return R_NO_MEMORY;
-    }
-
-    config->alpn = alpndup;
-  }
-
-  return 0;
-}
-
-int nr_proxy_tunnel_config_copy(nr_proxy_tunnel_config *config, nr_proxy_tunnel_config **copypp)
-{
-  int r,_status;
-  nr_proxy_tunnel_config *copy = 0;
-
-  if ((r=nr_proxy_tunnel_config_create(&copy)))
-    ABORT(r);
-
-  if ((r=nr_proxy_tunnel_config_set_proxy(copy, config->proxy_host, config->proxy_port)))
-    ABORT(r);
-
-  if ((r=nr_proxy_tunnel_config_set_resolver(copy, config->resolver)))
-    ABORT(r);
-
-  if ((r=nr_proxy_tunnel_config_set_alpn(copy, config->alpn)))
-    ABORT(r);
-
-  *copypp = copy;
-
-  _status=0;
-abort:
-  if (_status) {
-    nr_proxy_tunnel_config_destroy(&copy);
-  }
-  return(_status);
-}
-
-
-int nr_socket_proxy_tunnel_create(nr_proxy_tunnel_config *config,
-                                  nr_socket *inner,
-                                  nr_socket **socketpp)
-{
-  int r, _status;
-  nr_socket_proxy_tunnel *sock=0;
-  void *sockv;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_create");
-
-  if (!config) {
-    ABORT(R_BAD_ARGS);
-  }
-
-  if (!(sock=RCALLOC(sizeof(nr_socket_proxy_tunnel)))) {
-    ABORT(R_NO_MEMORY);
-  }
-
-  sock->inner = inner;
-
-  if ((r=nr_proxy_tunnel_config_copy(config, &sock->config)))
-    ABORT(r);
-
-  if ((r=nr_socket_create_int(sock, &nr_socket_proxy_tunnel_vtbl, socketpp))) {
-    ABORT(r);
-  }
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_proxy_tunnel_created");
-
-  _status=0;
-abort:
-  if (_status) {
-    sockv = sock;
-    nr_socket_proxy_tunnel_destroy(&sockv);
-  }
-  return(_status);
-}
-
-int nr_socket_wrapper_factory_proxy_tunnel_wrap(void *obj,
-                                                nr_socket *inner,
-                                                nr_socket **socketpp)
-{
-  nr_socket_wrapper_factory_proxy_tunnel *wrapper = (nr_socket_wrapper_factory_proxy_tunnel *)obj;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_wrapper_factory_proxy_tunnel_wrap");
-
-  return nr_socket_proxy_tunnel_create(wrapper->config, inner, socketpp);
-}
-
-
-int nr_socket_wrapper_factory_proxy_tunnel_destroy(void **objpp) {
-  nr_socket_wrapper_factory_proxy_tunnel *wrapper;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_wrapper_factory_proxy_tunnel_destroy");
-
-  if (!objpp || !*objpp)
-    return 0;
-
-  wrapper = (nr_socket_wrapper_factory_proxy_tunnel *)*objpp;
-  *objpp = 0;
-
-  nr_proxy_tunnel_config_destroy(&wrapper->config);
-  RFREE(wrapper);
-
-  return 0;
-}
-
-static nr_socket_wrapper_factory_vtbl proxy_tunnel_wrapper_vtbl = {
-  nr_socket_wrapper_factory_proxy_tunnel_wrap,
-  nr_socket_wrapper_factory_proxy_tunnel_destroy
-};
-
-int nr_socket_wrapper_factory_proxy_tunnel_create(nr_proxy_tunnel_config *config,
-                                                  nr_socket_wrapper_factory **factory) {
-  int r,_status;
-  nr_socket_wrapper_factory_proxy_tunnel *wrapper=0;
-  void *wrapperv;
-
-  r_log(LOG_GENERIC,LOG_DEBUG,"nr_socket_wrapper_factory_proxy_tunnel_create");
-
-  if (!(wrapper=RCALLOC(sizeof(nr_socket_wrapper_factory_proxy_tunnel))))
-    ABORT(R_NO_MEMORY);
-
-  if ((r=nr_proxy_tunnel_config_copy(config, &wrapper->config)))
-    ABORT(r);
-
-  if ((r=nr_socket_wrapper_factory_create_int(wrapper, &proxy_tunnel_wrapper_vtbl, factory)))
-    ABORT(r);
-
-  _status=0;
-abort:
-  if (_status) {
-    wrapperv = wrapper;
-    nr_socket_wrapper_factory_proxy_tunnel_destroy(&wrapperv);
-  }
-  return(_status);
-}
deleted file mode 100644
--- a/media/mtransport/third_party/nICEr/src/net/nr_proxy_tunnel.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-Copyright (c) 2007, Adobe Systems, Incorporated
-Copyright (c) 2013, Mozilla
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-* Neither the name of Adobe Systems, Network Resonance, Mozilla nor
-  the names of its contributors may be used to endorse or promote
-  products derived from this software without specific prior written
-  permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#ifndef _nr_proxy_tunnel_h
-#define _nr_proxy_tunnel_h
-
-#include "nr_socket.h"
-#include "nr_resolver.h"
-#include "nr_socket_wrapper.h"
-
-typedef struct nr_proxy_tunnel_config_ {
-  nr_resolver *resolver;
-  char *proxy_host;
-  UINT2 proxy_port;
-  char *alpn;
-} nr_proxy_tunnel_config;
-
-int nr_proxy_tunnel_config_create(nr_proxy_tunnel_config **config);
-
-int nr_proxy_tunnel_config_destroy(nr_proxy_tunnel_config **config);
-
-int nr_proxy_tunnel_config_set_proxy(nr_proxy_tunnel_config *config,
-                                     const char* host, UINT2 port);
-
-int nr_proxy_tunnel_config_set_resolver(nr_proxy_tunnel_config *config,
-                                        nr_resolver *resolver);
-
-int nr_proxy_tunnel_config_set_alpn(nr_proxy_tunnel_config *config,
-                                    const char *alpn);
-
-int nr_socket_proxy_tunnel_create(nr_proxy_tunnel_config *config,
-                                  nr_socket *inner,
-                                  nr_socket **socketpp);
-
-int nr_socket_wrapper_factory_proxy_tunnel_create(nr_proxy_tunnel_config *config,
-                                                  nr_socket_wrapper_factory **factory);
-
-#endif
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
@@ -262,22 +262,19 @@ MediaTransportHandler::Destroy()
                        stats.turn_401s);
   Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_403S,
                        stats.turn_403s);
   Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_438S,
                        stats.turn_438s);
 }
 
 nsresult
-MediaTransportHandler::SetProxyServer(const std::string& aProxyHost,
-                                      uint16_t aProxyPort,
-                                      const std::string& aAlpnProtocols)
+MediaTransportHandler::SetProxyServer(NrSocketProxyConfig&& aProxyConfig)
 {
-  NrIceProxyServer proxyServer(aProxyHost, aProxyPort, aAlpnProtocols);
-  return mIceCtx->SetProxyServer(proxyServer);
+  return mIceCtx->SetProxyServer(std::move(aProxyConfig));
 }
 
 void
 MediaTransportHandler::EnsureProvisionalTransport(
     const std::string& aTransportId,
     const std::string& aUfrag,
     const std::string& aPwd,
     size_t aComponentCount)
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
@@ -56,19 +56,17 @@ class MediaTransportHandler : public Med
   public:
     MediaTransportHandler();
     nsresult Init(const std::string& aName,
                   const dom::RTCConfiguration& aConfiguration);
     void Destroy();
 
     // We will probably be able to move the proxy lookup stuff into
     // this class once we move mtransport to its own process.
-    nsresult SetProxyServer(const std::string& aProxyHost,
-                            uint16_t aProxyPort,
-                            const std::string& aAlpnProtocols);
+    nsresult SetProxyServer(NrSocketProxyConfig&& aProxyConfig);
 
     void EnsureProvisionalTransport(const std::string& aTransportId,
                                     const std::string& aLocalUfrag,
                                     const std::string& aLocalPwd,
                                     size_t aComponentCount);
 
     // We set default-route-only as late as possible because it depends on what
     // capture permissions have been granted on the window, which could easily
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -1,33 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CSFLog.h"
 
+#include "nr_socket_proxy_config.h"
 #include "MediaPipelineFilter.h"
 #include "MediaPipeline.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionMedia.h"
 #include "runnable_utils.h"
 #include "signaling/src/jsep/JsepSession.h"
 #include "signaling/src/jsep/JsepTransport.h"
 
 #include "nsContentUtils.h"
 #include "nsIURI.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsICancelable.h"
 #include "nsILoadInfo.h"
 #include "nsIContentPolicy.h"
 #include "nsIProxyInfo.h"
 #include "nsIProtocolProxyService.h"
+#include "nsIPrincipal.h"
+#include "nsProxyRelease.h"
 
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/dom/PBrowserOrId.h"
+#include "mozilla/dom/TabChild.h"
 #include "MediaManager.h"
 #include "WebrtcGmpVideoCodec.h"
 
 namespace mozilla {
 using namespace dom;
 
 static const char* pcmLogTag = "PeerConnectionMedia";
 #ifdef LOGTAG
@@ -59,35 +64,20 @@ OnProxyAvailable(nsICancelable *request,
   return NS_OK;
 }
 
 void
 PeerConnectionMedia::ProtocolProxyQueryHandler::SetProxyOnPcm(
     nsIProxyInfo& proxyinfo)
 {
   CSFLogInfo(LOGTAG, "%s: Had proxyinfo", __FUNCTION__);
-  nsresult rv;
-  nsCString httpsProxyHost;
-  int32_t httpsProxyPort;
 
-  rv = proxyinfo.GetHost(httpsProxyHost);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "%s: Failed to get proxy server host", __FUNCTION__);
-    return;
-  }
-
-  rv = proxyinfo.GetPort(&httpsProxyPort);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "%s: Failed to get proxy server port", __FUNCTION__);
-    return;
-  }
-
-  assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16));
-  pcm_->mProxyHost = httpsProxyHost.get();
-  pcm_->mProxyPort = static_cast<uint16_t>(httpsProxyPort);
+  nsCString alpn = NS_LITERAL_CSTRING("webrtc,c-webrtc");
+  PBrowserOrId browser = TabChild::GetFrom(pcm_->GetWindow());
+  pcm_->mProxyConfig.reset(new NrSocketProxyConfig(browser, alpn));
 }
 
 NS_IMPL_ISUPPORTS(PeerConnectionMedia::ProtocolProxyQueryHandler, nsIProtocolProxyCallback)
 
 void
 PeerConnectionMedia::StunAddrsHandler::OnStunAddrsAvailable(
     const mozilla::net::NrIceStunAddrArray& addrs)
 {
@@ -111,16 +101,17 @@ PeerConnectionMedia::StunAddrsHandler::O
 PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
     : mTransportHandler(nullptr),
       mParent(parent),
       mParentHandle(parent->GetHandle()),
       mParentName(parent->GetName()),
       mMainThread(mParent->GetMainThread()),
       mSTSThread(mParent->GetSTSThread()),
       mProxyResolveCompleted(false),
+      mProxyConfig(nullptr),
       mLocalAddrsCompleted(false) {
 }
 
 PeerConnectionMedia::~PeerConnectionMedia()
 {
   MOZ_RELEASE_ASSERT(!mMainThread);
 }
 
@@ -486,24 +477,24 @@ PeerConnectionMedia::GatherIfReady() {
         &PeerConnectionMedia::EnsureIceGathering_s,
         GetPrefDefaultAddressOnly()));
 
   PerformOrEnqueueIceCtxOperation(runnable);
 }
 
 void
 PeerConnectionMedia::EnsureIceGathering_s(bool aDefaultRouteOnly) {
-  if (!mProxyHost.empty()) {
+  if (mProxyConfig) {
     // 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.
-    mTransportHandler->SetProxyServer(
-        mProxyHost, mProxyPort, "webrtc,c-webrtc");
+    mTransportHandler->SetProxyServer(std::move(*mProxyConfig));
+    mProxyConfig.reset();
   }
 
   // Make sure we don't call StartIceGathering if we're in e10s mode
   // and we received no STUN addresses from the parent process.  In the
   // absence of previously provided STUN addresses, StartIceGathering will
   // attempt to gather them (as in non-e10s mode), and this will cause a
   // sandboxing exception in e10s mode.
   if (!mStunAddrs.Length() && XRE_IsContentProcess()) {
@@ -849,9 +840,14 @@ PeerConnectionMedia::AnyCodecHasPluginID
   for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
     if (transceiver->ConduitHasPluginID(aPluginID)) {
       return true;
     }
   }
   return false;
 }
 
+nsPIDOMWindowInner*
+PeerConnectionMedia::GetWindow() const
+{
+  return mParent->GetWindow();
+}
 } // namespace mozilla
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -121,16 +121,18 @@ class PeerConnectionMedia : public sigsl
 
   // Used by PCImpl in a couple of places. Might be good to move that code in
   // here.
   std::vector<RefPtr<TransceiverImpl>>& GetTransceivers()
   {
     return mTransceivers;
   }
 
+  nsPIDOMWindowInner* GetWindow() const;
+
   void AlpnNegotiated_s(const std::string& aAlpn);
   static void AlpnNegotiated_m(const std::string& aParentHandle,
                                const std::string& aAlpn);
 
   // ICE state signals
   sigslot::signal1<mozilla::dom::PCImplIceGatheringState>
       SignalIceGatheringStateChange;
   sigslot::signal1<mozilla::dom::PCImplIceConnectionState>
@@ -242,19 +244,18 @@ class PeerConnectionMedia : public sigsl
   std::vector<nsCOMPtr<nsIRunnable>> mQueuedIceCtxOperations;
 
   // Used to cancel any ongoing proxy request.
   nsCOMPtr<nsICancelable> mProxyRequest;
 
   // Used to track the state of the request.
   bool mProxyResolveCompleted;
 
-  // Used to store the result of the request.
-  std::string mProxyHost;
-  uint16_t mProxyPort;
+  // Used to track proxy existence and socket proxy configuration.
+  std::unique_ptr<NrSocketProxyConfig> mProxyConfig;
 
   // Used to cancel incoming stun addrs response
   RefPtr<net::StunAddrsRequestChild> mStunAddrsRequest;
 
   // Used to track the state of the stun addr IPC request
   bool mLocalAddrsCompleted;
 
   // Used to store the result of the stun addr IPC request
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/net/IPCTransportProvider.h"
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "mozilla/net/AltDataOutputStreamChild.h"
 #include "mozilla/net/TrackingDummyChannelChild.h"
 #ifdef MOZ_WEBRTC
 #include "mozilla/net/StunAddrsRequestChild.h"
+#include "mozilla/net/WebrtcProxyChannelChild.h"
 #endif
 
 #include "SerializedLoadContext.h"
 #include "nsGlobalWindow.h"
 #include "nsIOService.h"
 #include "nsINetworkPredictor.h"
 #include "nsINetworkPredictorVerifier.h"
 #include "nsINetworkLinkService.h"
@@ -109,16 +110,36 @@ NeckoChild::DeallocPStunAddrsRequestChil
 {
 #ifdef MOZ_WEBRTC
   StunAddrsRequestChild* p = static_cast<StunAddrsRequestChild*>(aActor);
   p->ReleaseIPDLReference();
 #endif
   return true;
 }
 
+PWebrtcProxyChannelChild*
+NeckoChild::AllocPWebrtcProxyChannelChild(const PBrowserOrId& browser)
+{
+  // We don't allocate here: instead we always use IPDL constructor that takes
+  // an existing object
+  MOZ_ASSERT_UNREACHABLE("AllocPWebrtcProxyChannelChild should not be called on"
+                         " child");
+  return nullptr;
+}
+
+bool
+NeckoChild::DeallocPWebrtcProxyChannelChild(PWebrtcProxyChannelChild* aActor)
+{
+#ifdef MOZ_WEBRTC
+  WebrtcProxyChannelChild* child = static_cast<WebrtcProxyChannelChild*>(aActor);
+  child->ReleaseIPDLReference();
+#endif
+  return true;
+}
+
 PAltDataOutputStreamChild*
 NeckoChild::AllocPAltDataOutputStreamChild(
         const nsCString& type,
         const int64_t& predictedSize,
         PHttpChannelChild* channel)
 {
   // We don't allocate here: see HttpChannelChild::OpenAlternativeOutputStream()
   MOZ_ASSERT_UNREACHABLE("AllocPAltDataOutputStreamChild should not be called");
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -29,16 +29,21 @@ protected:
     AllocPHttpChannelChild(const PBrowserOrId&, const SerializedLoadContext&,
                            const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool DeallocPHttpChannelChild(PHttpChannelChild*) override;
 
   virtual PStunAddrsRequestChild* AllocPStunAddrsRequestChild() override;
   virtual bool
     DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor) override;
 
+  virtual PWebrtcProxyChannelChild* AllocPWebrtcProxyChannelChild(
+    const PBrowserOrId& browser) override;
+  virtual bool
+    DeallocPWebrtcProxyChannelChild(PWebrtcProxyChannelChild* aActor) override;
+
   virtual PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(const nsCString& type, const int64_t& predictedSize, PHttpChannelChild* channel) override;
   virtual bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor) override;
 
   virtual PCookieServiceChild* AllocPCookieServiceChild() override;
   virtual bool DeallocPCookieServiceChild(PCookieServiceChild*) override;
   virtual PWyciwygChannelChild* AllocPWyciwygChannelChild() override;
   virtual bool DeallocPWyciwygChannelChild(PWyciwygChannelChild*) override;
   virtual PFTPChannelChild*
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -25,16 +25,17 @@
 #include "mozilla/net/FileChannelParent.h"
 #include "mozilla/net/DNSRequestParent.h"
 #include "mozilla/net/ChannelDiverterParent.h"
 #include "mozilla/net/IPCTransportProvider.h"
 #include "mozilla/net/RequestContextService.h"
 #include "mozilla/net/TrackingDummyChannelParent.h"
 #ifdef MOZ_WEBRTC
 #include "mozilla/net/StunAddrsRequestParent.h"
+#include "mozilla/net/WebrtcProxyChannelParent.h"
 #endif
 #include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
 #include "mozilla/dom/network/TCPServerSocketParent.h"
 #include "mozilla/dom/network/UDPSocketParent.h"
@@ -345,16 +346,39 @@ NeckoParent::DeallocPStunAddrsRequestPar
 {
 #ifdef MOZ_WEBRTC
   StunAddrsRequestParent* p = static_cast<StunAddrsRequestParent*>(aActor);
   p->Release();
 #endif
   return true;
 }
 
+PWebrtcProxyChannelParent*
+NeckoParent::AllocPWebrtcProxyChannelParent(const PBrowserOrId& aBrowser)
+{
+#ifdef MOZ_WEBRTC
+  RefPtr<TabParent> tab = TabParent::GetFrom(aBrowser.get_PBrowserParent());
+  WebrtcProxyChannelParent* parent = new WebrtcProxyChannelParent(tab);
+  parent->AddRef();
+  return parent;
+#else
+  return nullptr;
+#endif
+}
+
+bool
+NeckoParent::DeallocPWebrtcProxyChannelParent(PWebrtcProxyChannelParent* aActor)
+{
+#ifdef MOZ_WEBRTC
+  WebrtcProxyChannelParent* parent = static_cast<WebrtcProxyChannelParent*>(aActor);
+  parent->Release();
+#endif
+  return true;
+}
+
 PAltDataOutputStreamParent*
 NeckoParent::AllocPAltDataOutputStreamParent(
         const nsCString& type,
         const int64_t& predictedSize,
         PHttpChannelParent* channel)
 {
   HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel);
   nsCOMPtr<nsIOutputStream> stream;
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -103,16 +103,22 @@ protected:
       const SerializedLoadContext& aSerialized,
       const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool DeallocPHttpChannelParent(PHttpChannelParent*) override;
 
   virtual PStunAddrsRequestParent* AllocPStunAddrsRequestParent() override;
   virtual bool
     DeallocPStunAddrsRequestParent(PStunAddrsRequestParent* aActor) override;
 
+  virtual PWebrtcProxyChannelParent* AllocPWebrtcProxyChannelParent(
+    const PBrowserOrId& aBrowser) override;
+  virtual bool
+    DeallocPWebrtcProxyChannelParent(PWebrtcProxyChannelParent* aActor)
+    override;
+
   virtual PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent(
     const nsCString& type, const int64_t& predictedSize, PHttpChannelParent* channel) override;
   virtual bool DeallocPAltDataOutputStreamParent(
     PAltDataOutputStreamParent* aActor) override;
 
   virtual bool DeallocPCookieServiceParent(PCookieServiceParent*) override;
   virtual PWyciwygChannelParent* AllocPWyciwygChannelParent() override;
   virtual bool DeallocPWyciwygChannelParent(PWyciwygChannelParent*) override;
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -22,16 +22,17 @@ include protocol PFileDescriptorSet;
 include protocol PDataChannel;
 include protocol PSimpleChannel;
 include protocol PTransportProvider;
 include protocol PChildToParentStream; //FIXME: bug #792908
 include protocol PParentToChildStream; //FIXME: bug #792908
 include protocol PStunAddrsRequest;
 include protocol PFileChannel;
 include protocol PTrackingDummyChannel;
+include protocol PWebrtcProxyChannel;
 
 include IPCStream;
 include URIParams;
 include NeckoChannelParams;
 include PBrowserOrId;
 include protocol PAltDataOutputStream;
 
 using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
@@ -60,16 +61,17 @@ nested(upto inside_cpow) sync protocol P
   manages PDataChannel;
   manages PSimpleChannel;
   manages PFileChannel;
   manages PChannelDiverter;
   manages PTransportProvider;
   manages PAltDataOutputStream;
   manages PStunAddrsRequest;
   manages PTrackingDummyChannel;
+  manages PWebrtcProxyChannel;
 
 parent:
   async __delete__();
 
   nested(inside_cpow) async PCookieService();
   async PHttpChannel(PBrowserOrId browser,
                      SerializedLoadContext loadContext,
                      HttpChannelCreationArgs args);
@@ -128,16 +130,18 @@ parent:
   async RequestContextLoadBegin(uint64_t rcid);
   async RequestContextAfterDOMContentLoaded(uint64_t rcid);
   async RemoveRequestContext(uint64_t rcid);
 
   async PAltDataOutputStream(nsCString type, int64_t predictedSize, PHttpChannel channel);
 
   async PStunAddrsRequest();
 
+  async PWebrtcProxyChannel(PBrowserOrId browser);
+
   /**
    * WebExtension-specific remote resource loading
    */
   async GetExtensionStream(URIParams uri) returns (nsIInputStream stream);
   async GetExtensionFD(URIParams uri) returns (FileDescriptor fd);
 
 child:
   /*
--- a/netwerk/ipc/moz.build
+++ b/netwerk/ipc/moz.build
@@ -29,16 +29,17 @@ IPDL_SOURCES = [
     'PNecko.ipdl',
     'PSimpleChannel.ipdl',
 ]
 
 # needed so --disable-webrtc builds work (yes, a bit messy)
 if not CONFIG['MOZ_WEBRTC']:
   IPDL_SOURCES += [
       '../../media/mtransport/ipc/PStunAddrsRequest.ipdl',
+      '../../media/mtransport/ipc/PWebrtcProxyChannel.ipdl',
   ]
   EXPORTS.mozilla.net += [
       '../../media/mtransport/ipc/NrIceStunAddrMessageUtils.h',
       '../../media/mtransport/ipc/PStunAddrsParams.h',
   ]
 
 include('/ipc/chromium/chromium-config.mozbuild')