commit c4ef06f097354214460d45207a0297eec79d3bbe Author: Ximin Luo infinity0@torproject.org Date: Sun May 18 13:47:26 2014 +0100
disambiguate binary names to avoid potential name clashes --- Makefile | 22 +- pt-websocket-client/pt-websocket-client.go | 263 ++++++++++++++++++++++++ pt-websocket-server/pt-websocket-server.go | 299 ++++++++++++++++++++++++++++ websocket-client/websocket-client.go | 263 ------------------------ websocket-server/websocket-server.go | 299 ---------------------------- 5 files changed, 573 insertions(+), 573 deletions(-)
diff --git a/Makefile b/Makefile index b723c86..4882798 100644 --- a/Makefile +++ b/Makefile @@ -9,26 +9,26 @@ GOBUILDFLAGS = # apt-get install gccgo-multilib # GOBUILDFLAGS = -compiler gccgo -gccgoflags "-O3 -m32 -static-libgo"
-all: websocket-server/websocket-server +all: pt-websocket-server/pt-websocket-server
-websocket-server/websocket-server: websocket-server/*.go websocket/*.go - cd websocket-server && go build $(GOBUILDFLAGS) +pt-websocket-server/pt-websocket-server: pt-websocket-server/*.go websocket/*.go + cd pt-websocket-server && go build $(GOBUILDFLAGS)
-websocket-client/websocket-client: websocket-client/*.go - cd websocket-client && go build $(GOBUILDFLAGS) +pt-websocket-client/pt-websocket-client: pt-websocket-client/*.go + cd pt-websocket-client && go build $(GOBUILDFLAGS)
-doc/websocket-server.1: websocket-server/websocket-server +doc/pt-websocket-server.1: pt-websocket-server/pt-websocket-server help2man --no-info --name "WebSocket server pluggable transport" --version-string "$(VERSION)" -o "$@" "$<"
-install: websocket-server/websocket-server +install: pt-websocket-server/pt-websocket-server mkdir -p "$(DESTDIR)$(BINDIR)" - cp -f websocket-server/websocket-server "$(DESTDIR)$(BINDIR)" + cp -f "$<" "$(DESTDIR)$(BINDIR)"
clean: - rm -f websocket-server/websocket-server websocket-client/websocket-client - rm -f doc/websocket-server.1 + rm -f pt-websocket-server/pt-websocket-server pt-websocket-client/pt-websocket-client + rm -f doc/pt-websocket-server.1
fmt: - go fmt ./websocket-server ./websocket-client ./websocket + go fmt ./pt-websocket-server ./pt-websocket-client ./websocket
.PHONY: all install clean fmt diff --git a/pt-websocket-client/pt-websocket-client.go b/pt-websocket-client/pt-websocket-client.go new file mode 100644 index 0000000..fe38c9d --- /dev/null +++ b/pt-websocket-client/pt-websocket-client.go @@ -0,0 +1,263 @@ +// Tor websocket client transport plugin. +// +// Usage in torrc: +// UseBridges 1 +// Bridge websocket X.X.X.X:YYYY +// ClientTransportPlugin websocket exec ./websocket-client +package main + +import ( + "code.google.com/p/go.net/websocket" + "flag" + "fmt" + "io" + "net" + "net/url" + "os" + "os/signal" + "sync" + "syscall" + "time" +) + +import "git.torproject.org/pluggable-transports/goptlib.git" + +var ptInfo pt.ClientInfo + +const ptMethodName = "websocket" +const bufSiz = 1500 + +var logFile = os.Stderr + +// When a connection handler starts, +1 is written to this channel; when it +// ends, -1 is written. +var handlerChan = make(chan int) + +var logMutex sync.Mutex + +func usage() { + fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0]) + fmt.Printf("WebSocket client pluggable transport for Tor.\n") + fmt.Printf("Works only as a managed proxy.\n") + fmt.Printf("\n") + fmt.Printf(" -h, --help show this help.\n") + fmt.Printf(" --log FILE log messages to FILE (default stderr).\n") + fmt.Printf(" --socks ADDR listen for SOCKS on ADDR.\n") +} + +func log(format string, v ...interface{}) { + dateStr := time.Now().Format("2006-01-02 15:04:05") + logMutex.Lock() + defer logMutex.Unlock() + msg := fmt.Sprintf(format, v...) + fmt.Fprintf(logFile, "%s %s\n", dateStr, msg) +} + +func proxy(local *net.TCPConn, ws *websocket.Conn) { + var wg sync.WaitGroup + + wg.Add(2) + + // Local-to-WebSocket read loop. + go func() { + buf := make([]byte, bufSiz) + var err error + for { + n, er := local.Read(buf[:]) + if n > 0 { + ew := websocket.Message.Send(ws, buf[:n]) + if ew != nil { + err = ew + break + } + } + if er != nil { + err = er + break + } + } + if err != nil && err != io.EOF { + log("%s", err) + } + local.CloseRead() + ws.Close() + + wg.Done() + }() + + // WebSocket-to-local read loop. + go func() { + var buf []byte + var err error + for { + er := websocket.Message.Receive(ws, &buf) + if er != nil { + err = er + break + } + n, ew := local.Write(buf) + if ew != nil { + err = ew + break + } + if n != len(buf) { + err = io.ErrShortWrite + break + } + } + if err != nil && err != io.EOF { + log("%s", err) + } + local.CloseWrite() + ws.Close() + + wg.Done() + }() + + wg.Wait() +} + +func handleConnection(conn *pt.SocksConn) error { + defer conn.Close() + + handlerChan <- 1 + defer func() { + handlerChan <- -1 + }() + + var ws *websocket.Conn + + log("SOCKS request for %s", conn.Req.Target) + destAddr, err := net.ResolveTCPAddr("tcp", conn.Req.Target) + if err != nil { + conn.Reject() + return err + } + wsUrl := url.URL{Scheme: "ws", Host: conn.Req.Target} + ws, err = websocket.Dial(wsUrl.String(), "", wsUrl.String()) + if err != nil { + err = conn.Reject() + return err + } + log("WebSocket connection to %s", ws.Config().Location.String()) + defer ws.Close() + err = conn.Grant(destAddr) + if err != nil { + return err + } + + proxy(conn.Conn.(*net.TCPConn), ws) + + return nil +} + +func socksAcceptLoop(ln *pt.SocksListener) error { + defer ln.Close() + for { + socks, err := ln.AcceptSocks() + if err != nil { + if e, ok := err.(*net.OpError); ok && !e.Temporary() { + return err + } + continue + } + go func() { + err := handleConnection(socks) + if err != nil { + log("SOCKS from %s: %s", socks.RemoteAddr(), err) + } + }() + } + return nil +} + +func startListener(addrStr string) (*pt.SocksListener, error) { + ln, err := pt.ListenSocks("tcp", addrStr) + if err != nil { + return nil, err + } + go func() { + err := socksAcceptLoop(ln) + if err != nil { + log("accept: %s", err) + } + }() + return ln, nil +} + +func main() { + var logFilename string + var socksAddrStr string + var err error + + flag.Usage = usage + flag.StringVar(&logFilename, "log", "", "log file to write to") + flag.StringVar(&socksAddrStr, "socks", "127.0.0.1:0", "address on which to listen for SOCKS connections") + flag.Parse() + + if logFilename != "" { + f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error()) + os.Exit(1) + } + logFile = f + } + + log("starting") + ptInfo, err = pt.ClientSetup([]string{ptMethodName}) + if err != nil { + log("error in setup: %s", err) + os.Exit(1) + } + + listeners := make([]net.Listener, 0) + for _, methodName := range ptInfo.MethodNames { + switch methodName { + case ptMethodName: + ln, err := startListener(socksAddrStr) + if err != nil { + pt.CmethodError(ptMethodName, err.Error()) + break + } + pt.Cmethod(ptMethodName, ln.Version(), ln.Addr()) + log("listening on %s", ln.Addr().String()) + listeners = append(listeners, ln) + default: + pt.CmethodError(methodName, "no such method") + } + } + pt.CmethodsDone() + + 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: + } + } + for _, ln := range listeners { + ln.Close() + } + + 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/pt-websocket-server/pt-websocket-server.go b/pt-websocket-server/pt-websocket-server.go new file mode 100644 index 0000000..4120df8 --- /dev/null +++ b/pt-websocket-server/pt-websocket-server.go @@ -0,0 +1,299 @@ +// Tor websocket server transport plugin. +// +// Usage in torrc: +// ExtORPort 6669 +// ServerTransportPlugin websocket exec ./websocket-server --port 9901 +package main + +import ( + "encoding/base64" + "errors" + "flag" + "fmt" + "io" + "net" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" +) + +import "../websocket" + +import "git.torproject.org/pluggable-transports/goptlib.git" + +const ptMethodName = "websocket" +const requestTimeout = 10 * time.Second + +// "4/3+1" accounts for possible base64 encoding. +const maxMessageSize = 64*1024*4/3 + 1 + +var logFile = os.Stderr + +var ptInfo pt.ServerInfo + +// When a connection handler starts, +1 is written to this channel; when it +// ends, -1 is written. +var handlerChan = make(chan int) + +func usage() { + fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0]) + fmt.Printf("WebSocket server pluggable transport for Tor.\n") + fmt.Printf("Works only as a managed proxy.\n") + fmt.Printf("\n") + fmt.Printf(" -h, --help show this help.\n") + fmt.Printf(" --log FILE log messages to FILE (default stderr).\n") + fmt.Printf(" --port PORT listen on PORT (overrides Tor's requested port).\n") +} + +var logMutex sync.Mutex + +func log(format string, v ...interface{}) { + dateStr := time.Now().Format("2006-01-02 15:04:05") + logMutex.Lock() + defer logMutex.Unlock() + msg := fmt.Sprintf(format, v...) + fmt.Fprintf(logFile, "%s %s\n", dateStr, msg) +} + +// An abstraction that makes an underlying WebSocket connection look like an +// io.ReadWriteCloser. It internally takes care of things like base64 encoding +// and decoding. +type webSocketConn struct { + Ws *websocket.WebSocket + Base64 bool + messageBuf []byte +} + +// Implements io.Reader. +func (conn *webSocketConn) Read(b []byte) (n int, err error) { + for len(conn.messageBuf) == 0 { + var m websocket.Message + m, err = conn.Ws.ReadMessage() + if err != nil { + return + } + if m.Opcode == 8 { + err = io.EOF + return + } + if conn.Base64 { + if m.Opcode != 1 { + err = errors.New(fmt.Sprintf("got non-text opcode %d with the base64 subprotocol", m.Opcode)) + return + } + conn.messageBuf = make([]byte, base64.StdEncoding.DecodedLen(len(m.Payload))) + var num int + num, err = base64.StdEncoding.Decode(conn.messageBuf, m.Payload) + if err != nil { + return + } + conn.messageBuf = conn.messageBuf[:num] + } else { + if m.Opcode != 2 { + err = errors.New(fmt.Sprintf("got non-binary opcode %d with no subprotocol", m.Opcode)) + return + } + conn.messageBuf = m.Payload + } + } + + n = copy(b, conn.messageBuf) + conn.messageBuf = conn.messageBuf[n:] + + return +} + +// Implements io.Writer. +func (conn *webSocketConn) Write(b []byte) (n int, err error) { + if conn.Base64 { + buf := make([]byte, base64.StdEncoding.EncodedLen(len(b))) + base64.StdEncoding.Encode(buf, b) + err = conn.Ws.WriteMessage(1, buf) + if err != nil { + return + } + n = len(b) + } else { + err = conn.Ws.WriteMessage(2, b) + n = len(b) + } + return +} + +// Implements io.Closer. +func (conn *webSocketConn) Close() error { + // Ignore any error in trying to write a Close frame. + _ = conn.Ws.WriteFrame(8, nil) + return conn.Ws.Conn.Close() +} + +// Create a new webSocketConn. +func newWebSocketConn(ws *websocket.WebSocket) webSocketConn { + var conn webSocketConn + conn.Ws = ws + conn.Base64 = (ws.Subprotocol == "base64") + return conn +} + +// Copy from WebSocket to socket and vice versa. +func proxy(local *net.TCPConn, conn *webSocketConn) { + var wg sync.WaitGroup + wg.Add(2) + + go func() { + _, err := io.Copy(conn, local) + if err != nil { + log("error copying ORPort to WebSocket") + } + local.CloseRead() + conn.Close() + wg.Done() + }() + go func() { + _, err := io.Copy(local, conn) + if err != nil { + log("error copying WebSocket to ORPort") + } + local.CloseWrite() + conn.Close() + wg.Done() + }() + + wg.Wait() +} + +func webSocketHandler(ws *websocket.WebSocket) { + // Undo timeouts on HTTP request handling. + ws.Conn.SetDeadline(time.Time{}) + conn := newWebSocketConn(ws) + defer conn.Close() + + handlerChan <- 1 + defer func() { + handlerChan <- -1 + }() + + or, err := pt.DialOr(&ptInfo, ws.Conn.RemoteAddr().String(), ptMethodName) + if err != nil { + log("Failed to connect to ORPort: " + err.Error()) + return + } + defer or.Close() + + proxy(or, &conn) +} + +func startListener(addr *net.TCPAddr) (*net.TCPListener, error) { + ln, err := net.ListenTCP("tcp", addr) + if err != nil { + return nil, err + } + go func() { + defer ln.Close() + var config websocket.Config + config.Subprotocols = []string{"base64"} + config.MaxMessageSize = maxMessageSize + s := &http.Server{ + Handler: config.Handler(webSocketHandler), + ReadTimeout: requestTimeout, + } + err = s.Serve(ln) + if err != nil { + log("http.Serve: " + err.Error()) + } + }() + return ln, nil +} + +func main() { + var logFilename string + var port int + + flag.Usage = usage + flag.StringVar(&logFilename, "log", "", "log file to write to") + flag.IntVar(&port, "port", 0, "port to listen on if unspecified by Tor") + flag.Parse() + + if logFilename != "" { + f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error()) + os.Exit(1) + } + logFile = f + } + + log("starting") + var err error + ptInfo, err = pt.ServerSetup([]string{ptMethodName}) + if err != nil { + log("error in setup: %s", err) + os.Exit(1) + } + + listeners := make([]*net.TCPListener, 0) + for _, bindaddr := range ptInfo.Bindaddrs { + // Override tor's requested port (which is 0 if this transport + // has not been run before) with the one requested by the --port + // option. + if port != 0 { + bindaddr.Addr.Port = port + } + + switch bindaddr.MethodName { + case ptMethodName: + ln, err := startListener(bindaddr.Addr) + if err != nil { + pt.SmethodError(bindaddr.MethodName, err.Error()) + break + } + pt.Smethod(bindaddr.MethodName, ln.Addr()) + log("listening on %s", ln.Addr().String()) + listeners = append(listeners, ln) + 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: + } + } + log("Got first signal %q with %d running handlers.", sig, numHandlers) + for _, ln := range listeners { + ln.Close() + } + + if sig == syscall.SIGTERM { + log("Caught signal %q, exiting.", sig) + return + } + + // wait for second signal or no more handlers + sig = nil + for sig == nil && numHandlers != 0 { + select { + case n := <-handlerChan: + numHandlers += n + log("%d remaining handlers.", numHandlers) + case sig = <-sigChan: + } + } + if sig != nil { + log("Got second signal %q with %d running handlers.", sig, numHandlers) + } +} diff --git a/websocket-client/websocket-client.go b/websocket-client/websocket-client.go deleted file mode 100644 index fe38c9d..0000000 --- a/websocket-client/websocket-client.go +++ /dev/null @@ -1,263 +0,0 @@ -// Tor websocket client transport plugin. -// -// Usage in torrc: -// UseBridges 1 -// Bridge websocket X.X.X.X:YYYY -// ClientTransportPlugin websocket exec ./websocket-client -package main - -import ( - "code.google.com/p/go.net/websocket" - "flag" - "fmt" - "io" - "net" - "net/url" - "os" - "os/signal" - "sync" - "syscall" - "time" -) - -import "git.torproject.org/pluggable-transports/goptlib.git" - -var ptInfo pt.ClientInfo - -const ptMethodName = "websocket" -const bufSiz = 1500 - -var logFile = os.Stderr - -// When a connection handler starts, +1 is written to this channel; when it -// ends, -1 is written. -var handlerChan = make(chan int) - -var logMutex sync.Mutex - -func usage() { - fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0]) - fmt.Printf("WebSocket client pluggable transport for Tor.\n") - fmt.Printf("Works only as a managed proxy.\n") - fmt.Printf("\n") - fmt.Printf(" -h, --help show this help.\n") - fmt.Printf(" --log FILE log messages to FILE (default stderr).\n") - fmt.Printf(" --socks ADDR listen for SOCKS on ADDR.\n") -} - -func log(format string, v ...interface{}) { - dateStr := time.Now().Format("2006-01-02 15:04:05") - logMutex.Lock() - defer logMutex.Unlock() - msg := fmt.Sprintf(format, v...) - fmt.Fprintf(logFile, "%s %s\n", dateStr, msg) -} - -func proxy(local *net.TCPConn, ws *websocket.Conn) { - var wg sync.WaitGroup - - wg.Add(2) - - // Local-to-WebSocket read loop. - go func() { - buf := make([]byte, bufSiz) - var err error - for { - n, er := local.Read(buf[:]) - if n > 0 { - ew := websocket.Message.Send(ws, buf[:n]) - if ew != nil { - err = ew - break - } - } - if er != nil { - err = er - break - } - } - if err != nil && err != io.EOF { - log("%s", err) - } - local.CloseRead() - ws.Close() - - wg.Done() - }() - - // WebSocket-to-local read loop. - go func() { - var buf []byte - var err error - for { - er := websocket.Message.Receive(ws, &buf) - if er != nil { - err = er - break - } - n, ew := local.Write(buf) - if ew != nil { - err = ew - break - } - if n != len(buf) { - err = io.ErrShortWrite - break - } - } - if err != nil && err != io.EOF { - log("%s", err) - } - local.CloseWrite() - ws.Close() - - wg.Done() - }() - - wg.Wait() -} - -func handleConnection(conn *pt.SocksConn) error { - defer conn.Close() - - handlerChan <- 1 - defer func() { - handlerChan <- -1 - }() - - var ws *websocket.Conn - - log("SOCKS request for %s", conn.Req.Target) - destAddr, err := net.ResolveTCPAddr("tcp", conn.Req.Target) - if err != nil { - conn.Reject() - return err - } - wsUrl := url.URL{Scheme: "ws", Host: conn.Req.Target} - ws, err = websocket.Dial(wsUrl.String(), "", wsUrl.String()) - if err != nil { - err = conn.Reject() - return err - } - log("WebSocket connection to %s", ws.Config().Location.String()) - defer ws.Close() - err = conn.Grant(destAddr) - if err != nil { - return err - } - - proxy(conn.Conn.(*net.TCPConn), ws) - - return nil -} - -func socksAcceptLoop(ln *pt.SocksListener) error { - defer ln.Close() - for { - socks, err := ln.AcceptSocks() - if err != nil { - if e, ok := err.(*net.OpError); ok && !e.Temporary() { - return err - } - continue - } - go func() { - err := handleConnection(socks) - if err != nil { - log("SOCKS from %s: %s", socks.RemoteAddr(), err) - } - }() - } - return nil -} - -func startListener(addrStr string) (*pt.SocksListener, error) { - ln, err := pt.ListenSocks("tcp", addrStr) - if err != nil { - return nil, err - } - go func() { - err := socksAcceptLoop(ln) - if err != nil { - log("accept: %s", err) - } - }() - return ln, nil -} - -func main() { - var logFilename string - var socksAddrStr string - var err error - - flag.Usage = usage - flag.StringVar(&logFilename, "log", "", "log file to write to") - flag.StringVar(&socksAddrStr, "socks", "127.0.0.1:0", "address on which to listen for SOCKS connections") - flag.Parse() - - if logFilename != "" { - f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error()) - os.Exit(1) - } - logFile = f - } - - log("starting") - ptInfo, err = pt.ClientSetup([]string{ptMethodName}) - if err != nil { - log("error in setup: %s", err) - os.Exit(1) - } - - listeners := make([]net.Listener, 0) - for _, methodName := range ptInfo.MethodNames { - switch methodName { - case ptMethodName: - ln, err := startListener(socksAddrStr) - if err != nil { - pt.CmethodError(ptMethodName, err.Error()) - break - } - pt.Cmethod(ptMethodName, ln.Version(), ln.Addr()) - log("listening on %s", ln.Addr().String()) - listeners = append(listeners, ln) - default: - pt.CmethodError(methodName, "no such method") - } - } - pt.CmethodsDone() - - 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: - } - } - for _, ln := range listeners { - ln.Close() - } - - 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/websocket-server/websocket-server.go b/websocket-server/websocket-server.go deleted file mode 100644 index 4120df8..0000000 --- a/websocket-server/websocket-server.go +++ /dev/null @@ -1,299 +0,0 @@ -// Tor websocket server transport plugin. -// -// Usage in torrc: -// ExtORPort 6669 -// ServerTransportPlugin websocket exec ./websocket-server --port 9901 -package main - -import ( - "encoding/base64" - "errors" - "flag" - "fmt" - "io" - "net" - "net/http" - "os" - "os/signal" - "sync" - "syscall" - "time" -) - -import "../websocket" - -import "git.torproject.org/pluggable-transports/goptlib.git" - -const ptMethodName = "websocket" -const requestTimeout = 10 * time.Second - -// "4/3+1" accounts for possible base64 encoding. -const maxMessageSize = 64*1024*4/3 + 1 - -var logFile = os.Stderr - -var ptInfo pt.ServerInfo - -// When a connection handler starts, +1 is written to this channel; when it -// ends, -1 is written. -var handlerChan = make(chan int) - -func usage() { - fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0]) - fmt.Printf("WebSocket server pluggable transport for Tor.\n") - fmt.Printf("Works only as a managed proxy.\n") - fmt.Printf("\n") - fmt.Printf(" -h, --help show this help.\n") - fmt.Printf(" --log FILE log messages to FILE (default stderr).\n") - fmt.Printf(" --port PORT listen on PORT (overrides Tor's requested port).\n") -} - -var logMutex sync.Mutex - -func log(format string, v ...interface{}) { - dateStr := time.Now().Format("2006-01-02 15:04:05") - logMutex.Lock() - defer logMutex.Unlock() - msg := fmt.Sprintf(format, v...) - fmt.Fprintf(logFile, "%s %s\n", dateStr, msg) -} - -// An abstraction that makes an underlying WebSocket connection look like an -// io.ReadWriteCloser. It internally takes care of things like base64 encoding -// and decoding. -type webSocketConn struct { - Ws *websocket.WebSocket - Base64 bool - messageBuf []byte -} - -// Implements io.Reader. -func (conn *webSocketConn) Read(b []byte) (n int, err error) { - for len(conn.messageBuf) == 0 { - var m websocket.Message - m, err = conn.Ws.ReadMessage() - if err != nil { - return - } - if m.Opcode == 8 { - err = io.EOF - return - } - if conn.Base64 { - if m.Opcode != 1 { - err = errors.New(fmt.Sprintf("got non-text opcode %d with the base64 subprotocol", m.Opcode)) - return - } - conn.messageBuf = make([]byte, base64.StdEncoding.DecodedLen(len(m.Payload))) - var num int - num, err = base64.StdEncoding.Decode(conn.messageBuf, m.Payload) - if err != nil { - return - } - conn.messageBuf = conn.messageBuf[:num] - } else { - if m.Opcode != 2 { - err = errors.New(fmt.Sprintf("got non-binary opcode %d with no subprotocol", m.Opcode)) - return - } - conn.messageBuf = m.Payload - } - } - - n = copy(b, conn.messageBuf) - conn.messageBuf = conn.messageBuf[n:] - - return -} - -// Implements io.Writer. -func (conn *webSocketConn) Write(b []byte) (n int, err error) { - if conn.Base64 { - buf := make([]byte, base64.StdEncoding.EncodedLen(len(b))) - base64.StdEncoding.Encode(buf, b) - err = conn.Ws.WriteMessage(1, buf) - if err != nil { - return - } - n = len(b) - } else { - err = conn.Ws.WriteMessage(2, b) - n = len(b) - } - return -} - -// Implements io.Closer. -func (conn *webSocketConn) Close() error { - // Ignore any error in trying to write a Close frame. - _ = conn.Ws.WriteFrame(8, nil) - return conn.Ws.Conn.Close() -} - -// Create a new webSocketConn. -func newWebSocketConn(ws *websocket.WebSocket) webSocketConn { - var conn webSocketConn - conn.Ws = ws - conn.Base64 = (ws.Subprotocol == "base64") - return conn -} - -// Copy from WebSocket to socket and vice versa. -func proxy(local *net.TCPConn, conn *webSocketConn) { - var wg sync.WaitGroup - wg.Add(2) - - go func() { - _, err := io.Copy(conn, local) - if err != nil { - log("error copying ORPort to WebSocket") - } - local.CloseRead() - conn.Close() - wg.Done() - }() - go func() { - _, err := io.Copy(local, conn) - if err != nil { - log("error copying WebSocket to ORPort") - } - local.CloseWrite() - conn.Close() - wg.Done() - }() - - wg.Wait() -} - -func webSocketHandler(ws *websocket.WebSocket) { - // Undo timeouts on HTTP request handling. - ws.Conn.SetDeadline(time.Time{}) - conn := newWebSocketConn(ws) - defer conn.Close() - - handlerChan <- 1 - defer func() { - handlerChan <- -1 - }() - - or, err := pt.DialOr(&ptInfo, ws.Conn.RemoteAddr().String(), ptMethodName) - if err != nil { - log("Failed to connect to ORPort: " + err.Error()) - return - } - defer or.Close() - - proxy(or, &conn) -} - -func startListener(addr *net.TCPAddr) (*net.TCPListener, error) { - ln, err := net.ListenTCP("tcp", addr) - if err != nil { - return nil, err - } - go func() { - defer ln.Close() - var config websocket.Config - config.Subprotocols = []string{"base64"} - config.MaxMessageSize = maxMessageSize - s := &http.Server{ - Handler: config.Handler(webSocketHandler), - ReadTimeout: requestTimeout, - } - err = s.Serve(ln) - if err != nil { - log("http.Serve: " + err.Error()) - } - }() - return ln, nil -} - -func main() { - var logFilename string - var port int - - flag.Usage = usage - flag.StringVar(&logFilename, "log", "", "log file to write to") - flag.IntVar(&port, "port", 0, "port to listen on if unspecified by Tor") - flag.Parse() - - if logFilename != "" { - f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error()) - os.Exit(1) - } - logFile = f - } - - log("starting") - var err error - ptInfo, err = pt.ServerSetup([]string{ptMethodName}) - if err != nil { - log("error in setup: %s", err) - os.Exit(1) - } - - listeners := make([]*net.TCPListener, 0) - for _, bindaddr := range ptInfo.Bindaddrs { - // Override tor's requested port (which is 0 if this transport - // has not been run before) with the one requested by the --port - // option. - if port != 0 { - bindaddr.Addr.Port = port - } - - switch bindaddr.MethodName { - case ptMethodName: - ln, err := startListener(bindaddr.Addr) - if err != nil { - pt.SmethodError(bindaddr.MethodName, err.Error()) - break - } - pt.Smethod(bindaddr.MethodName, ln.Addr()) - log("listening on %s", ln.Addr().String()) - listeners = append(listeners, ln) - 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: - } - } - log("Got first signal %q with %d running handlers.", sig, numHandlers) - for _, ln := range listeners { - ln.Close() - } - - if sig == syscall.SIGTERM { - log("Caught signal %q, exiting.", sig) - return - } - - // wait for second signal or no more handlers - sig = nil - for sig == nil && numHandlers != 0 { - select { - case n := <-handlerChan: - numHandlers += n - log("%d remaining handlers.", numHandlers) - case sig = <-sigChan: - } - } - if sig != nil { - log("Got second signal %q with %d running handlers.", sig, numHandlers) - } -}
tor-commits@lists.torproject.org