Loading broker/broker.go +93 −34 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ package snowflake_broker import ( "container/heap" "fmt" "io/ioutil" "log" "net" Loading @@ -23,27 +24,68 @@ const ( ProxyTimeout = 10 ) // This is minimum viable client-proxy registration. // TODO(#13): better, more secure registration corresponding to what's in // the python flashproxy facilitator. var snowflakes *SnowflakeHeap type SnowflakeContext struct { snowflakes *SnowflakeHeap // Map keeping track of snowflakeIDs required to match SDP answers from // the second http POST. var snowflakeMap map[string]*Snowflake snowflakeMap map[string]*Snowflake } type SnowflakeHandler struct { *SnowflakeContext h func(*SnowflakeContext, http.ResponseWriter, *http.Request) } func (sh SnowflakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { sh.h(sh.SnowflakeContext, w, r) } type ProxyRequest struct { id string offerChan chan []byte } var createChan = make(chan *ProxyRequest) // Create and add a Snowflake to the heap. func AddSnowflake(id string) *Snowflake { func (sc *SnowflakeContext) AddSnowflake(id string) *Snowflake { log.Println(sc.snowflakes) snowflake := new(Snowflake) snowflake.id = id snowflake.clients = 0 snowflake.offerChannel = make(chan []byte) snowflake.answerChannel = make(chan []byte) heap.Push(snowflakes, snowflake) snowflakeMap[id] = snowflake heap.Push(sc.snowflakes, snowflake) sc.snowflakeMap[id] = snowflake log.Println("Total snowflakes available: ", sc.snowflakes.Len()) log.Println(sc.snowflakes) log.Println(sc.snowflakeMap) return snowflake } func (sc *SnowflakeContext) Broker(proxies <-chan *ProxyRequest) { for p := range proxies { log.Println("adding ", p.id) snowflake := sc.AddSnowflake(p.id) // Wait for a client to avail an offer to the snowflake, or timeout // and ask the snowflake to poll later. go func(p *ProxyRequest) { select { case offer := <-snowflake.offerChannel: log.Println("Passing client offer to snowflake.") p.offerChan <- offer case <-time.After(time.Second * ProxyTimeout): // This snowflake is no longer available to serve clients. heap.Remove(sc.snowflakes, snowflake.index) delete(sc.snowflakeMap, snowflake.id) p.offerChan <- nil } }(p) } } func robotsTxtHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Write([]byte("User-agent: *\nDisallow:\n")) Loading Loading @@ -73,7 +115,7 @@ Expects a WebRTC SDP offer in the Request to give to an assigned snowflake proxy, which responds with the SDP answer to be sent in the HTTP response back to the client. */ func clientHandler(w http.ResponseWriter, r *http.Request) { func clientHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { offer, err := ioutil.ReadAll(r.Body) if nil != err { log.Println("Invalid data.") Loading @@ -85,23 +127,24 @@ func clientHandler(w http.ResponseWriter, r *http.Request) { // Find the most available snowflake proxy, and pass the offer to it. // TODO: Needs improvement - maybe shouldn' snowflake := heap.Pop(snowflakes).(*Snowflake) if nil == snowflake { if ctx.snowflakes.Len() <= 0 { log.Println("Client: No snowflake proxies available.") w.WriteHeader(http.StatusServiceUnavailable) // w.Write([]byte("no snowflake proxies available")) return } snowflake := heap.Pop(ctx.snowflakes).(*Snowflake) snowflake.offerChannel <- offer // Wait for the answer to be returned on the channel. select { case answer := <-snowflake.answerChannel: log.Println("Retrieving answer") log.Println("Client: Retrieving answer") w.Write(answer) // Only remove from the snowflake map once the answer is set. delete(snowflakeMap, snowflake.id) delete(ctx.snowflakeMap, snowflake.id) case <-time.After(time.Second * ClientTimeout): log.Println("Client: Timed out.") w.WriteHeader(http.StatusGatewayTimeout) w.Write([]byte("timed out waiting for answer!")) } Loading @@ -110,7 +153,7 @@ func clientHandler(w http.ResponseWriter, r *http.Request) { /* For snowflake proxies to request a client from the Broker. */ func proxyHandler(w http.ResponseWriter, r *http.Request) { func proxyHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { if isPreflight(w, r) { return } Loading @@ -126,21 +169,22 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) { } // Maybe confirm that X-Session-ID is the same. log.Println("Received snowflake: ", id) snowflake := AddSnowflake(id) p := new(ProxyRequest) p.id = id p.offerChan = make(chan []byte) createChan <- p // Wait for a client to avail an offer to the snowflake, or timeout // and ask the snowflake to poll later. select { case offer := <-snowflake.offerChannel: log.Println("Passing client offer to snowflake.") w.Write(offer) case <-time.After(time.Second * ProxyTimeout): // This snowflake is no longer available to serve clients. heap.Remove(snowflakes, snowflake.index) delete(snowflakeMap, snowflake.id) offer := <-p.offerChan if nil == offer { log.Println("Proxy " + id + " did not receive a Client offer.") w.WriteHeader(http.StatusGatewayTimeout) return } log.Println("Passing client offer to snowflake.") w.Write(offer) } /* Loading @@ -148,12 +192,12 @@ 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) { func answerHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { if isPreflight(w, r) { return } id := r.Header.Get("X-Session-ID") snowflake, ok := snowflakeMap[id] snowflake, ok := ctx.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. Loading @@ -170,15 +214,30 @@ func answerHandler(w http.ResponseWriter, r *http.Request) { snowflake.answerChannel <- body } func debugHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { s := fmt.Sprintf("current: %d", ctx.snowflakes.Len()) w.Write([]byte(s)) } func init() { snowflakes = new(SnowflakeHeap) snowflakeMap = make(map[string]*Snowflake) // snowflakeMap = make(map[string]*Snowflake) snowflakes := new(SnowflakeHeap) heap.Init(snowflakes) ctx := &SnowflakeContext{ snowflakes: snowflakes, snowflakeMap: make(map[string]*Snowflake), } go ctx.Broker(createChan) http.HandleFunc("/robots.txt", robotsTxtHandler) http.HandleFunc("/ip", ipHandler) http.HandleFunc("/client", clientHandler) http.HandleFunc("/proxy", proxyHandler) http.HandleFunc("/answer", answerHandler) // http.HandleFunc("/client", clientHandler) // http.HandleFunc("/proxy", proxyHandler) // http.HandleFunc("/answer", answerHandler) http.Handle("/client", SnowflakeHandler{ctx, clientHandler}) http.Handle("/proxy", SnowflakeHandler{ctx, proxyHandler}) http.Handle("/answer", SnowflakeHandler{ctx, answerHandler}) http.Handle("/debug", SnowflakeHandler{ctx, debugHandler}) } broker/snowflake-broker_test.go +8 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ func TestSnowflakeHeap(t *testing.T) { s4.clients = 1 heap.Push(h, s1) if 1 != h.Len() { } heap.Push(h, s2) heap.Push(h, s3) heap.Push(h, s4) Loading @@ -36,6 +38,9 @@ func TestSnowflakeHeap(t *testing.T) { } r := heap.Pop(h).(*Snowflake) if 2 != h.Len() { t.Error("Unexpected length.") } if r.clients != 3 { t.Error("Unexpected clients: ", r.clients) } Loading @@ -44,6 +49,9 @@ func TestSnowflakeHeap(t *testing.T) { } r = heap.Pop(h).(*Snowflake) if 1 != h.Len() { t.Error("Unexpected length.") } if r.clients != 4 { t.Error("Unexpected clients: ", r.clients) } Loading client/torrc-localhost 0 → 100644 +7 −0 Original line number Diff line number Diff line UseBridges 1 DataDirectory datadir ClientTransportPlugin snowflake exec ./client \ -url http://localhost:8080/ \ Bridge snowflake 0.0.3.0:1 proxy/broker.coffee +2 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,8 @@ class Broker @clients = 0 @id = genSnowflakeID() # Ensure url has the right protocol + trailing slash. @url = 'https://' + @url if 0 != @url.indexOf('https://', 0) @url = 'http://' + @url if 0 == @url.indexOf('localhost', 0) @url = 'https://' + @url if 0 != @url.indexOf('http', 0) @url += '/' if '/' != @url.substr -1 # Promises some client SDP Offer. Loading Loading
broker/broker.go +93 −34 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ package snowflake_broker import ( "container/heap" "fmt" "io/ioutil" "log" "net" Loading @@ -23,27 +24,68 @@ const ( ProxyTimeout = 10 ) // This is minimum viable client-proxy registration. // TODO(#13): better, more secure registration corresponding to what's in // the python flashproxy facilitator. var snowflakes *SnowflakeHeap type SnowflakeContext struct { snowflakes *SnowflakeHeap // Map keeping track of snowflakeIDs required to match SDP answers from // the second http POST. var snowflakeMap map[string]*Snowflake snowflakeMap map[string]*Snowflake } type SnowflakeHandler struct { *SnowflakeContext h func(*SnowflakeContext, http.ResponseWriter, *http.Request) } func (sh SnowflakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { sh.h(sh.SnowflakeContext, w, r) } type ProxyRequest struct { id string offerChan chan []byte } var createChan = make(chan *ProxyRequest) // Create and add a Snowflake to the heap. func AddSnowflake(id string) *Snowflake { func (sc *SnowflakeContext) AddSnowflake(id string) *Snowflake { log.Println(sc.snowflakes) snowflake := new(Snowflake) snowflake.id = id snowflake.clients = 0 snowflake.offerChannel = make(chan []byte) snowflake.answerChannel = make(chan []byte) heap.Push(snowflakes, snowflake) snowflakeMap[id] = snowflake heap.Push(sc.snowflakes, snowflake) sc.snowflakeMap[id] = snowflake log.Println("Total snowflakes available: ", sc.snowflakes.Len()) log.Println(sc.snowflakes) log.Println(sc.snowflakeMap) return snowflake } func (sc *SnowflakeContext) Broker(proxies <-chan *ProxyRequest) { for p := range proxies { log.Println("adding ", p.id) snowflake := sc.AddSnowflake(p.id) // Wait for a client to avail an offer to the snowflake, or timeout // and ask the snowflake to poll later. go func(p *ProxyRequest) { select { case offer := <-snowflake.offerChannel: log.Println("Passing client offer to snowflake.") p.offerChan <- offer case <-time.After(time.Second * ProxyTimeout): // This snowflake is no longer available to serve clients. heap.Remove(sc.snowflakes, snowflake.index) delete(sc.snowflakeMap, snowflake.id) p.offerChan <- nil } }(p) } } func robotsTxtHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Write([]byte("User-agent: *\nDisallow:\n")) Loading Loading @@ -73,7 +115,7 @@ Expects a WebRTC SDP offer in the Request to give to an assigned snowflake proxy, which responds with the SDP answer to be sent in the HTTP response back to the client. */ func clientHandler(w http.ResponseWriter, r *http.Request) { func clientHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { offer, err := ioutil.ReadAll(r.Body) if nil != err { log.Println("Invalid data.") Loading @@ -85,23 +127,24 @@ func clientHandler(w http.ResponseWriter, r *http.Request) { // Find the most available snowflake proxy, and pass the offer to it. // TODO: Needs improvement - maybe shouldn' snowflake := heap.Pop(snowflakes).(*Snowflake) if nil == snowflake { if ctx.snowflakes.Len() <= 0 { log.Println("Client: No snowflake proxies available.") w.WriteHeader(http.StatusServiceUnavailable) // w.Write([]byte("no snowflake proxies available")) return } snowflake := heap.Pop(ctx.snowflakes).(*Snowflake) snowflake.offerChannel <- offer // Wait for the answer to be returned on the channel. select { case answer := <-snowflake.answerChannel: log.Println("Retrieving answer") log.Println("Client: Retrieving answer") w.Write(answer) // Only remove from the snowflake map once the answer is set. delete(snowflakeMap, snowflake.id) delete(ctx.snowflakeMap, snowflake.id) case <-time.After(time.Second * ClientTimeout): log.Println("Client: Timed out.") w.WriteHeader(http.StatusGatewayTimeout) w.Write([]byte("timed out waiting for answer!")) } Loading @@ -110,7 +153,7 @@ func clientHandler(w http.ResponseWriter, r *http.Request) { /* For snowflake proxies to request a client from the Broker. */ func proxyHandler(w http.ResponseWriter, r *http.Request) { func proxyHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { if isPreflight(w, r) { return } Loading @@ -126,21 +169,22 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) { } // Maybe confirm that X-Session-ID is the same. log.Println("Received snowflake: ", id) snowflake := AddSnowflake(id) p := new(ProxyRequest) p.id = id p.offerChan = make(chan []byte) createChan <- p // Wait for a client to avail an offer to the snowflake, or timeout // and ask the snowflake to poll later. select { case offer := <-snowflake.offerChannel: log.Println("Passing client offer to snowflake.") w.Write(offer) case <-time.After(time.Second * ProxyTimeout): // This snowflake is no longer available to serve clients. heap.Remove(snowflakes, snowflake.index) delete(snowflakeMap, snowflake.id) offer := <-p.offerChan if nil == offer { log.Println("Proxy " + id + " did not receive a Client offer.") w.WriteHeader(http.StatusGatewayTimeout) return } log.Println("Passing client offer to snowflake.") w.Write(offer) } /* Loading @@ -148,12 +192,12 @@ 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) { func answerHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { if isPreflight(w, r) { return } id := r.Header.Get("X-Session-ID") snowflake, ok := snowflakeMap[id] snowflake, ok := ctx.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. Loading @@ -170,15 +214,30 @@ func answerHandler(w http.ResponseWriter, r *http.Request) { snowflake.answerChannel <- body } func debugHandler(ctx *SnowflakeContext, w http.ResponseWriter, r *http.Request) { s := fmt.Sprintf("current: %d", ctx.snowflakes.Len()) w.Write([]byte(s)) } func init() { snowflakes = new(SnowflakeHeap) snowflakeMap = make(map[string]*Snowflake) // snowflakeMap = make(map[string]*Snowflake) snowflakes := new(SnowflakeHeap) heap.Init(snowflakes) ctx := &SnowflakeContext{ snowflakes: snowflakes, snowflakeMap: make(map[string]*Snowflake), } go ctx.Broker(createChan) http.HandleFunc("/robots.txt", robotsTxtHandler) http.HandleFunc("/ip", ipHandler) http.HandleFunc("/client", clientHandler) http.HandleFunc("/proxy", proxyHandler) http.HandleFunc("/answer", answerHandler) // http.HandleFunc("/client", clientHandler) // http.HandleFunc("/proxy", proxyHandler) // http.HandleFunc("/answer", answerHandler) http.Handle("/client", SnowflakeHandler{ctx, clientHandler}) http.Handle("/proxy", SnowflakeHandler{ctx, proxyHandler}) http.Handle("/answer", SnowflakeHandler{ctx, answerHandler}) http.Handle("/debug", SnowflakeHandler{ctx, debugHandler}) }
broker/snowflake-broker_test.go +8 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ func TestSnowflakeHeap(t *testing.T) { s4.clients = 1 heap.Push(h, s1) if 1 != h.Len() { } heap.Push(h, s2) heap.Push(h, s3) heap.Push(h, s4) Loading @@ -36,6 +38,9 @@ func TestSnowflakeHeap(t *testing.T) { } r := heap.Pop(h).(*Snowflake) if 2 != h.Len() { t.Error("Unexpected length.") } if r.clients != 3 { t.Error("Unexpected clients: ", r.clients) } Loading @@ -44,6 +49,9 @@ func TestSnowflakeHeap(t *testing.T) { } r = heap.Pop(h).(*Snowflake) if 1 != h.Len() { t.Error("Unexpected length.") } if r.clients != 4 { t.Error("Unexpected clients: ", r.clients) } Loading
client/torrc-localhost 0 → 100644 +7 −0 Original line number Diff line number Diff line UseBridges 1 DataDirectory datadir ClientTransportPlugin snowflake exec ./client \ -url http://localhost:8080/ \ Bridge snowflake 0.0.3.0:1
proxy/broker.coffee +2 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,8 @@ class Broker @clients = 0 @id = genSnowflakeID() # Ensure url has the right protocol + trailing slash. @url = 'https://' + @url if 0 != @url.indexOf('https://', 0) @url = 'http://' + @url if 0 == @url.indexOf('localhost', 0) @url = 'https://' + @url if 0 != @url.indexOf('http', 0) @url += '/' if '/' != @url.substr -1 # Promises some client SDP Offer. Loading