[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