commit fb2697bca0b99bc4771c9b4111f654733ba7f903
Author: David Fifield <[email protected]>
Date:   Sat Nov 10 21:32:01 2012 -0800

    First draft of websocket-client.
---
 .gitignore                              |    1 +
 websocket-transport/Makefile            |   16 ++++
 websocket-transport/pt.go               |  106 +++++++++++++++++++++++++++
 websocket-transport/socks.go            |   82 +++++++++++++++++++++
 websocket-transport/websocket-client.go |  120 +++++++++++++++++++++++++++++++
 5 files changed, 325 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore
index 493cc74..24f143b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 /facilitator.log
 *.pyc
 /dist
+/websocket-transport/websocket-client
diff --git a/websocket-transport/Makefile b/websocket-transport/Makefile
new file mode 100644
index 0000000..14ffac0
--- /dev/null
+++ b/websocket-transport/Makefile
@@ -0,0 +1,16 @@
+PROGRAMS = websocket-client
+
+all: $(PROGRAMS)
+
+websocket-client: websocket-client.go socks.go pt.go
+
+%: %.go
+       go build -o $@ $^
+
+clean:
+       rm -f $(PROGRAMS)
+
+fmt:
+       go fmt
+
+.PHONY: all clean fmt
diff --git a/websocket-transport/pt.go b/websocket-transport/pt.go
new file mode 100644
index 0000000..9eaed84
--- /dev/null
+++ b/websocket-transport/pt.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+       "bytes"
+       "fmt"
+       "net"
+       "os"
+       "strings"
+)
+
+// Escape a string so it contains no byte values over 127 and doesn't contain
+// any of the characters '\x00', '\n', or '\\'.
+func escape(s string) string {
+       var buf bytes.Buffer
+       for _, b := range []byte(s) {
+               if b == '\n' {
+                       buf.WriteString("\\n")
+               } else if b == '\\' {
+                       buf.WriteString("\\\\")
+               } else if 0 < b && b < 128 {
+                       buf.WriteByte(b)
+               } else {
+                       fmt.Fprintf(&buf, "\\x%02x", b)
+               }
+       }
+       return buf.String()
+}
+
+func ptLine(keyword string, v ...string) {
+       var buf bytes.Buffer
+       buf.WriteString(keyword)
+       for _, x := range v {
+               buf.WriteString(" " + escape(x))
+       }
+       fmt.Println(buf.String())
+}
+
+func ptEnvError(msg string) {
+       ptLine("ENV-ERROR", msg)
+       os.Exit(1)
+}
+
+func ptVersionError(msg string) {
+       ptLine("VERSION-ERROR", msg)
+       os.Exit(1)
+}
+
+func ptCmethodError(methodName, msg string) {
+       ptLine("CMETHOD-ERROR", methodName, msg)
+       os.Exit(1)
+}
+
+func ptGetManagedTransportVer() string {
+       const transportVersion = "1"
+       for _, offered := range 
strings.Split(os.Getenv("TOR_PT_MANAGED_TRANSPORT_VER"), ",") {
+               if offered == transportVersion {
+                       return offered
+               }
+       }
+       return ""
+}
+
+func ptGetClientTransports(supported []string) []string {
+       clientTransports := os.Getenv("TOR_PT_CLIENT_TRANSPORTS")
+       if clientTransports == "" {
+               ptEnvError("no TOR_PT_CLIENT_TRANSPORTS environment variable")
+       }
+       if clientTransports == "*" {
+               return supported
+       }
+
+       result := make([]string, 0)
+       for _, requested := range strings.Split(clientTransports, ",") {
+               for _, methodName := range supported {
+                       if requested == methodName {
+                               result = append(result, methodName)
+                       }
+               }
+       }
+       return result
+}
+
+func ptCmethod(name string, socks string, addr net.Addr) {
+       ptLine("CMETHOD", name, socks, addr.String())
+}
+
+func ptCmethodsDone() {
+       ptLine("CMETHODS", "DONE")
+}
+
+func ptClientSetup(methodNames []string) []string {
+       ver := ptGetManagedTransportVer()
+       if ver == "" {
+               ptVersionError("no-version")
+       } else {
+               ptLine("VERSION", ver)
+       }
+
+       methods := ptGetClientTransports(methodNames)
+       if len(methods) == 0 {
+               ptCmethodsDone()
+               os.Exit(1)
+       }
+
+       return methods
+}
diff --git a/websocket-transport/socks.go b/websocket-transport/socks.go
new file mode 100644
index 0000000..33df918
--- /dev/null
+++ b/websocket-transport/socks.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+       "bufio"
+       "errors"
+       "fmt"
+       "io"
+       "net"
+)
+
+const (
+       socksVersion         = 0x04
+       socksCmdConnect      = 0x01
+       socksResponseVersion = 0x00
+       socksRequestGranted  = 0x5a
+       socksRequestFailed   = 0x5b
+)
+
+func readSocks4aConnect(s io.Reader) (string, error) {
+       r := bufio.NewReader(s)
+
+       var h [8]byte
+       n, err := io.ReadFull(r, h[:])
+       if err != nil {
+               return "", errors.New(fmt.Sprintf("after %d bytes of SOCKS 
header: %s", n, err))
+       }
+       if h[0] != socksVersion {
+               return "", errors.New(fmt.Sprintf("SOCKS header had version 
0x%02x, not 0x%02x", h[0], socksVersion))
+       }
+       if h[1] != socksCmdConnect {
+               return "", errors.New(fmt.Sprintf("SOCKS header had command 
0x%02x, not 0x%02x", h[1], socksCmdConnect))
+       }
+
+       _, err = r.ReadBytes('\x00')
+       if err != nil {
+               return "", errors.New(fmt.Sprintf("reading SOCKS userid: %s", 
n, err))
+       }
+
+       var port int
+       var host string
+
+       port = int(h[2])<<8 | int(h[3])<<0
+       if h[4] == 0 && h[5] == 0 && h[6] == 0 && h[7] != 0 {
+               hostBytes, err := r.ReadBytes('\x00')
+               if err != nil {
+                       return "", errors.New(fmt.Sprintf("reading SOCKS4a 
destination: %s", err))
+               }
+               host = string(hostBytes[:len(hostBytes)-1])
+       } else {
+               host = net.IPv4(h[4], h[5], h[6], h[7]).String()
+       }
+
+       if r.Buffered() != 0 {
+               return "", errors.New(fmt.Sprintf("%d bytes left after SOCKS 
header", r.Buffered()))
+       }
+
+       return fmt.Sprintf("%s:%d", host, port), nil
+}
+
+func sendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error {
+       var resp [8]byte
+       resp[0] = socksResponseVersion
+       resp[1] = code
+       resp[2] = byte((addr.Port >> 8) & 0xff)
+       resp[3] = byte((addr.Port >> 0) & 0xff)
+       resp[4] = addr.IP[0]
+       resp[5] = addr.IP[1]
+       resp[6] = addr.IP[2]
+       resp[7] = addr.IP[3]
+       _, err := w.Write(resp[:])
+       return err
+}
+
+var emptyAddr = net.TCPAddr{net.IPv4(0, 0, 0, 0), 0}
+
+func sendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error {
+       return sendSocks4aResponse(w, socksRequestGranted, addr)
+}
+
+func sendSocks4aResponseFailed(w io.Writer) error {
+       return sendSocks4aResponse(w, socksRequestFailed, &emptyAddr)
+}
diff --git a/websocket-transport/websocket-client.go 
b/websocket-transport/websocket-client.go
new file mode 100644
index 0000000..5c1c882
--- /dev/null
+++ b/websocket-transport/websocket-client.go
@@ -0,0 +1,120 @@
+package main
+
+import (
+       "code.google.com/p/go.net/websocket"
+       "fmt"
+       "io"
+       "net"
+       "net/url"
+       "os"
+       "time"
+)
+
+const socksTimeout = 2
+
+func logDebug(format string, v ...interface{}) {
+       fmt.Fprintf(os.Stderr, format+"\n", v...)
+}
+
+func proxy(local *net.TCPConn, ws *websocket.Conn) error {
+       // Local-to-WebSocket read loop.
+       go func() {
+               n, err := io.Copy(ws, local)
+               logDebug("end local-to-WebSocket %d %s", n, err)
+       }()
+
+       // WebSocket-to-local read loop.
+       go func() {
+               n, err := io.Copy(local, ws)
+               logDebug("end WebSocket-to-local %d %s", n, err)
+       }()
+
+       select {}
+       return nil
+}
+
+func handleConnection(conn *net.TCPConn) error {
+       defer conn.Close()
+
+       conn.SetDeadline(time.Now().Add(socksTimeout * time.Second))
+       dest, err := readSocks4aConnect(conn)
+       if err != nil {
+               sendSocks4aResponseFailed(conn)
+               return err
+       }
+       // Disable deadline.
+       conn.SetDeadline(time.Time{})
+       logDebug("SOCKS request for %s", dest)
+
+       // We need the parsed IP and port for the SOCKS reply.
+       destAddr, err := net.ResolveTCPAddr("tcp", dest)
+       if err != nil {
+               sendSocks4aResponseFailed(conn)
+               return err
+       }
+
+       wsUrl := url.URL{Scheme: "ws", Host: dest}
+       ws, err := websocket.Dial(wsUrl.String(), "", wsUrl.String())
+       if err != nil {
+               sendSocks4aResponseFailed(conn)
+               return err
+       }
+       defer ws.Close()
+       logDebug("WebSocket connection to %s", ws.Config().Location.String())
+
+       sendSocks4aResponseGranted(conn, destAddr)
+
+       return proxy(conn, ws)
+}
+
+func socksAcceptLoop(ln *net.TCPListener) error {
+       for {
+               socks, err := ln.AcceptTCP()
+               if err != nil {
+                       return err
+               }
+               go func() {
+                       err := handleConnection(socks)
+                       if err != nil {
+                               logDebug("SOCKS from %s: %s", 
socks.RemoteAddr(), err)
+                       }
+               }()
+       }
+       return nil
+}
+
+func startListener(addrStr string) (*net.TCPListener, error) {
+       addr, err := net.ResolveTCPAddr("tcp", addrStr)
+       if err != nil {
+               return nil, err
+       }
+       ln, err := net.ListenTCP("tcp", addr)
+       if err != nil {
+               return nil, err
+       }
+       go func() {
+               err := socksAcceptLoop(ln)
+               if err != nil {
+                       logDebug("accept: %s", err)
+               }
+       }()
+       return ln, nil
+}
+
+func main() {
+       const ptMethodName = "websocket"
+       var socksAddrStrs = [...]string{"127.0.0.1:0", "[::1]:0"}
+
+       ptClientSetup([]string{ptMethodName})
+
+       for _, socksAddrStr := range socksAddrStrs {
+               ln, err := startListener(socksAddrStr)
+               if err != nil {
+                       ptCmethodError(ptMethodName, err.Error())
+               }
+               ptCmethod(ptMethodName, "socks4", ln.Addr())
+       }
+       ptCmethodsDone()
+
+       select {}
+}



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to