mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
datachannel successfully established between snowflake proxy and Tor client
This commit is contained in:
parent
7cbbe585e5
commit
026821f23c
1 changed files with 101 additions and 41 deletions
|
@ -51,14 +51,6 @@ query = Query.parse(window.location.search.substr(1))
|
||||||
HEADLESS = "undefined" == typeof(document)
|
HEADLESS = "undefined" == typeof(document)
|
||||||
DEBUG = Params.getBool(query, "debug", false)
|
DEBUG = Params.getBool(query, "debug", false)
|
||||||
|
|
||||||
# Janky state machine
|
|
||||||
MODE =
|
|
||||||
INIT: 0
|
|
||||||
CONNECTING: 1
|
|
||||||
CHAT: 2
|
|
||||||
currentMode = MODE.INIT
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Different ICE servers.
|
# TODO: Different ICE servers.
|
||||||
config = {
|
config = {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
|
@ -81,14 +73,24 @@ window.RTCSessionDescription = window.RTCSessionDescription ||
|
||||||
# TODO: Implement
|
# TODO: Implement
|
||||||
class Badge
|
class Badge
|
||||||
|
|
||||||
|
|
||||||
|
# Janky state machine
|
||||||
|
MODE =
|
||||||
|
INIT: 0
|
||||||
|
WEBRTC_CONNECTING: 1
|
||||||
|
WEBRTC_READY: 2
|
||||||
|
|
||||||
# Minimum viable snowflake for now - just 1 client.
|
# Minimum viable snowflake for now - just 1 client.
|
||||||
class Snowflake
|
class Snowflake
|
||||||
|
|
||||||
|
# PeerConnection
|
||||||
|
pc: null
|
||||||
rateLimit: 0
|
rateLimit: 0
|
||||||
proxyPairs: null
|
proxyPairs: null
|
||||||
badge: null
|
badge: null
|
||||||
$badge: null
|
$badge: null
|
||||||
MAX_NUM_CLIENTS = 1
|
MAX_NUM_CLIENTS = 1
|
||||||
|
state: MODE.INIT
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
if HEADLESS
|
if HEADLESS
|
||||||
|
@ -101,11 +103,74 @@ class Snowflake
|
||||||
if (@$badge)
|
if (@$badge)
|
||||||
@$badge.setAttribute("id", "snowflake-badge")
|
@$badge.setAttribute("id", "snowflake-badge")
|
||||||
|
|
||||||
start: ->
|
# Initialize WebRTC PeerConnection
|
||||||
|
beginWebRTC: ->
|
||||||
log "Starting up Snowflake..."
|
log "Starting up Snowflake..."
|
||||||
|
@state = MODE.WEBRTC_CONNECTING
|
||||||
|
|
||||||
|
@pc = new PeerConnection(config, {
|
||||||
|
optional: [
|
||||||
|
{ DtlsSrtpKeyAgreement: true }
|
||||||
|
{ RtpDataChannels: false }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
@pc.onicecandidate = (evt) =>
|
||||||
|
# Browser sends a null candidate once the ICE gathering completes.
|
||||||
|
# In this case, it makes sense to send one copy-paste blob.
|
||||||
|
if null == evt.candidate
|
||||||
|
log "Finished gathering ICE candidates."
|
||||||
|
Signalling.send @pc.localDescription
|
||||||
|
|
||||||
|
# OnDataChannel triggered remotely from the client when connection succeeds.
|
||||||
|
@pc.ondatachannel = (dc) =>
|
||||||
|
console.log dc;
|
||||||
|
channel = dc.channel
|
||||||
|
log "Data Channel established..."
|
||||||
|
@prepareDataChannel channel
|
||||||
|
|
||||||
|
prepareDataChannel: (channel) ->
|
||||||
|
channel.onopen = =>
|
||||||
|
log "Data channel opened!"
|
||||||
|
@state = MODE.WEBRTC_READY
|
||||||
|
# TODO: Prepare ProxyPair onw.
|
||||||
|
channel.onclose = =>
|
||||||
|
log "Data channel closed."
|
||||||
|
@state = MODE.INIT;
|
||||||
|
$chatlog.className = ""
|
||||||
|
channel.onerror = =>
|
||||||
|
log "Data channel error!"
|
||||||
|
channel.onmessage = (msg) =>
|
||||||
|
line = recv = msg.data
|
||||||
|
console.log(msg);
|
||||||
|
# Go sends only raw bytes...
|
||||||
|
if "[object ArrayBuffer]" == recv.toString()
|
||||||
|
bytes = new Uint8Array recv
|
||||||
|
line = String.fromCharCode.apply(null, bytes)
|
||||||
|
line = line.trim()
|
||||||
|
log "data: " + line
|
||||||
|
|
||||||
|
# Receive an SDP offer from client plugin.
|
||||||
|
receiveOffer: (desc) =>
|
||||||
|
sdp = new RTCSessionDescription desc
|
||||||
|
try
|
||||||
|
err = @pc.setRemoteDescription sdp
|
||||||
|
catch e
|
||||||
|
log "Invalid SDP message."
|
||||||
|
return false
|
||||||
|
log("SDP " + sdp.type + " successfully received.")
|
||||||
|
@sendAnswer() if "offer" == sdp.type
|
||||||
|
true
|
||||||
|
|
||||||
|
sendAnswer: =>
|
||||||
|
next = (sdp) =>
|
||||||
|
log "webrtc: Answer ready."
|
||||||
|
@pc.setLocalDescription sdp
|
||||||
|
promise = @pc.createAnswer next
|
||||||
|
promise.then next if promise
|
||||||
|
|
||||||
# Poll facilitator when this snowflake can support more clients.
|
# Poll facilitator when this snowflake can support more clients.
|
||||||
proxyMain = ->
|
proxyMain: ->
|
||||||
if @proxyPairs.length >= @MAX_NUM_CLIENTS * CONNECTIONS_PER_CLIENT
|
if @proxyPairs.length >= @MAX_NUM_CLIENTS * CONNECTIONS_PER_CLIENT
|
||||||
setTimeout(@proxyMain, @facilitator_poll_interval * 1000)
|
setTimeout(@proxyMain, @facilitator_poll_interval * 1000)
|
||||||
return
|
return
|
||||||
|
@ -114,11 +179,11 @@ class Snowflake
|
||||||
params.push ["transport", "websocket"]
|
params.push ["transport", "websocket"]
|
||||||
params.push ["transport", "webrtc"]
|
params.push ["transport", "webrtc"]
|
||||||
|
|
||||||
beginProxy = (client, relay) ->
|
beginProxy: (client, relay) ->
|
||||||
for i in [0..CONNECTIONS_PER_CLIENT]
|
for i in [0..CONNECTIONS_PER_CLIENT]
|
||||||
makeProxyPair(client, relay)
|
makeProxyPair(client, relay)
|
||||||
|
|
||||||
makeProxyPair = (client, relay) ->
|
makeProxyPair: (client, relay) ->
|
||||||
pair = new ProxyPair(client, relay, @rate_limit);
|
pair = new ProxyPair(client, relay, @rate_limit);
|
||||||
@proxyPairs.push pair
|
@proxyPairs.push pair
|
||||||
pair.onCleanup = (event) =>
|
pair.onCleanup = (event) =>
|
||||||
|
@ -132,19 +197,19 @@ class Snowflake
|
||||||
return
|
return
|
||||||
@badge.beginProxy if @badge
|
@badge.beginProxy if @badge
|
||||||
|
|
||||||
cease = ->
|
cease: ->
|
||||||
# @start = null
|
# @start = null
|
||||||
# @proxyMain = null
|
# @proxyMain = null
|
||||||
# @make_proxy_pair = function(client_addr, relay_addr) { };
|
# @make_proxy_pair = function(client_addr, relay_addr) { };
|
||||||
while @proxyPairs.length > 0
|
while @proxyPairs.length > 0
|
||||||
@proxyPairs.pop().close()
|
@proxyPairs.pop().close()
|
||||||
|
|
||||||
disable = ->
|
disable: ->
|
||||||
log "Disabling Snowflake."
|
log "Disabling Snowflake."
|
||||||
@cease()
|
@cease()
|
||||||
@badge.disable() if @badge
|
@badge.disable() if @badge
|
||||||
|
|
||||||
die = ->
|
die: ->
|
||||||
log "Snowflake died."
|
log "Snowflake died."
|
||||||
@cease()
|
@cease()
|
||||||
@badge.die() if @badge
|
@badge.die() if @badge
|
||||||
|
@ -157,7 +222,7 @@ class ProxyPair
|
||||||
#
|
#
|
||||||
## -- DOM & Input Functionality -- ##
|
## -- DOM & Input Functionality -- ##
|
||||||
#
|
#
|
||||||
snowflake = new Snowflake()
|
snowflake = null
|
||||||
|
|
||||||
welcome = ->
|
welcome = ->
|
||||||
log "== snowflake browser proxy =="
|
log "== snowflake browser proxy =="
|
||||||
|
@ -170,25 +235,22 @@ log = (msg) ->
|
||||||
# Scroll to latest
|
# Scroll to latest
|
||||||
$chatlog.scrollTop = $chatlog.scrollHeight
|
$chatlog.scrollTop = $chatlog.scrollHeight
|
||||||
|
|
||||||
# Local input from keyboard into message window.
|
Interface =
|
||||||
acceptInput = () ->
|
# Local input from keyboard into message window.
|
||||||
msg = $input.value
|
acceptInput: ->
|
||||||
switch currentMode
|
msg = $input.value
|
||||||
when MODE.INIT
|
switch snowflake.state
|
||||||
if msg.startsWith("start")
|
when MODE.WEBRTC_CONNECTING
|
||||||
start(true)
|
|
||||||
else
|
|
||||||
Signalling.receive msg
|
Signalling.receive msg
|
||||||
when MODE.CONNECTING
|
when MODE.WEBRTC_READY
|
||||||
Signalling.receive msg
|
log "No input expected - WebRTC connected."
|
||||||
when MODE.CHAT
|
# data = msg
|
||||||
data = msg
|
# log(data)
|
||||||
log(data)
|
# channel.send(data)
|
||||||
channel.send(data)
|
else
|
||||||
else
|
log "ERROR: " + msg
|
||||||
log("ERROR: " + msg)
|
$input.value = ""
|
||||||
$input.value = "";
|
$input.focus()
|
||||||
$input.focus()
|
|
||||||
|
|
||||||
# Signalling channel - just tells user to copy paste to the peer.
|
# Signalling channel - just tells user to copy paste to the peer.
|
||||||
# Eventually this should go over the facilitator.
|
# Eventually this should go over the facilitator.
|
||||||
|
@ -205,28 +267,26 @@ Signalling =
|
||||||
catch e
|
catch e
|
||||||
log "Invalid JSON."
|
log "Invalid JSON."
|
||||||
return
|
return
|
||||||
# Begin as answerer if peerconnection doesn't exist yet.
|
|
||||||
snowflake.start false if !pc
|
|
||||||
desc = recv['sdp']
|
desc = recv['sdp']
|
||||||
ice = recv['candidate']
|
if !desc
|
||||||
if !desc && ! ice
|
|
||||||
log "Invalid SDP."
|
log "Invalid SDP."
|
||||||
return false
|
return false
|
||||||
receiveDescription recv if desc
|
snowflake.receiveOffer recv if desc
|
||||||
receiveICE recv if ice
|
|
||||||
|
|
||||||
init = ->
|
init = ->
|
||||||
$chatlog = document.getElementById('chatlog')
|
$chatlog = document.getElementById('chatlog')
|
||||||
$chatlog.value = ""
|
$chatlog.value = ""
|
||||||
|
|
||||||
$send = document.getElementById('send')
|
$send = document.getElementById('send')
|
||||||
$send.onclick = acceptInput
|
$send.onclick = Interface.acceptInput
|
||||||
|
|
||||||
$input = document.getElementById('input')
|
$input = document.getElementById('input')
|
||||||
$input.focus()
|
$input.focus()
|
||||||
$input.onkeydown = (e) =>
|
$input.onkeydown = (e) =>
|
||||||
if 13 == e.keyCode # enter
|
if 13 == e.keyCode # enter
|
||||||
$send.onclick()
|
$send.onclick()
|
||||||
|
snowflake = new Snowflake()
|
||||||
|
snowflake.beginWebRTC()
|
||||||
welcome()
|
welcome()
|
||||||
|
|
||||||
window.onload = init
|
window.onload = init
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue