mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 11:11:30 -04:00
begin including flashproxy websocket stuff into the JS proxy
This commit is contained in:
parent
a16a4b43a5
commit
ceac90ec07
2 changed files with 319 additions and 3 deletions
|
@ -27,8 +27,10 @@ var MODE = {
|
|||
CHAT: 2
|
||||
}
|
||||
var currentMode = MODE.INIT;
|
||||
var CONNECTIONS_PER_CLIENT = 2;
|
||||
|
||||
// Signalling channel - just tells user to copy paste to the peer.
|
||||
// Eventually this should go over the facilitator.
|
||||
var Signalling = {
|
||||
send: function(msg) {
|
||||
log("---- Please copy the below to peer ----\n");
|
||||
|
@ -237,3 +239,317 @@ var log = function(msg) {
|
|||
}
|
||||
|
||||
window.onload = init;
|
||||
|
||||
//
|
||||
// some code sourced from flashproxy.js
|
||||
// TODO: refactor / webrtc-afy it
|
||||
//
|
||||
|
||||
/* Does the WebSocket implementation in this browser support binary frames? (RFC
|
||||
6455 section 5.6.) If not, we have to use base64-encoded text frames. It is
|
||||
assumed that the client and relay endpoints always support binary frames. */
|
||||
function have_websocket_binary_frames() {
|
||||
var BROWSERS = [
|
||||
{ idString: "Chrome", verString: "Chrome", version: 16 },
|
||||
{ idString: "Safari", verString: "Version", version: 6 },
|
||||
{ idString: "Firefox", verString: "Firefox", version: 11 }
|
||||
];
|
||||
var ua;
|
||||
|
||||
ua = window.navigator.userAgent;
|
||||
if (!ua)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < BROWSERS.length; i++) {
|
||||
var matches, reg;
|
||||
|
||||
reg = "\\b" + BROWSERS[i].idString + "\\b";
|
||||
if (!ua.match(new RegExp(reg, "i")))
|
||||
continue;
|
||||
reg = "\\b" + BROWSERS[i].verString + "\\/(\\d+)";
|
||||
matches = ua.match(new RegExp(reg, "i"));
|
||||
return matches !== null && Number(matches[1]) >= BROWSERS[i].version;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function make_websocket(addr) {
|
||||
var url;
|
||||
var ws;
|
||||
|
||||
url = build_url("ws", addr.host, addr.port, "/");
|
||||
|
||||
if (have_websocket_binary_frames())
|
||||
ws = new WebSocket(url);
|
||||
else
|
||||
ws = new WebSocket(url, "base64");
|
||||
/* "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;
|
||||
}
|
||||
|
||||
function Snowflake() {
|
||||
if (HEADLESS) {
|
||||
/* No badge. */
|
||||
} else if (DEBUG) {
|
||||
this.badge_elem = debug_div;
|
||||
} else {
|
||||
this.badge = new Badge();
|
||||
this.badge_elem = this.badge.elem;
|
||||
}
|
||||
if (this.badge_elem)
|
||||
this.badge_elem.setAttribute("id", "flashproxy-badge");
|
||||
|
||||
this.proxy_pairs = [];
|
||||
|
||||
this.start = function() {
|
||||
var client_addr;
|
||||
var relay_addr;
|
||||
var rate_limit_bytes;
|
||||
log("Snowflake starting.")
|
||||
// TODO: Facilitator interaction
|
||||
};
|
||||
|
||||
this.proxy_main = function() {
|
||||
var params;
|
||||
var base_url, url;
|
||||
var xhr;
|
||||
|
||||
if (this.proxy_pairs.length >= this.max_num_clients * CONNECTIONS_PER_CLIENT) {
|
||||
setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
params = [["r", "1"]];
|
||||
params.push(["transport", "websocket"]);
|
||||
params.push(["transport", "webrtc"]);
|
||||
};
|
||||
|
||||
this.begin_proxy = function(client_addr, relay_addr) {
|
||||
for (var i=0; i<CONNECTIONS_PER_CLIENT; i++) {
|
||||
this.make_proxy_pair(client_addr, relay_addr);
|
||||
}
|
||||
};
|
||||
|
||||
this.make_proxy_pair = function(client_addr, relay_addr) {
|
||||
var proxy_pair;
|
||||
|
||||
proxy_pair = new ProxyPair(client_addr, relay_addr, this.rate_limit);
|
||||
this.proxy_pairs.push(proxy_pair);
|
||||
proxy_pair.cleanup_callback = function(event) {
|
||||
/* Delete from the list of active proxy pairs. */
|
||||
this.proxy_pairs.splice(this.proxy_pairs.indexOf(proxy_pair), 1);
|
||||
if (this.badge)
|
||||
this.badge.proxy_end();
|
||||
}.bind(this);
|
||||
try {
|
||||
proxy_pair.connect();
|
||||
} catch (err) {
|
||||
puts("ProxyPair: exception while connecting: " + safe_repr(err.message) + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.badge)
|
||||
this.badge.proxy_begin();
|
||||
};
|
||||
|
||||
/* Cease all network operations and prevent any future ones. */
|
||||
this.cease_operation = function() {
|
||||
this.start = function() { };
|
||||
this.proxy_main = function() { };
|
||||
this.make_proxy_pair = function(client_addr, relay_addr) { };
|
||||
while (this.proxy_pairs.length > 0)
|
||||
this.proxy_pairs.pop().close();
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
puts("Disabling.");
|
||||
this.cease_operation();
|
||||
if (this.badge)
|
||||
this.badge.disable();
|
||||
};
|
||||
|
||||
this.die = function() {
|
||||
puts("Dying.");
|
||||
this.cease_operation();
|
||||
if (this.badge)
|
||||
this.badge.die();
|
||||
};
|
||||
}
|
||||
|
||||
/* An instance of a client-relay connection. */
|
||||
function ProxyPair(client_addr, relay_addr, rate_limit) {
|
||||
var MAX_BUFFER = 10 * 1024 * 1024;
|
||||
|
||||
function log(s) {
|
||||
if (!SAFE_LOGGING) {
|
||||
s = format_addr(client_addr) + '|' + format_addr(relay_addr) + ' : ' + s
|
||||
}
|
||||
// puts(s)
|
||||
window.log(s)
|
||||
}
|
||||
|
||||
this.client_addr = client_addr;
|
||||
this.relay_addr = relay_addr;
|
||||
this.rate_limit = rate_limit;
|
||||
|
||||
this.c2r_schedule = [];
|
||||
this.r2c_schedule = [];
|
||||
|
||||
this.running = true;
|
||||
this.flush_timeout_id = null;
|
||||
|
||||
/* This callback function can be overridden by external callers. */
|
||||
this.cleanup_callback = function() {
|
||||
};
|
||||
|
||||
this.connect = function() {
|
||||
log("Client: connecting.");
|
||||
this.client_s = make_websocket(this.client_addr);
|
||||
|
||||
/* Try to connect to the client first (since that is more likely to
|
||||
fail) and only after that try to connect to the relay. */
|
||||
this.client_s.label = "Client";
|
||||
this.client_s.onopen = this.client_onopen_callback;
|
||||
this.client_s.onclose = this.onclose_callback;
|
||||
this.client_s.onerror = this.onerror_callback;
|
||||
this.client_s.onmessage = this.onmessage_client_to_relay;
|
||||
};
|
||||
|
||||
this.client_onopen_callback = function(event) {
|
||||
var ws = event.target;
|
||||
|
||||
log(ws.label + ": connected.");
|
||||
log("Relay: connecting.");
|
||||
this.relay_s = make_websocket(this.relay_addr);
|
||||
|
||||
this.relay_s.label = "Relay";
|
||||
this.relay_s.onopen = this.relay_onopen_callback;
|
||||
this.relay_s.onclose = this.onclose_callback;
|
||||
this.relay_s.onerror = this.onerror_callback;
|
||||
this.relay_s.onmessage = this.onmessage_relay_to_client;
|
||||
}.bind(this);
|
||||
|
||||
this.relay_onopen_callback = function(event) {
|
||||
var ws = event.target;
|
||||
|
||||
log(ws.label + ": connected.");
|
||||
}.bind(this);
|
||||
|
||||
this.maybe_cleanup = function() {
|
||||
if (this.running && is_closed(this.client_s) && is_closed(this.relay_s)) {
|
||||
this.running = false;
|
||||
this.cleanup_callback();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.onclose_callback = function(event) {
|
||||
var ws = event.target;
|
||||
|
||||
log(ws.label + ": closed.");
|
||||
this.flush();
|
||||
|
||||
if (this.maybe_cleanup()) {
|
||||
puts("Complete.");
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
this.onerror_callback = function(event) {
|
||||
var ws = event.target;
|
||||
|
||||
log(ws.label + ": error.");
|
||||
this.close();
|
||||
|
||||
// we can't rely on onclose_callback to cleanup, since one common error
|
||||
// case is when the client fails to connect and the relay never starts.
|
||||
// in that case close() is a NOP and onclose_callback is never called.
|
||||
this.maybe_cleanup();
|
||||
}.bind(this);
|
||||
|
||||
this.onmessage_client_to_relay = function(event) {
|
||||
this.c2r_schedule.push(event.data);
|
||||
this.flush();
|
||||
}.bind(this);
|
||||
|
||||
this.onmessage_relay_to_client = function(event) {
|
||||
this.r2c_schedule.push(event.data);
|
||||
this.flush();
|
||||
}.bind(this);
|
||||
|
||||
function is_open(ws) {
|
||||
return ws !== undefined && ws.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
function is_closed(ws) {
|
||||
return ws === undefined || ws.readyState === WebSocket.CLOSED;
|
||||
}
|
||||
|
||||
this.close = function() {
|
||||
if (!is_closed(this.client_s))
|
||||
this.client_s.close();
|
||||
if (!is_closed(this.relay_s))
|
||||
this.relay_s.close();
|
||||
};
|
||||
|
||||
/* Send as much data as the rate limit currently allows. */
|
||||
this.flush = function() {
|
||||
var busy;
|
||||
|
||||
if (this.flush_timeout_id)
|
||||
clearTimeout(this.flush_timeout_id);
|
||||
this.flush_timeout_id = null;
|
||||
|
||||
busy = true;
|
||||
while (busy && !this.rate_limit.is_limited()) {
|
||||
var chunk;
|
||||
busy = false;
|
||||
|
||||
if (is_open(this.client_s) &&
|
||||
this.client_s.bufferedAmount < MAX_BUFFER &&
|
||||
this.r2c_schedule.length > 0) {
|
||||
chunk = this.r2c_schedule.shift();
|
||||
this.rate_limit.update(chunk.length);
|
||||
this.client_s.send(chunk);
|
||||
busy = true;
|
||||
}
|
||||
if (is_open(this.relay_s) &&
|
||||
this.relay_s.bufferedAmount < MAX_BUFFER &&
|
||||
this.c2r_schedule.length > 0) {
|
||||
chunk = this.c2r_schedule.shift();
|
||||
this.rate_limit.update(chunk.length);
|
||||
this.relay_s.send(chunk);
|
||||
busy = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_closed(this.relay_s) &&
|
||||
!is_closed(this.client_s) &&
|
||||
this.client_s.bufferedAmount === 0 &&
|
||||
this.r2c_schedule.length === 0) {
|
||||
log("Client: closing.");
|
||||
this.client_s.close();
|
||||
}
|
||||
if (is_closed(this.client_s) &&
|
||||
!is_closed(this.relay_s) &&
|
||||
this.relay_s.bufferedAmount === 0 &&
|
||||
this.c2r_schedule.length === 0) {
|
||||
log("Relay: closing.");
|
||||
this.relay_s.close();
|
||||
}
|
||||
|
||||
if (this.r2c_schedule.length > 0 ||
|
||||
(is_open(this.client_s) && this.client_s.bufferedAmount > 0) ||
|
||||
this.c2r_schedule.length > 0 ||
|
||||
(is_open(this.relay_s) && this.relay_s.bufferedAmount > 0))
|
||||
this.flush_timeout_id = setTimeout(
|
||||
this.flush.bind(this), this.rate_limit.when() * 1000);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue