[tor-commits] [flashproxy/master] First draft of websocket-client.
dcf at torproject.org
dcf at torproject.org
Wed Jan 30 05:11:38 UTC 2013
commit fb2697bca0b99bc4771c9b4111f654733ba7f903
Author: David Fifield <david at bamsoftware.com>
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 {}
+}
More information about the tor-commits
mailing list