Bug 1056934 - Part 2: Test-case for TURN TLS. r=drno
authorByron Campen [:bwc] <docfaraday@gmail.com>
Tue, 03 Jan 2017 12:28:13 -0600
changeset 462758 341ba823f532f2915481e44ce673a7c9f607cfb7
parent 462757 a9a5828b9c89324e71563b3c14d8b76df6f21a93
child 462759 0eb5a5b7809b2ece5e17654311f3690171cf97e2
push id41857
push userbmo:mh+mozilla@glandium.org
push dateWed, 18 Jan 2017 00:24:11 +0000
reviewersdrno
bugs1056934
milestone53.0a1
Bug 1056934 - Part 2: Test-case for TURN TLS. r=drno MozReview-Commit-ID: AWJGwWE55Ct
dom/media/tests/mochitest/addTurnsSelfsignedCert.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTLS.html
media/mtransport/nricectx.cpp
media/mtransport/test_nr_socket.cpp
media/mtransport/test_nr_socket.h
media/mtransport/third_party/nICEr/src/net/transport_addr.h
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/addTurnsSelfsignedCert.js
@@ -0,0 +1,26 @@
+/* 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/. */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+// This is only usable from the parent process, even for doing simple stuff like
+// serializing a cert.
+var gCertMaker = Cc["@mozilla.org/security/x509certdb;1"].
+              getService(Ci.nsIX509CertDB);
+
+var gCertOverrides = Cc["@mozilla.org/security/certoverride;1"].
+              getService(Ci.nsICertOverrideService);
+
+
+addMessageListener('add-turns-certs', certs => {
+  var port = 5349;
+  certs.forEach(certDescription => {
+    var cert = gCertMaker.constructX509FromBase64(certDescription.cert);
+    gCertOverrides.rememberValidityOverride(certDescription.hostname, port,
+        cert, Ci.nsICertOverrideService.ERROR_UNTRUSTED, false);
+  });
+  sendAsyncMessage('certs-added');
+});
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
   network.js
   nonTrickleIce.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
   sdpUtils.js
+  addTurnsSelfsignedCert.js
   !/dom/canvas/test/captureStream_common.js
   !/dom/canvas/test/webgl-mochitest/webgl-util.js
   !/dom/media/test/manifest.js
   !/dom/media/test/320x240.ogv
   !/dom/media/test/r11025_s16_c1.wav
   !/dom/media/test/bug461281.ogg
   !/dom/media/test/seek.webm
   !/dom/media/test/gizmo.mp4
