commit c4cfc7f8ba2a3be9dbccad6d6d7bab35cb7fb8f9 Author: David Fifield david@bamsoftware.com Date: Sat Mar 26 11:00:20 2016 -0700
Move server to server-webrtc.
To make room for the WebSocket server we're actually going to use as a primary server. Move server-webrtc docs to server-webrtc/README.md. --- .gitignore | 2 +- Makefile | 2 +- README.md | 37 +----- server-webrtc/README.md | 26 ++++ server-webrtc/http.go | 67 ++++++++++ server-webrtc/snowflake.go | 300 +++++++++++++++++++++++++++++++++++++++++++++ server-webrtc/torrc | 8 ++ server/http.go | 67 ---------- server/snowflake.go | 300 --------------------------------------------- server/torrc | 8 -- 10 files changed, 408 insertions(+), 409 deletions(-)
diff --git a/.gitignore b/.gitignore index f9356ae..784e6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ .DS_Store datadir/ client/client -server/server +server-webrtc/server-webrtc snowflake.log proxy/test proxy/build diff --git a/Makefile b/Makefile index 688e273..fbf12ae 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,4 @@ .PHONY: check check: ! gofmt -l . 2>&1 | read - go vet ./broker ./client ./server \ No newline at end of file + go vet ./broker ./client ./server-webrtc diff --git a/README.md b/README.md index f9f1b1f..db896e6 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,6 @@ method (see `torrc-manual`): ClientTransportPlugin snowflake exec ./client --meek ```
-Also, it is possible to connect directly to the go-webrtc server plugin -(skipping all the browser snowflake / broker stuff - see appendix) -
#### Building a Snowflake
@@ -134,35 +131,6 @@ abundance of ephemeral and short-lived (and special!) volunteer proxies...
### Appendix
-##### -- Testing directly via WebRTC Server -- - -Ordinarily, the WebRTC client plugin speaks with a Broker which helps -match and signal with a browser proxy, which ultimately speaks with a default -websocket server. - - -However, there is a WebRTC server plugin which uses an HTTP server that -simulates the interaction that a client would have with the broker, for -direct testing. - -Edit server/torrc and add "-http 127.0.0.1:8080" to the end of the -ServerTransportPlugin line: -``` -ServerTransportPlugin snowflake exec ./server -http 127.0.0.1:8080 -``` - -``` -cd server/ -go build -tor -f torrc -``` - -Edit client/torrc and add "-url http://127.0.0.1:8080" to the end of the -ClientTransportPlugin line: -``` -ClientTransportPlugin snowflake exec ./client -url http://127.0.0.1:8080/ -``` - ##### -- Testing Copy-Paste Via Browser Proxy --
Open a browser proxy, passing the `manual` parameter; e.g. @@ -183,6 +151,11 @@ Then, in the browser proxy: - Once WebRTC successfully connects, the browser terminal should turn green. Shortly after, the tor client should bootstrap to 100%.
+##### -- Testing directly via WebRTC Server -- + +See server-webrtc/README.md for information on connecting directly to a +WebRTC server transport plugin, bypassing the Broker and browser proxy. + More documentation on the way.
Also available at: diff --git a/server-webrtc/README.md b/server-webrtc/README.md new file mode 100644 index 0000000..53cad14 --- /dev/null +++ b/server-webrtc/README.md @@ -0,0 +1,26 @@ +Ordinarily, the WebRTC client plugin speaks with a Broker which helps +match and signal with a browser proxy, which ultimately speaks with a default +websocket server. + + +However, this directory contains a WebRTC server plugin which uses an +HTTP server that simulates the interaction that a client would have with +the broker, for direct testing. + +Edit server-webrtc/torrc and add "-http 127.0.0.1:8080" to the end of the +ServerTransportPlugin line: +``` +ServerTransportPlugin snowflake exec ./server-webrtc -http 127.0.0.1:8080 +``` + +``` +cd server-webrtc/ +go build +tor -f torrc +``` + +Edit client/torrc and add "-url http://127.0.0.1:8080" to the end of the +ClientTransportPlugin line: +``` +ClientTransportPlugin snowflake exec ./client -url http://127.0.0.1:8080/ +``` diff --git a/server-webrtc/http.go b/server-webrtc/http.go new file mode 100644 index 0000000..f0ecb88 --- /dev/null +++ b/server-webrtc/http.go @@ -0,0 +1,67 @@ +// An HTTP-based signaling channel for the WebRTC server. It imitates the +// broker as seen by clients, but it doesn't connect them to an +// intermediate WebRTC proxy, rather connects them directly to this WebRTC +// server. This code should be deleted when we have proxies in place. + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/keroserene/go-webrtc" +) + +type httpHandler struct { + config *webrtc.Configuration +} + +func (h *httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + switch req.Method { + case "GET": + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`HTTP signaling channel + +Send a POST request containing an SDP offer. The response will +contain an SDP answer. +`)) + return + case "POST": + break + default: + http.Error(w, "Bad request.", http.StatusBadRequest) + return + } + + // POST handling begins here. + body, err := ioutil.ReadAll(http.MaxBytesReader(w, req.Body, 100000)) + if err != nil { + http.Error(w, "Bad request.", http.StatusBadRequest) + return + } + offer := webrtc.DeserializeSessionDescription(string(body)) + if offer == nil { + http.Error(w, "Bad request.", http.StatusBadRequest) + return + } + + pc, err := makePeerConnectionFromOffer(offer, h.config) + if err != nil { + http.Error(w, fmt.Sprintf("Cannot create offer: %s", err), http.StatusInternalServerError) + return + } + + log.Println("answering HTTP POST") + + w.WriteHeader(http.StatusOK) + w.Write([]byte(pc.LocalDescription().Serialize())) +} + +func receiveSignalsHTTP(addr string, config *webrtc.Configuration) error { + http.Handle("/", &httpHandler{config}) + log.Printf("listening HTTP on %s", addr) + return http.ListenAndServe(addr, nil) +} diff --git a/server-webrtc/snowflake.go b/server-webrtc/snowflake.go new file mode 100644 index 0000000..9a9c7dc --- /dev/null +++ b/server-webrtc/snowflake.go @@ -0,0 +1,300 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "log" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "git.torproject.org/pluggable-transports/goptlib.git" + "github.com/keroserene/go-webrtc" +) + +var ptMethodName = "snowflake" +var ptInfo pt.ServerInfo +var logFile *os.File + +// When a datachannel handler starts, +1 is written to this channel; +// when it ends, -1 is written. +var handlerChan = make(chan int) + +func copyLoop(a, b net.Conn) { + var wg sync.WaitGroup + wg.Add(2) + go func() { + io.Copy(b, a) + wg.Done() + }() + go func() { + io.Copy(a, b) + wg.Done() + }() + wg.Wait() +} + +type webRTCConn struct { + dc *webrtc.DataChannel + pc *webrtc.PeerConnection + pr *io.PipeReader +} + +func (c *webRTCConn) Read(b []byte) (int, error) { + return c.pr.Read(b) +} + +func (c *webRTCConn) Write(b []byte) (int, error) { + // log.Printf("webrtc Write %d %+q", len(b), string(b)) + log.Printf("Write %d bytes --> WebRTC", len(b)) + c.dc.Send(b) + return len(b), nil +} + +func (c *webRTCConn) Close() error { + return c.pc.Close() +} + +func (c *webRTCConn) LocalAddr() net.Addr { + return nil +} + +func (c *webRTCConn) RemoteAddr() net.Addr { + return nil +} + +func (c *webRTCConn) SetDeadline(t time.Time) error { + return fmt.Errorf("SetDeadline not implemented") +} + +func (c *webRTCConn) SetReadDeadline(t time.Time) error { + return fmt.Errorf("SetReadDeadline not implemented") +} + +func (c *webRTCConn) SetWriteDeadline(t time.Time) error { + return fmt.Errorf("SetWriteDeadline not implemented") +} + +func datachannelHandler(conn *webRTCConn) { + defer conn.Close() + + handlerChan <- 1 + defer func() { + handlerChan <- -1 + }() + + or, err := pt.DialOr(&ptInfo, "", ptMethodName) // TODO: Extended OR + if err != nil { + log.Printf("Failed to connect to ORPort: " + err.Error()) + return + } + defer or.Close() + + copyLoop(conn, or) +} + +// Create a PeerConnection from an SDP offer. Blocks until the gathering of ICE +// candidates is complete and the answer is available in LocalDescription. +// Installs an OnDataChannel callback that creates a webRTCConn and passes it to +// datachannelHandler. +func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription, config *webrtc.Configuration) (*webrtc.PeerConnection, error) { + errChan := make(chan error) + answerChan := make(chan struct{}) + + pc, err := webrtc.NewPeerConnection(config) + if err != nil { + return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) + } + pc.OnNegotiationNeeded = func() { + panic("OnNegotiationNeeded") + } + pc.OnIceComplete = func() { + answerChan <- struct{}{} + } + pc.OnDataChannel = func(dc *webrtc.DataChannel) { + log.Println("OnDataChannel") + + pr, pw := io.Pipe() + + dc.OnOpen = func() { + log.Println("OnOpen channel") + } + dc.OnClose = func() { + log.Println("OnClose channel") + pw.Close() + } + dc.OnMessage = func(msg []byte) { + log.Printf("OnMessage <--- %d bytes", len(msg)) + n, err := pw.Write(msg) + if err != nil { + pw.CloseWithError(err) + } + if n != len(msg) { + panic("short write") + } + } + + conn := &webRTCConn{pc: pc, dc: dc, pr: pr} + go datachannelHandler(conn) + } + + err = pc.SetRemoteDescription(sdp) + if err != nil { + pc.Close() + return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err) + } + log.Println("sdp offer successfully received.") + + go func() { + log.Println("Generating answer...") + answer, err := pc.CreateAnswer() // blocking + if err != nil { + errChan <- err + return + } + err = pc.SetLocalDescription(answer) + if err != nil { + errChan <- err + return + } + }() + + // Wait until answer is ready. + select { + case err = <-errChan: + pc.Close() + return nil, err + case _, ok := <-answerChan: + if !ok { + pc.Close() + return nil, fmt.Errorf("Failed gathering ICE candidates.") + } + } + + return pc, nil +} + +// Create a signaling named pipe and feed offers from it into +// makePeerConnectionFromOffer. +func receiveSignalsFIFO(filename string, config *webrtc.Configuration) error { + err := syscall.Mkfifo(filename, 0600) + if err != nil { + if err.(syscall.Errno) != syscall.EEXIST { + return err + } + } + signalFile, err := os.OpenFile(filename, os.O_RDONLY, 0600) + if err != nil { + return err + } + defer signalFile.Close() + + s := bufio.NewScanner(signalFile) + for s.Scan() { + msg := s.Text() + sdp := webrtc.DeserializeSessionDescription(msg) + if sdp == nil { + log.Printf("ignoring invalid signal message %+q", msg) + continue + } + + pc, err := makePeerConnectionFromOffer(sdp, config) + if err != nil { + log.Printf("makePeerConnectionFromOffer: %s", err) + continue + } + // Write offer to log for manual signaling. + log.Printf("----------------") + fmt.Fprintln(logFile, pc.LocalDescription().Serialize()) + log.Printf("----------------") + } + return s.Err() +} + +func main() { + var err error + var httpAddr string + + flag.StringVar(&httpAddr, "http", "", "listen for HTTP signaling") + flag.Parse() + + logFile, err = os.OpenFile("snowflake.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + log.Fatal(err) + } + defer logFile.Close() + log.SetOutput(logFile) + + log.Println("starting") + webrtc.SetLoggingVerbosity(1) + + ptInfo, err = pt.ServerSetup(nil) + if err != nil { + log.Fatal(err) + } + + webRTCConfig := webrtc.NewConfiguration(webrtc.OptionIceServer("stun:stun.l.google.com:19302")) + + // Start FIFO-based signaling receiver. + go func() { + err := receiveSignalsFIFO("signal", webRTCConfig) + if err != nil { + log.Printf("receiveSignalsFIFO: %s", err) + } + }() + + // Start HTTP-based signaling receiver. + if httpAddr != "" { + go func() { + err := receiveSignalsHTTP(httpAddr, webRTCConfig) + if err != nil { + log.Printf("receiveSignalsHTTP: %s", err) + } + }() + } + + for _, bindaddr := range ptInfo.Bindaddrs { + switch bindaddr.MethodName { + case ptMethodName: + bindaddr.Addr.Port = 12345 // lies!!! + pt.Smethod(bindaddr.MethodName, bindaddr.Addr) + default: + pt.SmethodError(bindaddr.MethodName, "no such method") + } + } + pt.SmethodsDone() + + var numHandlers int = 0 + var sig os.Signal + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // wait for first signal + sig = nil + for sig == nil { + select { + case n := <-handlerChan: + numHandlers += n + case sig = <-sigChan: + } + } + + if sig == syscall.SIGTERM { + return + } + + // wait for second signal or no more handlers + sig = nil + for sig == nil && numHandlers != 0 { + select { + case n := <-handlerChan: + numHandlers += n + case sig = <-sigChan: + } + } +} diff --git a/server-webrtc/torrc b/server-webrtc/torrc new file mode 100644 index 0000000..44b5964 --- /dev/null +++ b/server-webrtc/torrc @@ -0,0 +1,8 @@ +BridgeRelay 1 +ORPort 9001 +ExtORPort auto +SocksPort 0 +ExitPolicy reject *:* +DataDirectory datadir + +ServerTransportPlugin snowflake exec ./server diff --git a/server/http.go b/server/http.go deleted file mode 100644 index f0ecb88..0000000 --- a/server/http.go +++ /dev/null @@ -1,67 +0,0 @@ -// An HTTP-based signaling channel for the WebRTC server. It imitates the -// broker as seen by clients, but it doesn't connect them to an -// intermediate WebRTC proxy, rather connects them directly to this WebRTC -// server. This code should be deleted when we have proxies in place. - -package main - -import ( - "fmt" - "io/ioutil" - "log" - "net/http" - - "github.com/keroserene/go-webrtc" -) - -type httpHandler struct { - config *webrtc.Configuration -} - -func (h *httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - switch req.Method { - case "GET": - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(`HTTP signaling channel - -Send a POST request containing an SDP offer. The response will -contain an SDP answer. -`)) - return - case "POST": - break - default: - http.Error(w, "Bad request.", http.StatusBadRequest) - return - } - - // POST handling begins here. - body, err := ioutil.ReadAll(http.MaxBytesReader(w, req.Body, 100000)) - if err != nil { - http.Error(w, "Bad request.", http.StatusBadRequest) - return - } - offer := webrtc.DeserializeSessionDescription(string(body)) - if offer == nil { - http.Error(w, "Bad request.", http.StatusBadRequest) - return - } - - pc, err := makePeerConnectionFromOffer(offer, h.config) - if err != nil { - http.Error(w, fmt.Sprintf("Cannot create offer: %s", err), http.StatusInternalServerError) - return - } - - log.Println("answering HTTP POST") - - w.WriteHeader(http.StatusOK) - w.Write([]byte(pc.LocalDescription().Serialize())) -} - -func receiveSignalsHTTP(addr string, config *webrtc.Configuration) error { - http.Handle("/", &httpHandler{config}) - log.Printf("listening HTTP on %s", addr) - return http.ListenAndServe(addr, nil) -} diff --git a/server/snowflake.go b/server/snowflake.go deleted file mode 100644 index 9a9c7dc..0000000 --- a/server/snowflake.go +++ /dev/null @@ -1,300 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - "io" - "log" - "net" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "git.torproject.org/pluggable-transports/goptlib.git" - "github.com/keroserene/go-webrtc" -) - -var ptMethodName = "snowflake" -var ptInfo pt.ServerInfo -var logFile *os.File - -// When a datachannel handler starts, +1 is written to this channel; -// when it ends, -1 is written. -var handlerChan = make(chan int) - -func copyLoop(a, b net.Conn) { - var wg sync.WaitGroup - wg.Add(2) - go func() { - io.Copy(b, a) - wg.Done() - }() - go func() { - io.Copy(a, b) - wg.Done() - }() - wg.Wait() -} - -type webRTCConn struct { - dc *webrtc.DataChannel - pc *webrtc.PeerConnection - pr *io.PipeReader -} - -func (c *webRTCConn) Read(b []byte) (int, error) { - return c.pr.Read(b) -} - -func (c *webRTCConn) Write(b []byte) (int, error) { - // log.Printf("webrtc Write %d %+q", len(b), string(b)) - log.Printf("Write %d bytes --> WebRTC", len(b)) - c.dc.Send(b) - return len(b), nil -} - -func (c *webRTCConn) Close() error { - return c.pc.Close() -} - -func (c *webRTCConn) LocalAddr() net.Addr { - return nil -} - -func (c *webRTCConn) RemoteAddr() net.Addr { - return nil -} - -func (c *webRTCConn) SetDeadline(t time.Time) error { - return fmt.Errorf("SetDeadline not implemented") -} - -func (c *webRTCConn) SetReadDeadline(t time.Time) error { - return fmt.Errorf("SetReadDeadline not implemented") -} - -func (c *webRTCConn) SetWriteDeadline(t time.Time) error { - return fmt.Errorf("SetWriteDeadline not implemented") -} - -func datachannelHandler(conn *webRTCConn) { - defer conn.Close() - - handlerChan <- 1 - defer func() { - handlerChan <- -1 - }() - - or, err := pt.DialOr(&ptInfo, "", ptMethodName) // TODO: Extended OR - if err != nil { - log.Printf("Failed to connect to ORPort: " + err.Error()) - return - } - defer or.Close() - - copyLoop(conn, or) -} - -// Create a PeerConnection from an SDP offer. Blocks until the gathering of ICE -// candidates is complete and the answer is available in LocalDescription. -// Installs an OnDataChannel callback that creates a webRTCConn and passes it to -// datachannelHandler. -func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription, config *webrtc.Configuration) (*webrtc.PeerConnection, error) { - errChan := make(chan error) - answerChan := make(chan struct{}) - - pc, err := webrtc.NewPeerConnection(config) - if err != nil { - return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) - } - pc.OnNegotiationNeeded = func() { - panic("OnNegotiationNeeded") - } - pc.OnIceComplete = func() { - answerChan <- struct{}{} - } - pc.OnDataChannel = func(dc *webrtc.DataChannel) { - log.Println("OnDataChannel") - - pr, pw := io.Pipe() - - dc.OnOpen = func() { - log.Println("OnOpen channel") - } - dc.OnClose = func() { - log.Println("OnClose channel") - pw.Close() - } - dc.OnMessage = func(msg []byte) { - log.Printf("OnMessage <--- %d bytes", len(msg)) - n, err := pw.Write(msg) - if err != nil { - pw.CloseWithError(err) - } - if n != len(msg) { - panic("short write") - } - } - - conn := &webRTCConn{pc: pc, dc: dc, pr: pr} - go datachannelHandler(conn) - } - - err = pc.SetRemoteDescription(sdp) - if err != nil { - pc.Close() - return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err) - } - log.Println("sdp offer successfully received.") - - go func() { - log.Println("Generating answer...") - answer, err := pc.CreateAnswer() // blocking - if err != nil { - errChan <- err - return - } - err = pc.SetLocalDescription(answer) - if err != nil { - errChan <- err - return - } - }() - - // Wait until answer is ready. - select { - case err = <-errChan: - pc.Close() - return nil, err - case _, ok := <-answerChan: - if !ok { - pc.Close() - return nil, fmt.Errorf("Failed gathering ICE candidates.") - } - } - - return pc, nil -} - -// Create a signaling named pipe and feed offers from it into -// makePeerConnectionFromOffer. -func receiveSignalsFIFO(filename string, config *webrtc.Configuration) error { - err := syscall.Mkfifo(filename, 0600) - if err != nil { - if err.(syscall.Errno) != syscall.EEXIST { - return err - } - } - signalFile, err := os.OpenFile(filename, os.O_RDONLY, 0600) - if err != nil { - return err - } - defer signalFile.Close() - - s := bufio.NewScanner(signalFile) - for s.Scan() { - msg := s.Text() - sdp := webrtc.DeserializeSessionDescription(msg) - if sdp == nil { - log.Printf("ignoring invalid signal message %+q", msg) - continue - } - - pc, err := makePeerConnectionFromOffer(sdp, config) - if err != nil { - log.Printf("makePeerConnectionFromOffer: %s", err) - continue - } - // Write offer to log for manual signaling. - log.Printf("----------------") - fmt.Fprintln(logFile, pc.LocalDescription().Serialize()) - log.Printf("----------------") - } - return s.Err() -} - -func main() { - var err error - var httpAddr string - - flag.StringVar(&httpAddr, "http", "", "listen for HTTP signaling") - flag.Parse() - - logFile, err = os.OpenFile("snowflake.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - log.Fatal(err) - } - defer logFile.Close() - log.SetOutput(logFile) - - log.Println("starting") - webrtc.SetLoggingVerbosity(1) - - ptInfo, err = pt.ServerSetup(nil) - if err != nil { - log.Fatal(err) - } - - webRTCConfig := webrtc.NewConfiguration(webrtc.OptionIceServer("stun:stun.l.google.com:19302")) - - // Start FIFO-based signaling receiver. - go func() { - err := receiveSignalsFIFO("signal", webRTCConfig) - if err != nil { - log.Printf("receiveSignalsFIFO: %s", err) - } - }() - - // Start HTTP-based signaling receiver. - if httpAddr != "" { - go func() { - err := receiveSignalsHTTP(httpAddr, webRTCConfig) - if err != nil { - log.Printf("receiveSignalsHTTP: %s", err) - } - }() - } - - for _, bindaddr := range ptInfo.Bindaddrs { - switch bindaddr.MethodName { - case ptMethodName: - bindaddr.Addr.Port = 12345 // lies!!! - pt.Smethod(bindaddr.MethodName, bindaddr.Addr) - default: - pt.SmethodError(bindaddr.MethodName, "no such method") - } - } - pt.SmethodsDone() - - var numHandlers int = 0 - var sig os.Signal - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - - // wait for first signal - sig = nil - for sig == nil { - select { - case n := <-handlerChan: - numHandlers += n - case sig = <-sigChan: - } - } - - if sig == syscall.SIGTERM { - return - } - - // wait for second signal or no more handlers - sig = nil - for sig == nil && numHandlers != 0 { - select { - case n := <-handlerChan: - numHandlers += n - case sig = <-sigChan: - } - } -} diff --git a/server/torrc b/server/torrc deleted file mode 100644 index 44b5964..0000000 --- a/server/torrc +++ /dev/null @@ -1,8 +0,0 @@ -BridgeRelay 1 -ORPort 9001 -ExtORPort auto -SocksPort 0 -ExitPolicy reject *:* -DataDirectory datadir - -ServerTransportPlugin snowflake exec ./server