[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [snowflake/master] answer successfully roundtripped back from snowflake proxy through broker to client (#1)
commit c9013b2f80aeda0aa6e86d984f1d7e9b89aabd46
Author: Serene Han <keroserene+git@xxxxxxxxx>
Date: Thu Jan 21 13:02:46 2016 -0800
answer successfully roundtripped back from snowflake proxy through broker to client (#1)
---
broker/config.go | 16 ------
broker/snowflake-broker.go | 123 +++++++++++++++++++++++++++++++-------------
proxy/broker.coffee | 42 ++++++++++++---
proxy/proxypair.coffee | 5 +-
proxy/snowflake.coffee | 11 +---
5 files changed, 127 insertions(+), 70 deletions(-)
diff --git a/broker/config.go b/broker/config.go
deleted file mode 100644
index 7e250aa..0000000
--- a/broker/config.go
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
-This is the server-side code that runs on Google App Engine for the
-"appspot" registration method.
-
-See doc/appspot-howto.txt for more details about setting up an
-application, and advice on running one.
-
-To upload a new version:
-$ torify ~/go_appengine/appcfg.py --no_cookies -A $YOUR_APP_ID update .
-*/
-package snowflake_broker
-
-// host:port/basepath of the broker you want to register with
-// for example, fp-broker.org or example.com:12345/broker
-// https:// and /reg/ will be prepended and appended respectively.
-const SNOWFLAKE_BROKER = ""
diff --git a/broker/snowflake-broker.go b/broker/snowflake-broker.go
index 1d26eb1..c2bde7b 100644
--- a/broker/snowflake-broker.go
+++ b/broker/snowflake-broker.go
@@ -1,3 +1,12 @@
+/*
+Broker acts as the HTTP signaling channel.
+It matches clients and snowflake proxies by passing corresponding
+SessionDescriptions in order to negotiate a WebRTC connection.
+
+TODO(serene): This code is currently the absolute minimum required to
+cause a successful negotiation.
+It's otherwise very unsafe and problematic, and needs quite some work...
+*/
package snowflake_broker
import (
@@ -8,8 +17,6 @@ import (
"net"
"net/http"
"time"
- // "appengine"
- // "appengine/urlfetch"
)
// This is an intermediate step - a basic hardcoded appengine rendezvous
@@ -23,10 +30,11 @@ import (
// var snowflakes []chan []byte
type Snowflake struct {
- id string
- sigChannel chan []byte
- clients int
- index int
+ id string
+ offerChannel chan []byte
+ answerChannel chan []byte
+ clients int
+ index int
}
// Implements heap.Interface, and holds Snowflakes.
@@ -63,14 +71,17 @@ func (sh *SnowflakeHeap) Pop() interface{} {
}
var snowflakes *SnowflakeHeap
+var snowflakeMap map[string]*Snowflake
// Create and add a Snowflake to the heap.
func AddSnowflake(id string) *Snowflake {
snowflake := new(Snowflake)
snowflake.id = id
snowflake.clients = 0
- snowflake.sigChannel = make(chan []byte)
+ snowflake.offerChannel = make(chan []byte)
+ snowflake.answerChannel = make(chan []byte)
heap.Push(snowflakes, snowflake)
+ snowflakeMap[id] = snowflake
return snowflake
}
@@ -97,65 +108,108 @@ func clientHandler(w http.ResponseWriter, r *http.Request) {
offer, err := ioutil.ReadAll(r.Body)
if nil != err {
log.Println("Invalid data.")
+ w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
- // Pop the most available snowflake proxy, and pass the offer to it.
- // TODO: Make this much better.
+ w.Header().Set("Access-Control-Allow-Headers", "X-Session-ID")
+
+ // Find the most available snowflake proxy, and pass the offer to it.
+ // TODO: Needs improvement.
snowflake := heap.Pop(snowflakes).(*Snowflake)
if nil == snowflake {
- // w.Header().Set("Status", http.StatusServiceUnavailable)
- w.Write([]byte("no snowflake proxies available"))
+ w.Header().Set("Status", http.StatusServiceUnavailable)
+ // w.Write([]byte("no snowflake proxies available"))
return
}
- // snowflakes = snowflakes[1:]
- snowflake.sigChannel <- offer
- w.Write([]byte("sent offer to proxy!"))
- // TODO: Get browser snowflake to talkto this appengine instance
- // so it can reply with an answer, and not just the offer again :)
- // TODO: Real broker which matches clients and snowflake proxies.
- w.Write(offer)
+ snowflake.offerChannel <- offer
+
+ // Wait for the answer to be returned on the channel.
+ select {
+ case answer := <-snowflake.answerChannel:
+ log.Println("Retrieving answer")
+ w.Write(answer)
+ // Only remove from the snowflake map once the answer is set.
+ delete(snowflakeMap, snowflake.id)
+
+ case <-time.After(time.Second * 10):
+ w.WriteHeader(http.StatusGatewayTimeout)
+ w.Write([]byte("timed out waiting for answer!"))
+ }
}
/*
-A snowflake browser proxy requests a client from the Broker.
+For snowflake proxies to request a client from the Broker.
*/
func proxyHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Session-ID")
+ // For CORS preflight.
+ if "OPTIONS" == r.Method {
+ return
+ }
+
+ id := r.Header.Get("X-Session-ID")
body, err := ioutil.ReadAll(r.Body)
if nil != err {
log.Println("Invalid data.")
+ w.WriteHeader(http.StatusBadRequest)
return
}
- w.Header().Set("Access-Control-Allow-Origin", "*")
- snowflakeSession := body
- log.Println("Received snowflake: ", snowflakeSession)
- snowflake := AddSnowflake(string(snowflakeSession))
+ if string(body) != id { // Mismatched IDs!
+ w.WriteHeader(http.StatusBadRequest)
+ }
+ // Maybe confirm that X-Session-ID is the same.
+ log.Println("Received snowflake: ", id)
+ snowflake := AddSnowflake(id)
+
+ // Wait for a client to avail an offer to the snowflake, or timeout
+ // and ask the snowflake to poll later.
select {
- case offer := <-snowflake.sigChannel:
+ case offer := <-snowflake.offerChannel:
log.Println("Passing client offer to snowflake.")
w.Write(offer)
+
case <-time.After(time.Second * 10):
- // s := fmt.Sprintf("%d snowflakes left.", snowflakes.Len())
- // w.Write([]byte("timed out. " + s))
- // w.Header().Set("Status", http.StatusRequestTimeout)
- w.WriteHeader(http.StatusGatewayTimeout)
heap.Remove(snowflakes, snowflake.index)
+ w.WriteHeader(http.StatusGatewayTimeout)
}
}
-func reflectHandler(w http.ResponseWriter, r *http.Request) {
+/*
+Expects snowflake proxes which have previously successfully received
+an offer from proxyHandler to respond with an answer in an HTTP POST,
+which the broker will pass back to the original client.
+*/
+func answerHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Headers", "X-Session-ID")
+ // For CORS preflight.
+ if "OPTIONS" == r.Method {
+ return
+ }
+
+ id := r.Header.Get("X-Session-ID")
+ snowflake, ok := snowflakeMap[id]
+ if !ok || nil == snowflake {
+ // The snowflake took too long to respond with an answer,
+ // and the designated client is no longer around / recognized by the Broker.
+ w.WriteHeader(http.StatusGone)
+ return
+ }
body, err := ioutil.ReadAll(r.Body)
if nil != err {
log.Println("Invalid data.")
+ w.WriteHeader(http.StatusBadRequest)
return
}
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Write(body)
+ log.Println("Received answer: ", body)
+ snowflake.answerChannel <- body
}
func init() {
- // snowflakes = make([]chan []byte, 0)
snowflakes = new(SnowflakeHeap)
+ snowflakeMap = make(map[string]*Snowflake)
heap.Init(snowflakes)
http.HandleFunc("/robots.txt", robotsTxtHandler)
@@ -163,8 +217,5 @@ func init() {
http.HandleFunc("/client", clientHandler)
http.HandleFunc("/proxy", proxyHandler)
- http.HandleFunc("/reflect", reflectHandler)
- // if SNOWFLAKE_BROKER == "" {
- // panic("SNOWFLAKE_BROKER empty; did you forget to edit config.go?")
- // }
+ http.HandleFunc("/answer", answerHandler)
}
diff --git a/proxy/broker.coffee b/proxy/broker.coffee
index 688d45d..91a81d2 100644
--- a/proxy/broker.coffee
+++ b/proxy/broker.coffee
@@ -6,19 +6,25 @@ to get assigned to clients.
###
STATUS_OK = 200
+STATUS_GONE = 410
STATUS_GATEWAY_TIMEOUT = 504
+genSnowflakeID = ->
+ Math.random().toString(36).substring(2)
+
# Represents a broker running remotely.
class Broker
clients: 0
+ id: null
# 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
# to.
constructor: (@url) ->
- log 'Using Broker at ' + @url
- clients = 0
+ @clients = 0
+ @id = genSnowflakeID()
+ log 'Contacting Broker at ' + @url + '\nSnowflake ID: ' + @id
# Snowflake registers with the broker using an HTTP POST request, and expects
# a response from the broker containing some client offer.
@@ -27,7 +33,8 @@ class Broker
new Promise (fulfill, reject) =>
xhr = new XMLHttpRequest()
try
- xhr.open 'POST', @url
+ xhr.open 'POST', @url + 'proxy'
+ xhr.setRequestHeader('X-Session-ID', @id)
catch err
###
An exception happens here when, for example, NoScript allows the domain
@@ -47,10 +54,29 @@ class Broker
else
log 'Broker ERROR: Unexpected ' + xhr.status +
' - ' + xhr.statusText
-
- xhr.send 'snowflake-testing'
- log "Broker: polling for client offer..."
+ xhr.send @id
+ log @id + " - polling for client offer..."
sendAnswer: (answer) ->
- log 'Sending answer to broker.'
- log answer
+ log @id + ' - Sending answer back to broker...\n'
+ log answer.sdp
+ xhr = new XMLHttpRequest()
+ try
+ xhr.open 'POST', @url + 'answer'
+ xhr.setRequestHeader('X-Session-ID', @id)
+ catch err
+ log 'Broker: exception while connecting: ' + err.message
+ return
+ xhr.onreadystatechange = ->
+ return if xhr.DONE != xhr.readyState
+ log xhr
+ switch xhr.status
+ when STATUS_OK
+ log 'Broker: Successfully replied with answer.'
+ log xhr.responseText
+ when STATUS_GONE
+ log 'Broker: No longer valid to reply with answer.'
+ else
+ log 'Broker ERROR: Unexpected ' + xhr.status +
+ ' - ' + xhr.statusText
+ xhr.send JSON.stringify(answer)
diff --git a/proxy/proxypair.coffee b/proxy/proxypair.coffee
index a27ac92..f78e9f6 100644
--- a/proxy/proxypair.coffee
+++ b/proxy/proxypair.coffee
@@ -32,7 +32,10 @@ class ProxyPair
# TODO: Use a promise.all to tell Snowflake about all offers at once,
# once multiple proxypairs are supported.
log 'Finished gathering ICE candidates.'
- Signalling.send @pc.localDescription
+ if COPY_PASTE_ENABLED
+ Signalling.send @pc.localDescription
+ else
+ snowflake.broker.sendAnswer @pc.localDescription
# OnDataChannel triggered remotely from the client when connection succeeds.
@pc.ondatachannel = (dc) =>
console.log dc
diff --git a/proxy/snowflake.coffee b/proxy/snowflake.coffee
index 646b9b4..cc34f81 100644
--- a/proxy/snowflake.coffee
+++ b/proxy/snowflake.coffee
@@ -8,7 +8,7 @@ Assume that the webrtc client plugin is always the offerer, in which case
this must always act as the answerer.
###
DEFAULT_WEBSOCKET = '192.81.135.242:9901'
-DEFAULT_BROKER = 'https://snowflake-reg.appspot.com/proxy'
+DEFAULT_BROKER = 'https://snowflake-reg.appspot.com/'
COPY_PASTE_ENABLED = false
DEFAULT_PORTS =
http: 80
@@ -104,8 +104,8 @@ class Snowflake
poll = =>
recv = broker.getClientOffer()
recv.then((desc) =>
- log 'Received:\n\n' + desc + '\n'
offer = JSON.parse desc
+ log 'Received:\n\n' + offer.sdp + '\n'
@receiveOffer offer
, (err) ->
log err
@@ -113,13 +113,6 @@ class Snowflake
)
poll()
- # if @proxyPairs.length >= MAX_NUM_CLIENTS * CONNECTIONS_PER_CLIENT
- # setTimeout(@proxyMain, @broker_poll_interval * 1000)
- # return
- # params = [['r', '1']]
- # params.push ['transport', 'websocket']
- # params.push ['transport', 'webrtc']
-
# Receive an SDP offer from client plugin.
receiveOffer: (desc) =>
sdp = new RTCSessionDescription desc
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits