[tor-commits] [snowflake-webext/master] Make a test connection to determine NAT type

cohosh at torproject.org cohosh at torproject.org
Thu Oct 29 14:55:34 UTC 2020


commit e1958c454de9e24f464efaf82837fa6da1270120
Author: Cecylia Bocovich <cohosh at torproject.org>
Date:   Wed Oct 14 11:32:29 2020 -0400

    Make a test connection to determine NAT type
    
    Make a test connection to the symmetrically NAT'd probe site to
    determine NAT type. This replaces the previous method of getting
    ICE candidates from two different STUN servers and more
    definitively determines compatability with a symmetrically NAT'd
    client.
---
 config.js      |  2 ++
 init-badge.js  |  7 ++++--
 init-webext.js |  9 +++++---
 util.js        | 72 +++++++++++++++++++++++++++++++++++++++++-----------------
 4 files changed, 64 insertions(+), 26 deletions(-)

diff --git a/config.js b/config.js
index d662013..b5db8b5 100644
--- a/config.js
+++ b/config.js
@@ -46,3 +46,5 @@ Config.prototype.pcConfig = {
     }
   ]
 };
+
+Config.PROBEURL = "https://snowflake-broker.torproject.net:8443/probe";
diff --git a/init-badge.js b/init-badge.js
index 4b432fc..6c4c7a7 100644
--- a/init-badge.js
+++ b/init-badge.js
@@ -150,10 +150,13 @@ var debug, snowflake, config, broker, ui, log, dbg, init, update, initNATType, s
   initNATType = function() {
     this.natType = "unknown";
     (function loop(_this) {
-      Util.checkNATType().then((type) => {
+      Util.checkNATType(config.datachannelTimeout).then((type) => {
         console.log("Setting NAT type: " + type);
         _this.natType = type;
-      }).catch((e) => console.log(e));
+      }).catch((e) => {
+        console.log(e);
+        _this.natType = "unknown";
+      });
       // reset NAT type every 24 hours in case proxy location changed
       setTimeout(_this.initNATType, 24 * 60 * 60 * 1000);
     })(this);
diff --git a/init-webext.js b/init-webext.js
index e38ab88..0a2c22b 100644
--- a/init-webext.js
+++ b/init-webext.js
@@ -13,7 +13,6 @@ class WebExtUI extends UI {
     this.onMessage = this.onMessage.bind(this);
     this.onDisconnect = this.onDisconnect.bind(this);
     this.initStats();
-    this.initNATType();
     chrome.runtime.onConnect.addListener(this.onConnect);
   }
 
@@ -29,10 +28,13 @@ class WebExtUI extends UI {
   initNATType() {
     this.natType = "unknown";
     (function loop(_this) {
-      Util.checkNATType().then((type) => {
+      Util.checkNATType(config.datachannelTimeout).then((type) => {
         console.log("Setting NAT type: " + type);
         _this.natType = type;
-      }).catch((e) => console.log(e));
+      }).catch((e) => {
+        console.log(e);
+        _this.natType = "unknown";
+      });
       // reset NAT type every 24 hours in case proxy location changed
       setTimeout(_this.initNATType, 24 * 60 * 60 * 1000);
     })(this);
@@ -200,6 +202,7 @@ var debug, snowflake, config, broker, ui, log, dbg, init, update, silenceNotific
     snowflake = new Snowflake(config, ui, broker);
     log('== snowflake proxy ==');
     ui.initToggle();
+    ui.initNATType();
   };
 
   update = function() {
diff --git a/util.js b/util.js
index ee36478..8ab65d9 100644
--- a/util.js
+++ b/util.js
@@ -1,5 +1,5 @@
 /* exported Util, Params, DummyRateLimit */
-/* global PeerConnection */
+/* global PeerConnection, Config */
 
 /*
 A JavaScript WebRTC snowflake proxy
@@ -21,33 +21,63 @@ class Util {
     return navigator.cookieEnabled;
   }
 
-  // returns a promise that fullfills to "restricted" if the
-  // mapping is symmetric, and we know it's a restrictive NAT,
-  // and fullfills to "unknown" if the mapping is not
-  // symmetric.
-  static checkNATType() {
+  // returns a promise that fullfills to "restricted" if we
+  // fail to make a test connection to a known restricted
+  // NAT, "unrestricted" if the test connection fails, and
+  // "unknown" if we fail to reach the probe test server
+  static checkNATType(timeout) {
     return new Promise((fulfill, reject) => {
-      let port = null;
+      let open = false;
       let pc = new PeerConnection({iceServers: [
-        {urls: 'stun:stun1.l.google.com:19302'},
-        {urls: 'stun:stun2.l.google.com:19302'}
+        {urls: 'stun:stun1.l.google.com:19302'}
       ]});
-      pc.createDataChannel("NAT test");
-      pc.onicecandidate = function(e) {
-        if (e.candidate) {
-          let p = Parse.portFromCandidate(e.candidate.candidate);
-          if (port == null) port = p;
-          else if (p != null && p != port) fulfill("restricted");
-        } else { // done parsing candidates
-          fulfill("unknown");
-        }
+      let channel = pc.createDataChannel("NAT test");
+      channel.onopen = function() {
+        open = true;
+        fulfill("unrestricted");
+        channel.close();
+        pc.close();
       };
-      pc.createOffer().then((offer) => {
-        pc.setLocalDescription(offer);
-      }).catch((e) => {
+      pc.createOffer()
+      .then((offer) =>  pc.setLocalDescription(offer))
+      .then(() => Util.sendOffer(pc.localDescription))
+      .then((answer) => pc.setRemoteDescription(JSON.parse(answer)))
+      .catch((e) => {
         console.log(e);
         reject("Error checking NAT type");
       });
+      setTimeout(() => {if(!open) fulfill("restricted");}, timeout);
+    });
+  }
+
+  // Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
+  // Sends it back to the broker, which passes it back to the original client.
+  static sendOffer(offer) {
+    return new Promise((fulfill, reject) => {
+      var xhr;
+      xhr = new XMLHttpRequest();
+      xhr.timeout = 10 * 1000;
+      xhr.onreadystatechange = function() {
+        if (xhr.DONE !== xhr.readyState) {
+          return;
+        }
+        switch (xhr.status) {
+          case 200:
+            var response = JSON.parse(xhr.responseText);
+            return fulfill(response.Answer); // Should contain offer.
+          default:
+            console.log('Probe ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
+            return reject('Failed to get answer from probe service');
+        }
+      };
+      var data = {"Status": "client match", "Offer": JSON.stringify(offer)};
+      try {
+        xhr.open('POST', Config.PROBEURL);
+      } catch (error) {
+        console.log('Signaling Server: exception while connecting: ' + error.message);
+        return reject('unable to connect to signaling server');
+      }
+      return xhr.send(JSON.stringify(data));
     });
   }
 }



More information about the tor-commits mailing list