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 374678 341ba823f532f2915481e44ce673a7c9f607cfb7
parent 374677 a9a5828b9c89324e71563b3c14d8b76df6f21a93
child 374679 0eb5a5b7809b2ece5e17654311f3690171cf97e2
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdrno
bugs1056934
milestone53.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 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);