[tor-commits] [snowflake/master] Initialize snowflake instance with a config

arlo at torproject.org arlo at torproject.org
Thu May 16 16:08:58 UTC 2019


commit 2d8a1690ba833829f086e0bfe9fc450513682d44
Author: Arlo Breault <arlolra at gmail.com>
Date:   Wed May 8 16:13:22 2019 -0400

    Initialize snowflake instance with a config
---
 proxy/Cakefile                   |  2 ++
 proxy/broker.coffee              | 24 +++++++++--------
 proxy/config.coffee              | 26 ++++++++++++++++++
 proxy/init.coffee                | 57 ++++++++++------------------------------
 proxy/proxypair.coffee           | 10 +++----
 proxy/snowflake.coffee           | 25 +++++++++++-------
 proxy/spec/broker.spec.coffee    | 12 ++++-----
 proxy/spec/init.spec.coffee      | 28 ++++++++++++++++++++
 proxy/spec/proxypair.spec.coffee |  5 ++--
 proxy/spec/snowflake.spec.coffee | 43 ++++++++----------------------
 proxy/util.coffee                |  4 +--
 11 files changed, 124 insertions(+), 112 deletions(-)

diff --git a/proxy/Cakefile b/proxy/Cakefile
index 8013dac..e0d610c 100644
--- a/proxy/Cakefile
+++ b/proxy/Cakefile
@@ -11,6 +11,7 @@ FILES = [
   'broker.coffee'
   'ui.coffee'
   'snowflake.coffee'
+  'config.coffee'
 ]
 FILES_SPEC = [
   'spec/util.spec.coffee'
@@ -19,6 +20,7 @@ FILES_SPEC = [
   'spec/proxypair.spec.coffee'
   'spec/snowflake.spec.coffee'
   'spec/websocket.spec.coffee'
+  'spec/init.spec.coffee'
 ]
 FILES_ALL = FILES.concat FILES_SPEC
 OUTFILE = 'snowflake.js'
diff --git a/proxy/broker.coffee b/proxy/broker.coffee
index 981a761..ca8a2e5 100644
--- a/proxy/broker.coffee
+++ b/proxy/broker.coffee
@@ -7,12 +7,14 @@ to get assigned to clients.
 
 # Represents a broker running remotely.
 class Broker
-  @STATUS_OK = 200
-  @STATUS_GONE = 410
-  @STATUS_GATEWAY_TIMEOUT = 504
+  @STATUS:
+    OK: 200
+    GONE: 410
+    GATEWAY_TIMEOUT: 504
 
-  @MESSAGE_TIMEOUT = 'Timed out waiting for a client offer.'
-  @MESSAGE_UNEXPECTED = 'Unexpected status.'
+  @MESSAGE:
+    TIMEOUT: 'Timed out waiting for a client offer.'
+    UNEXPECTED: 'Unexpected status.'
 
   clients: 0
 
@@ -38,15 +40,15 @@ class Broker
       xhr.onreadystatechange = ->
         return if xhr.DONE != xhr.readyState
         switch xhr.status
-          when Broker.STATUS_OK
+          when Broker.STATUS.OK
             fulfill xhr.responseText  # Should contain offer.
-          when Broker.STATUS_GATEWAY_TIMEOUT
-            reject Broker.MESSAGE_TIMEOUT
+          when Broker.STATUS.GATEWAY_TIMEOUT
+            reject Broker.MESSAGE.TIMEOUT
           else
             log 'Broker ERROR: Unexpected ' + xhr.status +
                 ' - ' + xhr.statusText
             snowflake.ui.setStatus ' failure. Please refresh.'
-            reject Broker.MESSAGE_UNEXPECTED
+            reject Broker.MESSAGE.UNEXPECTED
       @_xhr = xhr  # Used by spec to fake async Broker interaction
       @_postRequest id, xhr, 'proxy', id
 
@@ -59,10 +61,10 @@ class Broker
     xhr.onreadystatechange = ->
       return if xhr.DONE != xhr.readyState
       switch xhr.status
-        when Broker.STATUS_OK
+        when Broker.STATUS.OK
           dbg 'Broker: Successfully replied with answer.'
           dbg xhr.responseText
-        when Broker.STATUS_GONE
+        when Broker.STATUS.GONE
           dbg 'Broker: No longer valid to reply with answer.'
         else
           dbg 'Broker ERROR: Unexpected ' + xhr.status +
diff --git a/proxy/config.coffee b/proxy/config.coffee
new file mode 100644
index 0000000..810c79f
--- /dev/null
+++ b/proxy/config.coffee
@@ -0,0 +1,26 @@
+class Config
+  brokerUrl: 'snowflake-broker.bamsoftware.com'
+  relayAddr:
+    host: 'snowflake.bamsoftware.com'
+    port: '443'
+    # Original non-wss relay:
+    # host: '192.81.135.242'
+    # port: 9902
+
+  cookieName: "snowflake-allow"
+
+  # Bytes per second. Set to undefined to disable limit.
+  rateLimitBytes: undefined
+  minRateLimit: 10 * 1024
+  rateLimitHistory: 5.0
+  defaultBrokerPollInterval: 5.0 * 1000
+
+  maxNumClients: 1
+  connectionsPerClient: 1
+
+  # TODO: Different ICE servers.
+  pcConfig = {
+    iceServers: [
+      { urls: ['stun:stun.l.google.com:19302'] }
+    ]
+  }
diff --git a/proxy/init.coffee b/proxy/init.coffee
index 497a54f..48a2c39 100644
--- a/proxy/init.coffee
+++ b/proxy/init.coffee
@@ -1,37 +1,9 @@
-# General snowflake proxy constants.
-# For websocket-specific constants, see websocket.coffee.
-BROKER = 'snowflake-broker.bamsoftware.com'
-RELAY =
-  host: 'snowflake.bamsoftware.com'
-  port: '443'
-  # Original non-wss relay:
-  # host: '192.81.135.242'
-  # port: 9902
-COOKIE_NAME = "snowflake-allow"
 
-# Bytes per second. Set to undefined to disable limit.
-DEFAULT_RATE_LIMIT = undefined
-MIN_RATE_LIMIT = 10 * 1024
-RATE_LIMIT_HISTORY = 5.0
-DEFAULT_BROKER_POLL_INTERVAL = 5.0 * 1000
-
-MAX_NUM_CLIENTS = 1
-CONNECTIONS_PER_CLIENT = 1
-
-# TODO: Different ICE servers.
-config = {
-  iceServers: [
-    { urls: ['stun:stun.l.google.com:19302'] }
-  ]
-}
-
-CONFIRMATION_MESSAGE = 'You\'re currently serving a Tor user via Snowflake.'
+snowflake = null
 
 query = Query.parse(location)
-DEBUG = Params.getBool(query, 'debug', false)
-
-snowflake = null
-silenceNotifications = false
+debug = Params.getBool(query, 'debug', false)
+silenceNotifications = Params.getBool(query, 'silent', false)
 
 # Log to both console and UI if applicable.
 # Requires that the snowflake and UI objects are hooked up in order to
@@ -40,13 +12,17 @@ log = (msg) ->
   console.log 'Snowflake: ' + msg
   snowflake?.ui.log msg
 
-dbg = (msg) -> log msg if DEBUG or (snowflake?.ui instanceof DebugUI)
-
+dbg = (msg) -> log msg if debug or (snowflake?.ui instanceof DebugUI)
 
 ###
 Entry point.
 ###
 init = () ->
+  config = new Config
+
+  if 'off' != query['ratelimit']
+    config.rateLimitBytes = Params.getByteCount(query, 'ratelimit', config.rateLimitBytes)
+
   ui = null
   if (document.getElementById('badge') != null)
     ui = new BadgeUI()
@@ -57,29 +33,24 @@ init = () ->
   else
     ui = new UI()
 
-  rateLimitBytes = undefined
-  if 'off' != query['ratelimit']
-    rateLimitBytes = Params.getByteCount(query, 'ratelimit', DEFAULT_RATE_LIMIT)
-
-  silenceNotifications = Params.getBool(query, 'silent', false)
-  broker = new Broker BROKER
-  snowflake = new Snowflake broker, ui, rateLimitBytes
+  broker = new Broker config.brokerUrl
+  snowflake = new Snowflake config, ui, broker
 
   log '== snowflake proxy =='
-  if Util.snowflakeIsDisabled()
+  if Util.snowflakeIsDisabled(config.cookieName)
     # Do not activate the proxy if any number of conditions are true.
     log 'Currently not active.'
     return
 
   # Otherwise, begin setting up WebRTC and acting as a proxy.
   dbg 'Contacting Broker at ' + broker.url
-  snowflake.setRelayAddr RELAY
+  snowflake.setRelayAddr config.relayAddr
   snowflake.beginWebRTC()
 
 # Notification of closing tab with active proxy.
 window.onbeforeunload = ->
   if !silenceNotifications && Snowflake.MODE.WEBRTC_READY == snowflake.state
-    return CONFIRMATION_MESSAGE
+    return Snowflake.MESSAGE.CONFIRMATION
   null
 
 window.onunload = ->
diff --git a/proxy/proxypair.coffee b/proxy/proxypair.coffee
index bd6850b..879c6b6 100644
--- a/proxy/proxypair.coffee
+++ b/proxy/proxypair.coffee
@@ -24,14 +24,14 @@ class ProxyPair
   - @relayAddr is the destination relay
   - @rateLimit specifies a rate limit on traffic
   ###
-  constructor: (@relayAddr, @rateLimit) ->
+  constructor: (@relayAddr, @rateLimit, @pcConfig) ->
     @id = Util.genSnowflakeID()
     @c2rSchedule = []
     @r2cSchedule = []
 
   # Prepare a WebRTC PeerConnection and await for an SDP offer.
   begin: ->
-    @pc = new PeerConnection config, {
+    @pc = new PeerConnection @pcConfig, {
       optional: [
         { DtlsSrtpKeyAgreement: true }
         { RtpDataChannels: false }
@@ -126,15 +126,13 @@ class ProxyPair
 
   # WebRTC --> websocket
   onClientToRelayMessage: (msg) =>
-    if DEBUG
-      log 'WebRTC --> websocket data: ' + msg.data.byteLength + ' bytes'
+    dbg 'WebRTC --> websocket data: ' + msg.data.byteLength + ' bytes'
     @c2rSchedule.push msg.data
     @flush()
 
   # websocket --> WebRTC
   onRelayToClientMessage: (event) =>
-    if DEBUG
-      log 'websocket --> WebRTC data: ' + event.data.byteLength + ' bytes'
+    dbg 'websocket --> WebRTC data: ' + event.data.byteLength + ' bytes'
     @r2cSchedule.push event.data
     @flush()
 
diff --git a/proxy/snowflake.coffee b/proxy/snowflake.coffee
index fa421ee..66885d9 100644
--- a/proxy/snowflake.coffee
+++ b/proxy/snowflake.coffee
@@ -16,21 +16,26 @@ class Snowflake
   retries:    0
 
   # Janky state machine
-  @MODE =
+  @MODE:
     INIT:              0
     WEBRTC_CONNECTING: 1
     WEBRTC_READY:      2
 
+  @MESSAGE:
+    CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
+
   # Prepare the Snowflake with a Broker (to find clients) and optional UI.
-  constructor: (@broker, @ui, rateLimitBytes) ->
+  constructor: (@config, @ui, @broker) ->
     @state = Snowflake.MODE.INIT
     @proxyPairs = []
 
-    if undefined == rateLimitBytes
+    if undefined == @config.rateLimitBytes
       @rateLimit = new DummyRateLimit()
     else
-      @rateLimit = new BucketRateLimit(rateLimitBytes * RATE_LIMIT_HISTORY,
-                                       RATE_LIMIT_HISTORY)
+      @rateLimit = new BucketRateLimit(
+        @config.rateLimitBytes * @config.rateLimitHistory,
+        @config.rateLimitHistory
+      )
     @retries = 0
 
   # Set the target relay address spec, which is expected to be websocket.
@@ -45,7 +50,7 @@ class Snowflake
   # process. |pollBroker| automatically arranges signalling.
   beginWebRTC: ->
     @state = Snowflake.MODE.WEBRTC_CONNECTING
-    for i in [1..CONNECTIONS_PER_CLIENT]
+    for i in [1.. at config.connectionsPerClient]
       @makeProxyPair @relayAddr
     log 'ProxyPair Slots: ' + @proxyPairs.length
     log 'Snowflake IDs: ' + (@proxyPairs.map (p) -> p.id).join ' | '
@@ -77,9 +82,9 @@ class Snowflake
       recv = @broker.getClientOffer pair.id
       recv.then (desc) =>
         @receiveOffer pair, desc
-        countdown('Serving 1 new client.', DEFAULT_BROKER_POLL_INTERVAL / 1000)
-      , (err) ->
-        countdown(err, DEFAULT_BROKER_POLL_INTERVAL / 1000)
+        countdown('Serving 1 new client.', @config.defaultBrokerPollInterval / 1000)
+      , (err) =>
+        countdown(err, @config.defaultBrokerPollInterval / 1000)
       @retries++
 
     findClients()
@@ -111,7 +116,7 @@ class Snowflake
     .catch fail
 
   makeProxyPair: (relay) ->
-    pair = new ProxyPair relay, @rateLimit
+    pair = new ProxyPair relay, @rateLimit, @config.pcConfig
     @proxyPairs.push pair
     pair.onCleanup = (event) =>
       # Delete from the list of active proxy pairs.
diff --git a/proxy/spec/broker.spec.coffee b/proxy/spec/broker.spec.coffee
index 3532fe1..2b1d2bd 100644
--- a/proxy/spec/broker.spec.coffee
+++ b/proxy/spec/broker.spec.coffee
@@ -25,7 +25,7 @@ describe 'Broker', ->
       # fake successful request and response from broker.
       spyOn(b, '_postRequest').and.callFake ->
         b._xhr.readyState = b._xhr.DONE
-        b._xhr.status = Broker.STATUS_OK
+        b._xhr.status = Broker.STATUS.OK
         b._xhr.responseText = 'fake offer'
         b._xhr.onreadystatechange()
       poll = b.getClientOffer()
@@ -35,7 +35,7 @@ describe 'Broker', ->
         expect(desc).toEqual 'fake offer'
         done()
       .catch ->
-        fail 'should not reject on Broker.STATUS_OK'
+        fail 'should not reject on Broker.STATUS.OK'
         done()
 
     it 'rejects if the broker timed-out', (done) ->
@@ -43,16 +43,16 @@ describe 'Broker', ->
       # fake timed-out request from broker
       spyOn(b, '_postRequest').and.callFake ->
         b._xhr.readyState = b._xhr.DONE
-        b._xhr.status = Broker.STATUS_GATEWAY_TIMEOUT
+        b._xhr.status = Broker.STATUS.GATEWAY_TIMEOUT
         b._xhr.onreadystatechange()
       poll = b.getClientOffer()
       expect(poll).not.toBeNull()
       expect(b._postRequest).toHaveBeenCalled()
       poll.then (desc) ->
-        fail 'should not fulfill on GATEWAY_TIMEOUT'
+        fail 'should not fulfill on Broker.STATUS.GATEWAY_TIMEOUT'
         done()
       , (err) ->
-        expect(err).toBe Broker.MESSAGE_TIMEOUT
+        expect(err).toBe Broker.MESSAGE.TIMEOUT
         done()
 
     it 'rejects on any other status', (done) ->
@@ -69,7 +69,7 @@ describe 'Broker', ->
         fail 'should not fulfill on non-OK status'
         done()
       , (err) ->
-        expect(err).toBe Broker.MESSAGE_UNEXPECTED
+        expect(err).toBe Broker.MESSAGE.UNEXPECTED
         expect(b._xhr.status).toBe 1337
         done()
 
diff --git a/proxy/spec/init.spec.coffee b/proxy/spec/init.spec.coffee
new file mode 100644
index 0000000..4134a22
--- /dev/null
+++ b/proxy/spec/init.spec.coffee
@@ -0,0 +1,28 @@
+
+# Fake snowflake to interact with
+snowflake =
+  ui: new UI
+  broker:
+    sendAnswer: ->
+  state: Snowflake.MODE.INIT
+
+describe 'Init', ->
+
+  it 'gives a dialog when closing, only while active', ->
+    silenceNotifications = false
+    snowflake.state = Snowflake.MODE.WEBRTC_READY
+    msg = window.onbeforeunload()
+    expect(snowflake.state).toBe Snowflake.MODE.WEBRTC_READY
+    expect(msg).toBe Snowflake.MESSAGE.CONFIRMATION
+
+    snowflake.state = Snowflake.MODE.INIT
+    msg = window.onbeforeunload()
+    expect(snowflake.state).toBe Snowflake.MODE.INIT
+    expect(msg).toBe null
+
+  it 'does not give a dialog when silent flag is on', ->
+    silenceNotifications = true
+    snowflake.state = Snowflake.MODE.WEBRTC_READY
+    msg = window.onbeforeunload()
+    expect(snowflake.state).toBe Snowflake.MODE.WEBRTC_READY
+    expect(msg).toBe null
diff --git a/proxy/spec/proxypair.spec.coffee b/proxy/spec/proxypair.spec.coffee
index 566c6f1..87aeb55 100644
--- a/proxy/spec/proxypair.spec.coffee
+++ b/proxy/spec/proxypair.spec.coffee
@@ -24,10 +24,11 @@ arrayMatching = (sample) -> {
 
 describe 'ProxyPair', ->
   fakeRelay = Parse.address '0.0.0.0:12345'
-  rateLimit = new DummyRateLimit()
+  rateLimit = new DummyRateLimit
+  config = new Config
   destination = []
   # Using the mock PeerConnection definition from spec/snowflake.spec.coffee.
-  pp = new ProxyPair(fakeRelay, rateLimit)
+  pp = new ProxyPair(fakeRelay, rateLimit, config.pcConfig)
 
   beforeEach ->
     pp.begin()
diff --git a/proxy/spec/snowflake.spec.coffee b/proxy/spec/snowflake.spec.coffee
index a619a06..2c87204 100644
--- a/proxy/spec/snowflake.spec.coffee
+++ b/proxy/spec/snowflake.spec.coffee
@@ -15,40 +15,38 @@ class WebSocket
   constructor: ->
     @bufferedAmount = 0
   send: (data) ->
+
 log = ->
-fakeUI = new UI()
+
+config = new Config
+ui = new UI
+
 class FakeBroker
   getClientOffer: -> new Promise((F,R) -> {})
-# Fake snowflake to interact with
-snowflake =
-  ui: fakeUI
-  broker:
-    sendAnswer: ->
-  state: Snowflake.MODE.INIT
 
 describe 'Snowflake', ->
 
   it 'constructs correctly', ->
-    s = new Snowflake({ fake: 'broker' }, fakeUI)
+    s = new Snowflake(config, ui, { fake: 'broker' })
     expect(s.rateLimit).not.toBeNull()
     expect(s.broker).toEqual { fake: 'broker' }
     expect(s.ui).not.toBeNull()
     expect(s.retries).toBe 0
 
   it 'sets relay address correctly', ->
-    s = new Snowflake(null, fakeUI)
+    s = new Snowflake(config, ui, null)
     s.setRelayAddr 'foo'
     expect(s.relayAddr).toEqual 'foo'
 
   it 'initalizes WebRTC connection', ->
-    s = new Snowflake(new FakeBroker(), fakeUI)
+    s = new Snowflake(config, ui, new FakeBroker())
     spyOn(s.broker, 'getClientOffer').and.callThrough()
     s.beginWebRTC()
     expect(s.retries).toBe 1
     expect(s.broker.getClientOffer).toHaveBeenCalled()
 
   it 'receives SDP offer and sends answer', ->
-    s = new Snowflake(new FakeBroker(), fakeUI)
+    s = new Snowflake(config, ui, new FakeBroker())
     pair = { receiveWebRTCOffer: -> }
     spyOn(pair, 'receiveWebRTCOffer').and.returnValue true
     spyOn(s, 'sendAnswer')
@@ -56,7 +54,7 @@ describe 'Snowflake', ->
     expect(s.sendAnswer).toHaveBeenCalled()
 
   it 'does not send answer when receiving invalid offer', ->
-    s = new Snowflake(new FakeBroker(), fakeUI)
+    s = new Snowflake(config, ui, new FakeBroker())
     pair = { receiveWebRTCOffer: -> }
     spyOn(pair, 'receiveWebRTCOffer').and.returnValue false
     spyOn(s, 'sendAnswer')
@@ -64,25 +62,6 @@ describe 'Snowflake', ->
     expect(s.sendAnswer).not.toHaveBeenCalled()
 
   it 'can make a proxypair', ->
-    s = new Snowflake(new FakeBroker(), fakeUI)
+    s = new Snowflake(config, ui, new FakeBroker())
     s.makeProxyPair()
     expect(s.proxyPairs.length).toBe 1
-
-  it 'gives a dialog when closing, only while active', ->
-    silenceNotifications = false
-    snowflake.state = Snowflake.MODE.WEBRTC_READY
-    msg = window.onbeforeunload()
-    expect(snowflake.state).toBe Snowflake.MODE.WEBRTC_READY
-    expect(msg).toBe CONFIRMATION_MESSAGE
-
-    snowflake.state = Snowflake.MODE.INIT
-    msg = window.onbeforeunload()
-    expect(snowflake.state).toBe Snowflake.MODE.INIT
-    expect(msg).toBe null
-
-  it 'does not give a dialog when silent flag is on', ->
-    silenceNotifications = true
-    snowflake.state = Snowflake.MODE.WEBRTC_READY
-    msg = window.onbeforeunload()
-    expect(snowflake.state).toBe Snowflake.MODE.WEBRTC_READY
-    expect(msg).toBe null
diff --git a/proxy/util.coffee b/proxy/util.coffee
index 15b4152..b8057fb 100644
--- a/proxy/util.coffee
+++ b/proxy/util.coffee
@@ -22,10 +22,10 @@ class Util
   @genSnowflakeID: ->
     Math.random().toString(36).substring(2)
 
-  @snowflakeIsDisabled = ->
+  @snowflakeIsDisabled = (cookieName) ->
     cookies = Parse.cookie document.cookie
     # Do nothing if snowflake has not been opted in by user.
-    if cookies[COOKIE_NAME] != '1'
+    if cookies[cookieName] != '1'
       log 'Not opted-in. Please click the badge to change options.'
       return true
     # Also do nothing if running in Tor Browser.





More information about the tor-commits mailing list