@@ -92,16 +93,18 @@ skip-if = android_version == '18' # andr
 [test_peerConnection_basicAudio.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
+[test_peerConnection_basicAudioNATRelayTLS.html]
+skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioRequireEOC.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioPcmaPcmuOnly.html]
 skip-if = android_version == '18'
 [test_peerConnection_basicAudioDynamicPtMissingRtpmap.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioVideo.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1898,16 +1898,43 @@ var scriptsReady = Promise.all([
 
 function createHTML(options) {
   return scriptsReady.then(() => realCreateHTML(options));
 }
 
 var iceServerWebsocket;
 var iceServersArray = [];
 
+var addTurnsSelfsignedCerts = () => {
+  var gUrl = SimpleTest.getTestFileURL('addTurnsSelfsignedCert.js');
+  var gScript = SpecialPowers.loadChromeScript(gUrl);
+  var certs = [];
+  // If the ICE server is running TURNS, and includes a "cert" attribute in
+  // its JSON, we set up an override that will forgive things like
+  // self-signed for it.
+  iceServersArray.forEach(iceServer => {
+    if (iceServer.hasOwnProperty("cert")) {
+      iceServer.urls.forEach(url => {
+        if (url.startsWith("turns:")) {
+          // Assumes no port or params!
+          certs.push({"cert": iceServer.cert, "hostname": url.substr(6)});
+        }
+      });
+    }
+  });
+
+  return new Promise((resolve, reject) => {
+    gScript.addMessageListener('certs-added', () => {
+      resolve();
+    });
+
+    gScript.sendAsyncMessage('add-turns-certs', certs);
+  });
+};
+
 var setupIceServerConfig = useIceServer => {
   // We disable ICE support for HTTP proxy when using a TURN server, because
   // mochitest uses a fake HTTP proxy to serve content, which will eat our STUN
   // packets for TURN TCP.
   var enableHttpProxy = enable =>
     SpecialPowers.pushPrefEnv(
       {'set': [['media.peerconnection.disable_http_proxy', !enable]]});
 
@@ -1937,17 +1964,18 @@ var setupIceServerConfig = useIceServer 
 
   if (!useIceServer) {
     info("Skipping ICE Server for this test");
     return enableHttpProxy(true);
   }
 
   return enableHttpProxy(false)
     .then(spawnIceServer)
-    .then(iceServersStr => { iceServersArray = JSON.parse(iceServersStr); });
+    .then(iceServersStr => { iceServersArray = JSON.parse(iceServersStr); })
+    .then(addTurnsSelfsignedCerts);
 };
 
 function runNetworkTest(testFunction, fixtureOptions) {
   fixtureOptions = fixtureOptions || {}
   return scriptsReady.then(() =>
     runTestWhenReady(options =>
       startNetworkAndTest()
         .then(() => setupIceServerConfig(fixtureOptions.useIceServer))
copy from dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTCP.html
copy to dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTLS.html
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTCP.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTLS.html
@@ -3,27 +3,28 @@
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231975",
-    title: "Basic audio-only peer connection with port dependent NAT that blocks UDP"
+    title: "Basic audio-only peer connection with port dependent NAT that blocks STUN"
   });
 
   var test;
   runNetworkTest(options => {
     SpecialPowers.pushPrefEnv(
       {
         'set': [
           ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
           ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
-          ['media.peerconnection.nat_simulator.block_udp', true]
+          ['media.peerconnection.nat_simulator.block_udp', true],
+          ['media.peerconnection.nat_simulator.block_tcp', true]
         ]
       }, function (options) {
         options = options || {};
         options.expectedLocalCandidateType = "relayed-tcp";
         options.expectedRemoteCandidateType = "relayed-tcp";
         // No reason to wait for gathering to complete like the other NAT tests,
         // since relayed-tcp is the only thing that can work.
         test = new PeerConnectionTest(options);
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -582,16 +582,17 @@ NrIceCtx::Initialize(const std::string& 
       MOZ_MTLOG(ML_ERROR, "Couldn't set trickle cb for '" << name_ << "'");
       return false;
     }
   }
 
   nsCString mapping_type;
   nsCString filtering_type;
   bool block_udp = false;
+  bool block_tcp = false;
 
   nsresult rv;
   nsCOMPtr<nsIPrefService> pref_service =
     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
 
   if (NS_SUCCEEDED(rv)) {
     nsCOMPtr<nsIPrefBranch> pref_branch;
     rv = pref_service->GetBranch(nullptr, getter_AddRefs(pref_branch));
@@ -600,26 +601,30 @@ NrIceCtx::Initialize(const std::string& 
           "media.peerconnection.nat_simulator.mapping_type",
           getter_Copies(mapping_type));
       rv = pref_branch->GetCharPref(
           "media.peerconnection.nat_simulator.filtering_type",
           getter_Copies(filtering_type));
       rv = pref_branch->GetBoolPref(
           "media.peerconnection.nat_simulator.block_udp",
           &block_udp);
+      rv = pref_branch->GetBoolPref(
+          "media.peerconnection.nat_simulator.block_tcp",
+          &block_tcp);
     }
   }
 
   if (!mapping_type.IsEmpty() && !filtering_type.IsEmpty()) {
     MOZ_MTLOG(ML_DEBUG, "NAT filtering type: " << filtering_type.get());
     MOZ_MTLOG(ML_DEBUG, "NAT mapping type: " << mapping_type.get());
     TestNat* test_nat = new TestNat;
     test_nat->filtering_type_ = TestNat::ToNatBehavior(filtering_type.get());
     test_nat->mapping_type_ = TestNat::ToNatBehavior(mapping_type.get());
     test_nat->block_udp_ = block_udp;
+    test_nat->block_tcp_ = block_tcp;
     test_nat->enabled_ = true;
     SetNat(test_nat);
   }
 
   // Create the handler objects
   ice_handler_vtbl_ = new nr_ice_handler_vtbl();
   ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair;
   ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready;
--- a/media/mtransport/test_nr_socket.cpp
+++ b/media/mtransport/test_nr_socket.cpp
@@ -196,16 +196,17 @@ int TestNat::create_socket_factory(nr_so
   if (!r) {
     AddRef();
   }
   return r;
 }
 
 TestNrSocket::TestNrSocket(TestNat *nat)
   : nat_(nat),
+    tls_(false),
     timer_handle_(nullptr) {
   nat_->insert_socket(this);
 }
 
 TestNrSocket::~TestNrSocket() {
   nat_->erase_socket(this);
 }
 
@@ -469,16 +470,20 @@ bool TestNrSocket::allow_ingress(const n
 
 int TestNrSocket::connect(nr_transport_addr *addr) {
 
   if (connect_invoked_ || !port_mappings_.empty()) {
     MOZ_CRASH("TestNrSocket::connect() called more than once!");
     return R_INTERNAL;
   }
 
+  if (addr->tls_host[0] != '\0') {
+    tls_ = true;
+  }
+
   if (!nat_->enabled_
       || addr->protocol==IPPROTO_UDP  // Horrible hack to allow default address
                                       // discovery to work. Only works because
                                       // we don't normally connect on UDP.
       || nat_->is_an_internal_tuple(*addr)) {
     // This will set connect_invoked_
     return internal_socket_->connect(addr);
   }
@@ -504,53 +509,91 @@ int TestNrSocket::connect(nr_transport_a
                              (char*)__FUNCTION__,
                              __LINE__);
   }
 
   return r;
 }
 
 int TestNrSocket::write(const void *msg, size_t len, size_t *written) {
+  UCHAR *buf = static_cast<UCHAR*>(const_cast<void*>(msg));
+  if (nat_->block_stun_ && nr_is_stun_message(buf, len)) {
+    // Should cause this socket to be abandoned
+    r_log(LOG_GENERIC, LOG_DEBUG,
+          "TestNrSocket %s dropping outgoing TCP "
+          "because it is configured to drop STUN",
+          my_addr().as_string);
+    return R_INTERNAL;
+  }
+
+  if (nat_->block_tcp_ && !tls_) {
+    // Should cause this socket to be abandoned
+    r_log(LOG_GENERIC, LOG_DEBUG,
+          "TestNrSocket %s dropping outgoing TCP "
+          "because it is configured to drop TCP",
+          my_addr().as_string);
+    return R_INTERNAL;
+  }
 
   if (port_mappings_.empty()) {
     // The no-nat case, just pass call through.
     r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s writing",
           my_addr().as_string);
 
     return internal_socket_->write(msg, len, written);
   } else {
     destroy_stale_port_mappings();
     if (port_mappings_.empty()) {
-      return -1;
+      r_log(LOG_GENERIC, LOG_DEBUG,
+            "TestNrSocket %s dropping outgoing TCP "
+            "because the port mapping was stale",
+            my_addr().as_string);
+      return R_INTERNAL;
     }
     // This is TCP only
     MOZ_ASSERT(port_mappings_.size() == 1);
     r_log(LOG_GENERIC, LOG_DEBUG,
           "PortMapping %s -> %s writing",
           port_mappings_.front()->external_socket_->my_addr().as_string,
           port_mappings_.front()->remote_address_.as_string);
 
     port_mappings_.front()->last_used_ = PR_IntervalNow();
     return port_mappings_.front()->external_socket_->write(msg, len, written);
   }
 }
 
 int TestNrSocket::read(void *buf, size_t maxlen, size_t *len) {
+  int r;
 
   if (port_mappings_.empty()) {
-    return internal_socket_->read(buf, maxlen, len);
+    r = internal_socket_->read(buf, maxlen, len);
   } else {
     MOZ_ASSERT(port_mappings_.size() == 1);
-    int bytesRead =
-      port_mappings_.front()->external_socket_->read(buf, maxlen, len);
-    if (bytesRead > 0 && nat_->refresh_on_ingress_) {
+    r = port_mappings_.front()->external_socket_->read(buf, maxlen, len);
+    if (!r && nat_->refresh_on_ingress_) {
       port_mappings_.front()->last_used_ = PR_IntervalNow();
     }
-    return bytesRead;
+  }
+
+  if (r) {
+    return r;
   }
+
+  if (nat_->block_tcp_ && !tls_) {
+    // Should cause this socket to be abandoned
+    return R_INTERNAL;
+  }
+
+  UCHAR *cbuf = static_cast<UCHAR*>(const_cast<void*>(buf));
+  if (nat_->block_stun_ && nr_is_stun_message(cbuf, *len)) {
+    // Should cause this socket to be abandoned
+    return R_INTERNAL;
+  }
+
+  return r;
 }
 
 int TestNrSocket::async_wait(int how, NR_async_cb cb, void *cb_arg,
                              char *function, int line) {
   r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s waiting for %s",
                                 internal_socket_->my_addr().as_string,
                                 how == NR_ASYNC_WAIT_READ ? "read" : "write");
 
--- a/media/mtransport/test_nr_socket.h
+++ b/media/mtransport/test_nr_socket.h
@@ -133,16 +133,17 @@ class TestNat {
       enabled_(false),
       filtering_type_(ENDPOINT_INDEPENDENT),
       mapping_type_(ENDPOINT_INDEPENDENT),
       mapping_timeout_(30000),
       allow_hairpinning_(false),
       refresh_on_ingress_(false),
       block_udp_(false),
       block_stun_(false),
+      block_tcp_(false),
       delay_stun_resp_ms_(0),
       sockets_() {}
 
     bool has_port_mappings() const;
 
     // Helps determine whether we're hairpinning
     bool is_my_external_tuple(const nr_transport_addr &addr) const;
     bool is_an_internal_tuple(const nr_transport_addr &addr) const;
@@ -164,16 +165,17 @@ class TestNat {
     bool enabled_;
     TestNat::NatBehavior filtering_type_;
     TestNat::NatBehavior mapping_type_;
     uint32_t mapping_timeout_;
     bool allow_hairpinning_;
     bool refresh_on_ingress_;
     bool block_udp_;
     bool block_stun_;
+    bool block_tcp_;
     /* Note: this can only delay a single response so far (bug 1253657) */
     uint32_t delay_stun_resp_ms_;
 
   private:
     std::set<TestNrSocket*> sockets_;
 
     ~TestNat(){}
 };
@@ -315,16 +317,17 @@ class TestNrSocket : public NrSocketBase
 
     static void process_delayed_cb(NR_SOCKET s, int how, void *cb_arg);
 
     RefPtr<NrSocketBase> readable_socket_;
     // The socket for the "internal" address; used to talk to stuff behind the
     // same nat.
     RefPtr<NrSocketBase> internal_socket_;
     RefPtr<TestNat> nat_;
+    bool tls_;
     // Since our comparison logic is different depending on what kind of NAT
     // we simulate, and the STL does not make it very easy to switch out the
     // comparison function at runtime, and these lists are going to be very
     // small anyway, we just brute-force it.
     std::list<RefPtr<PortMapping>> port_mappings_;
 
     void *timer_handle_;
 };
--- a/media/mtransport/third_party/nICEr/src/net/transport_addr.h
+++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.h
@@ -61,16 +61,17 @@ typedef struct nr_transport_addr_ {
   union {
     struct sockaddr_in addr4;
     struct sockaddr_in6 addr6;
   } u;
   char ifname[MAXIFNAME];
   /* A string version.
      56 = 5 ("IP6:[") + 39 (ipv6 address) + 2 ("]:") + 5 (port) + 4 (/UDP) + 1 (null) */
   char as_string[56];
+  char tls_host[256];
 } nr_transport_addr;
 
 typedef struct nr_transport_addr_mask_ {
   UINT4 addr;
   UINT4 mask;
 } nr_transport_addr_mask;
 
 int nr_sockaddr_to_transport_addr(struct sockaddr *saddr, int protocol, int keep, nr_transport_addr *addr);