This is an automated email from the git hooks/post-receive script.
shelikhoo pushed a commit to branch main in repository pluggable-transports/snowflake-webext.
The following commit(s) were added to refs/heads/main by this push: new 15768f5 Add Relay URL support to web proxy 15768f5 is described below
commit 15768f50c0ddd68d3ffb815cd532ddbd3d85fd41 Author: Shelikhoo xiaokangwang@outlook.com AuthorDate: Fri May 20 16:37:14 2022 +0100
Add Relay URL support to web proxy --- broker.js | 3 ++- config.js | 2 ++ proxypair.js | 16 +++++++++++---- snowflake.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++++----------- websocket.js | 26 +++++++++++++++++++++++- 5 files changed, 94 insertions(+), 18 deletions(-)
diff --git a/broker.js b/broker.js index d83f03c..e993a48 100644 --- a/broker.js +++ b/broker.js @@ -67,11 +67,12 @@ class Broker { this._xhr = xhr; // Used by spec to fake async Broker interaction const clients = Math.floor(numClientsConnected / 8) * 8; var data = { - Version: "1.2", + Version: "1.3", Sid: id, Type: this.config.proxyType, NAT: this.natType, Clients: clients, + AcceptedRelayPattern: this.config.allowedRelayPattern, }; return this._postRequest(xhr, 'proxy', JSON.stringify(data)); }); diff --git a/config.js b/config.js index d05554c..cac8506 100644 --- a/config.js +++ b/config.js @@ -51,3 +51,5 @@ Config.prototype.pcConfig = { };
Config.PROBEURL = "https://snowflake-broker.freehaven.net:8443/probe"; + +Config.prototype.allowedRelayPattern="snowflake.torproject.net"; \ No newline at end of file diff --git a/proxypair.js b/proxypair.js index 169511b..1d9689e 100644 --- a/proxypair.js +++ b/proxypair.js @@ -24,6 +24,7 @@ class ProxyPair { this.onError = this.onError.bind(this); this.flush = this.flush.bind(this);
+ this.relayURL = undefined; this.relayAddr = relayAddr; this.rateLimit = rateLimit; this.config = config; @@ -82,14 +83,14 @@ class ProxyPair { channel.onclose = () => { log('WebRTC DataChannel closed.'); snowflake.ui.setStatus('disconnected by webrtc.'); - if(this.counted) { + if (this.counted) { snowflake.ui.decreaseClients(); this.counted = false; } this.flush(); return this.close(); }; - channel.onerror = function() { + channel.onerror = function () { return log('Data channel error!'); }; channel.binaryType = "arraybuffer"; @@ -112,7 +113,10 @@ class ProxyPair { if (peer_ip != null) { params.push(["client_ip", peer_ip]); } - var relay = this.relay = WS.makeWebsocket(this.relayAddr, params); + var relay = this.relay = + (this.relayURL === undefined) ? + WS.makeWebsocket(this.relayAddr, params) : + WS.makeWebsocketFromURL(this.relayURL, params); this.relay.label = 'websocket-relay'; this.relay.onopen = () => { if (this.timer) { @@ -125,7 +129,7 @@ class ProxyPair { this.relay.onclose = () => { log(relay.label + ' closed.'); snowflake.ui.setStatus('disconnected.'); - if(this.counted) { + if (this.counted) { snowflake.ui.decreaseClients(); this.counted = false; } @@ -248,6 +252,10 @@ class ProxyPair { return (null !== this.pc) && ('closed' !== this.pc.connectionState); }
+ setRelayURL(relayURL) { + this.relayURL = relayURL; + } + }
ProxyPair.prototype.MAX_BUFFER = 10 * 1024 * 1024; diff --git a/snowflake.js b/snowflake.js index 03b0229..7370f01 100644 --- a/snowflake.js +++ b/snowflake.js @@ -68,12 +68,12 @@ class Snowflake { } this.ui.setStatus(msg); //update NAT type - console.log("NAT type: "+ this.ui.natType); + console.log("NAT type: " + this.ui.natType); this.broker.setNATType(this.ui.natType); recv = this.broker.getClientOffer(pair.id, this.proxyPairs.length); recv.then((resp) => { var clientNAT = resp.NAT; - if (!this.receiveOffer(pair, resp.Offer)) { + if (!this.receiveOffer(pair, resp.Offer, resp.RelayURL)) { return pair.close(); } //set a timeout for channel creation @@ -83,14 +83,14 @@ class Snowflake { pair.close(); // increase poll interval this.pollInterval = - Math.min(this.pollInterval + this.config.pollAdjustment, - this.config.slowestBrokerPollInterval); + Math.min(this.pollInterval + this.config.pollAdjustment, + this.config.slowestBrokerPollInterval); if (clientNAT == "restricted") { this.natFailures++; } // if we fail to connect to a restricted client 3 times in // a row, assume we have a restricted NAT - if (this.natFailures >= 3){ + if (this.natFailures >= 3) { this.ui.natType = "restricted"; console.log("Learned NAT type: restricted"); this.natFailures = 0; @@ -99,13 +99,13 @@ class Snowflake { } else { // decrease poll interval this.pollInterval = - Math.max(this.pollInterval - this.config.pollAdjustment, - this.config.defaultBrokerPollInterval); + Math.max(this.pollInterval - this.config.pollAdjustment, + this.config.defaultBrokerPollInterval); this.natFailures = 0; } return; }), this.config.datachannelTimeout); - }, function() { + }, function () { //on error, close proxy pair return pair.close(); }); @@ -114,9 +114,24 @@ class Snowflake {
// Receive an SDP offer from some client assigned by the Broker, // |pair| - an available ProxyPair. - receiveOffer(pair, desc) { + receiveOffer(pair, desc, relayURL) { var e, offer, sdp; + try { + if (relayURL !== undefined) { + let relayURLParsed = new URL(relayURL); + let hostname = relayURLParsed.hostname; + let protocol = relayURLParsed.protocol; + if (protocol !== "wss:") { + log('incorrect relay url protocol'); + return false; + } + if (!this.checkRelayPattern(this.config.allowedRelayPattern, hostname)) { + log('relay url hostname does not match allowed pattern'); + return false; + } + pair.setRelayURL(relayURL); + } offer = JSON.parse(desc); dbg('Received:\n\n' + offer.sdp + '\n'); sdp = new RTCSessionDescription(offer); @@ -135,11 +150,11 @@ class Snowflake {
sendAnswer(pair) { var fail, next; - next = function(sdp) { + next = function (sdp) { dbg('webrtc: Answer ready.'); return pair.pc.setLocalDescription(sdp).catch(fail); }; - fail = function() { + fail = function () { pair.close(); return dbg('webrtc: Failed to create or set Answer'); }; @@ -154,7 +169,7 @@ class Snowflake { pair = new ProxyPair(this.relayAddr, this.rateLimit, this.config); this.proxyPairs.push(pair);
- log('Snowflake IDs: ' + (this.proxyPairs.map(function(p) { + log('Snowflake IDs: ' + (this.proxyPairs.map(function (p) { return p.id; })).join(' | '));
@@ -182,6 +197,32 @@ class Snowflake { return results; }
+ /** + * checkRelayPattern match str against patten + * @param {string} pattern + * @param {string} str typically a domain name to be checked + * @return {boolean} + */ + checkRelayPattern(pattern, str) { + if (typeof pattern !== "string") { + throw 'invalid checkRelayPattern input: pattern'; + } + if (typeof str !== "string") { + throw 'invalid checkRelayPattern input: str'; + } + + let exactMatch = false; + if (pattern.charAt(0) === "^") { + exactMatch = true; + pattern = pattern.substring(1); + } + + if (exactMatch) { + return pattern.localeCompare(str) === 0; + } + return str.endsWith(pattern); + } + }
Snowflake.prototype.relayAddr = null; diff --git a/websocket.js b/websocket.js index da7ba94..fdf470c 100644 --- a/websocket.js +++ b/websocket.js @@ -27,7 +27,7 @@ class WS { if (!path.match(/^//)) { path = '/' + path; } - path = path.replace(/[^/]+/, function(m) { + path = path.replace(/[^/]+/, function (m) { return encodeURIComponent(m); }); parts.push(path); @@ -54,6 +54,30 @@ class WS { return ws; }
+ /** + * Creates a websocket connection from a URL and params to override + * @param {URL|string} url + * @param {URLSearchParams|string[][]} params + * @return {WebSocket} + */ + static makeWebsocketFromURL(url, params) { + let parsedURL = new URL(url); + let urlpa = new URLSearchParams(params); + urlpa.forEach(function (value, key) { + parsedURL.searchParams.set(key, value); + }); + + let ws = new WebSocket(url); + /* + 'User agents can use this as a hint for how to handle incoming binary data: + if the attribute is set to 'blob', it is safe to spool it to disk, and if it + is set to 'arraybuffer', it is likely more efficient to keep the data in + memory.' + */ + ws.binaryType = 'arraybuffer'; + return ws; + } + static probeWebsocket(addr) { return new Promise((resolve, reject) => { const ws = WS.makeWebsocket(addr);
tor-commits@lists.torproject.org