mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
per-proxypair Snowflake ID generation instead of just one for the Broker (#11)
This commit is contained in:
parent
422717f36f
commit
b1e76420bd
5 changed files with 44 additions and 23 deletions
|
@ -12,21 +12,16 @@ STATUS_GATEWAY_TIMEOUT = 504
|
||||||
MESSAGE_TIMEOUT = 'Timed out waiting for a client offer.'
|
MESSAGE_TIMEOUT = 'Timed out waiting for a client offer.'
|
||||||
MESSAGE_UNEXPECTED = 'Unexpected status.'
|
MESSAGE_UNEXPECTED = 'Unexpected status.'
|
||||||
|
|
||||||
genSnowflakeID = ->
|
|
||||||
Math.random().toString(36).substring(2)
|
|
||||||
|
|
||||||
# Represents a broker running remotely.
|
# Represents a broker running remotely.
|
||||||
class Broker
|
class Broker
|
||||||
|
|
||||||
clients: 0
|
clients: 0
|
||||||
id: null
|
|
||||||
|
|
||||||
# When interacting with the Broker, snowflake must generate a unique session
|
# When interacting with the Broker, snowflake must generate a unique session
|
||||||
# ID so the Broker can keep track of which signalling channel it's speaking
|
# ID so the Broker can keep track of which signalling channel it's speaking
|
||||||
# to.
|
# to.
|
||||||
constructor: (@url) ->
|
constructor: (@url) ->
|
||||||
@clients = 0
|
@clients = 0
|
||||||
@id = genSnowflakeID()
|
|
||||||
# Ensure url has the right protocol + trailing slash.
|
# Ensure url has the right protocol + trailing slash.
|
||||||
@url = 'http://' + @url if 0 == @url.indexOf('localhost', 0)
|
@url = 'http://' + @url if 0 == @url.indexOf('localhost', 0)
|
||||||
@url = 'https://' + @url if 0 != @url.indexOf('http', 0)
|
@url = 'https://' + @url if 0 != @url.indexOf('http', 0)
|
||||||
|
@ -37,7 +32,7 @@ class Broker
|
||||||
# waits for a response containing some client offer that the Broker chooses
|
# waits for a response containing some client offer that the Broker chooses
|
||||||
# for this proxy..
|
# for this proxy..
|
||||||
# TODO: Actually support multiple clients.
|
# TODO: Actually support multiple clients.
|
||||||
getClientOffer: =>
|
getClientOffer: (id) =>
|
||||||
new Promise (fulfill, reject) =>
|
new Promise (fulfill, reject) =>
|
||||||
xhr = new XMLHttpRequest()
|
xhr = new XMLHttpRequest()
|
||||||
xhr.onreadystatechange = ->
|
xhr.onreadystatechange = ->
|
||||||
|
@ -53,12 +48,12 @@ class Broker
|
||||||
snowflake.ui.setStatus ' failure. Please refresh.'
|
snowflake.ui.setStatus ' failure. Please refresh.'
|
||||||
reject MESSAGE_UNEXPECTED
|
reject MESSAGE_UNEXPECTED
|
||||||
@_xhr = xhr # Used by spec to fake async Broker interaction
|
@_xhr = xhr # Used by spec to fake async Broker interaction
|
||||||
@_postRequest xhr, 'proxy', @id
|
@_postRequest id, xhr, 'proxy', id
|
||||||
|
|
||||||
# Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
|
# Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
|
||||||
# Sends it back to the broker, which passes it to back to the original client.
|
# Sends it back to the broker, which passes it to back to the original client.
|
||||||
sendAnswer: (answer) ->
|
sendAnswer: (id, answer) ->
|
||||||
dbg @id + ' - Sending answer back to broker...\n'
|
dbg id + ' - Sending answer back to broker...\n'
|
||||||
dbg answer.sdp
|
dbg answer.sdp
|
||||||
xhr = new XMLHttpRequest()
|
xhr = new XMLHttpRequest()
|
||||||
xhr.onreadystatechange = ->
|
xhr.onreadystatechange = ->
|
||||||
|
@ -73,14 +68,14 @@ class Broker
|
||||||
dbg 'Broker ERROR: Unexpected ' + xhr.status +
|
dbg 'Broker ERROR: Unexpected ' + xhr.status +
|
||||||
' - ' + xhr.statusText
|
' - ' + xhr.statusText
|
||||||
snowflake.ui.setStatus ' failure. Please refresh.'
|
snowflake.ui.setStatus ' failure. Please refresh.'
|
||||||
@_postRequest xhr, 'answer', JSON.stringify(answer)
|
@_postRequest id, xhr, 'answer', JSON.stringify(answer)
|
||||||
|
|
||||||
# urlSuffix for the broker is different depending on what action
|
# urlSuffix for the broker is different depending on what action
|
||||||
# is desired.
|
# is desired.
|
||||||
_postRequest: (xhr, urlSuffix, payload) =>
|
_postRequest: (id, xhr, urlSuffix, payload) =>
|
||||||
try
|
try
|
||||||
xhr.open 'POST', @url + urlSuffix
|
xhr.open 'POST', @url + urlSuffix
|
||||||
xhr.setRequestHeader('X-Session-ID', @id)
|
xhr.setRequestHeader('X-Session-ID', id)
|
||||||
catch err
|
catch err
|
||||||
###
|
###
|
||||||
An exception happens here when, for example, NoScript allows the domain
|
An exception happens here when, for example, NoScript allows the domain
|
||||||
|
|
|
@ -3,6 +3,8 @@ Represents a single:
|
||||||
|
|
||||||
client <-- webrtc --> snowflake <-- websocket --> relay
|
client <-- webrtc --> snowflake <-- websocket --> relay
|
||||||
|
|
||||||
|
Every ProxyPair has a Snowflake ID, which is necessary when responding to the
|
||||||
|
Broker with an WebRTC answer.
|
||||||
###
|
###
|
||||||
|
|
||||||
class ProxyPair
|
class ProxyPair
|
||||||
|
@ -16,10 +18,12 @@ class ProxyPair
|
||||||
running: true
|
running: true
|
||||||
active: false # Whether serving a client.
|
active: false # Whether serving a client.
|
||||||
flush_timeout_id: null
|
flush_timeout_id: null
|
||||||
onCleanup: null
|
onCleanup: null
|
||||||
|
id: null
|
||||||
|
|
||||||
constructor: (@clientAddr, @relayAddr, @rateLimit) ->
|
constructor: (@clientAddr, @relayAddr, @rateLimit) ->
|
||||||
@active = false
|
@active = false
|
||||||
|
@id = genSnowflakeID()
|
||||||
|
|
||||||
# Prepare a WebRTC PeerConnection and await for an SDP offer.
|
# Prepare a WebRTC PeerConnection and await for an SDP offer.
|
||||||
begin: ->
|
begin: ->
|
||||||
|
@ -38,7 +42,7 @@ class ProxyPair
|
||||||
if COPY_PASTE_ENABLED
|
if COPY_PASTE_ENABLED
|
||||||
Signalling.send @pc.localDescription
|
Signalling.send @pc.localDescription
|
||||||
else
|
else
|
||||||
snowflake.broker.sendAnswer @pc.localDescription
|
snowflake.broker.sendAnswer @id, @pc.localDescription
|
||||||
# OnDataChannel triggered remotely from the client when connection succeeds.
|
# OnDataChannel triggered remotely from the client when connection succeeds.
|
||||||
@pc.ondatachannel = (dc) =>
|
@pc.ondatachannel = (dc) =>
|
||||||
channel = dc.channel
|
channel = dc.channel
|
||||||
|
|
|
@ -84,6 +84,9 @@ class Snowflake
|
||||||
for i in [1..CONNECTIONS_PER_CLIENT]
|
for i in [1..CONNECTIONS_PER_CLIENT]
|
||||||
@makeProxyPair @relayAddr
|
@makeProxyPair @relayAddr
|
||||||
return if COPY_PASTE_ENABLED
|
return if COPY_PASTE_ENABLED
|
||||||
|
log 'ProxyPair Slots: ' + @proxyPairs.length
|
||||||
|
log 'Snowflake IDs: ' + (@proxyPairs.map (p) -> p.id).join ' | '
|
||||||
|
|
||||||
timer = null
|
timer = null
|
||||||
# Temporary countdown.
|
# Temporary countdown.
|
||||||
countdown = (msg, sec) =>
|
countdown = (msg, sec) =>
|
||||||
|
@ -99,22 +102,37 @@ class Snowflake
|
||||||
msg = 'polling for client... '
|
msg = 'polling for client... '
|
||||||
msg += '[retries: ' + @retries + ']' if @retries > 0
|
msg += '[retries: ' + @retries + ']' if @retries > 0
|
||||||
@ui.setStatus msg
|
@ui.setStatus msg
|
||||||
recv = @broker.getClientOffer()
|
# Pick an available ProxyPair to poll with.
|
||||||
@retries++
|
pair = @nextAvailableProxyPair()
|
||||||
|
if !pair
|
||||||
|
log 'No more available ProxyPair slots.'
|
||||||
|
countdown(err, DEFAULT_BROKER_POLL_INTERVAL / 1000)
|
||||||
|
return
|
||||||
|
log 'Polling for ' + pair.id
|
||||||
|
recv = @broker.getClientOffer pair.id
|
||||||
recv.then (desc) =>
|
recv.then (desc) =>
|
||||||
offer = JSON.parse desc
|
offer = JSON.parse desc
|
||||||
dbg 'Received:\n\n' + offer.sdp + '\n'
|
dbg 'Received:\n\n' + offer.sdp + '\n'
|
||||||
@receiveOffer offer
|
console.log desc
|
||||||
|
sdp = new RTCSessionDescription offer
|
||||||
|
# @receiveOffer offer
|
||||||
|
if pair.receiveWebRTCOffer sdp
|
||||||
|
@sendAnswer pair if 'offer' == sdp.type
|
||||||
, (err) ->
|
, (err) ->
|
||||||
countdown(err, DEFAULT_BROKER_POLL_INTERVAL / 1000)
|
countdown(err, DEFAULT_BROKER_POLL_INTERVAL / 1000)
|
||||||
|
@retries++
|
||||||
|
|
||||||
findClients()
|
findClients()
|
||||||
|
|
||||||
|
# Returns the first ProxyPair that's available to connect.
|
||||||
|
nextAvailableProxyPair: ->
|
||||||
|
return @proxyPairs.find (pp, i, arr) -> return !pp.active
|
||||||
|
|
||||||
# Receive an SDP offer from some client assigned by the Broker,
|
# Receive an SDP offer from some client assigned by the Broker,
|
||||||
|
# TODO: remove
|
||||||
receiveOffer: (desc) =>
|
receiveOffer: (desc) =>
|
||||||
sdp = new RTCSessionDescription desc
|
sdp = new RTCSessionDescription desc
|
||||||
# Use the first proxyPair that's available.
|
pair = @nextAvailableProxyPair()
|
||||||
pair = @proxyPairs.find (pp, i, arr) -> return !pp.active
|
|
||||||
if pair.receiveWebRTCOffer sdp
|
if pair.receiveWebRTCOffer sdp
|
||||||
@sendAnswer pair if 'offer' == sdp.type
|
@sendAnswer pair if 'offer' == sdp.type
|
||||||
|
|
||||||
|
@ -190,7 +208,7 @@ init = ->
|
||||||
broker = new Broker brokerUrl
|
broker = new Broker brokerUrl
|
||||||
snowflake = new Snowflake broker, ui
|
snowflake = new Snowflake broker, ui
|
||||||
|
|
||||||
dbg 'Contacting Broker at ' + broker.url + '\nSnowflake ID: ' + broker.id
|
dbg 'Contacting Broker at ' + broker.url
|
||||||
log '== snowflake proxy =='
|
log '== snowflake proxy =='
|
||||||
log 'Copy-Paste mode detected.' if COPY_PASTE_ENABLED
|
log 'Copy-Paste mode detected.' if COPY_PASTE_ENABLED
|
||||||
|
|
||||||
|
|
|
@ -76,9 +76,9 @@ describe 'Broker', ->
|
||||||
it 'responds to the broker with answer', ->
|
it 'responds to the broker with answer', ->
|
||||||
b = new Broker 'fake'
|
b = new Broker 'fake'
|
||||||
spyOn(b, '_postRequest')
|
spyOn(b, '_postRequest')
|
||||||
b.sendAnswer 123
|
b.sendAnswer 'fake id', 123
|
||||||
expect(b._postRequest).toHaveBeenCalledWith(
|
expect(b._postRequest).toHaveBeenCalledWith(
|
||||||
jasmine.any(Object), 'answer', '123')
|
'fake id', jasmine.any(Object), 'answer', '123')
|
||||||
|
|
||||||
it 'POST XMLHttpRequests to the broker', ->
|
it 'POST XMLHttpRequests to the broker', ->
|
||||||
b = new Broker 'fake'
|
b = new Broker 'fake'
|
||||||
|
@ -86,7 +86,7 @@ describe 'Broker', ->
|
||||||
spyOn(b._xhr, 'open')
|
spyOn(b._xhr, 'open')
|
||||||
spyOn(b._xhr, 'setRequestHeader')
|
spyOn(b._xhr, 'setRequestHeader')
|
||||||
spyOn(b._xhr, 'send')
|
spyOn(b._xhr, 'send')
|
||||||
b._postRequest b._xhr, 'test', 'data'
|
b._postRequest 0, b._xhr, 'test', 'data'
|
||||||
expect(b._xhr.open).toHaveBeenCalled()
|
expect(b._xhr.open).toHaveBeenCalled()
|
||||||
expect(b._xhr.setRequestHeader).toHaveBeenCalled()
|
expect(b._xhr.setRequestHeader).toHaveBeenCalled()
|
||||||
expect(b._xhr.send).toHaveBeenCalled()
|
expect(b._xhr.send).toHaveBeenCalled()
|
||||||
|
|
|
@ -5,6 +5,10 @@ Contains helpers for parsing query strings and other utilities.
|
||||||
###
|
###
|
||||||
|
|
||||||
|
|
||||||
|
genSnowflakeID = ->
|
||||||
|
Math.random().toString(36).substring(2)
|
||||||
|
|
||||||
|
|
||||||
Query =
|
Query =
|
||||||
###
|
###
|
||||||
Parse a URL query string or application/x-www-form-urlencoded body. The
|
Parse a URL query string or application/x-www-form-urlencoded body. The
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue