commit 75eff440ccef95f818ac16ae6b327032cd8d98aa
Author: David Fifield <david(a)bamsoftware.com>
Date: Mon Nov 26 21:32:11 2012 -0800
Documentation comments.
---
websocket-transport/pt.go | 121 ++++++++++++++++++++++++++++----------
websocket-transport/socks.go | 6 ++
websocket-transport/websocket.go | 74 +++++++++++++++++++----
3 files changed, 156 insertions(+), 45 deletions(-)
diff --git a/websocket-transport/pt.go b/websocket-transport/pt.go
index f8760d3..8e74d13 100644
--- a/websocket-transport/pt.go
+++ b/websocket-transport/pt.go
@@ -1,3 +1,27 @@
+// Tor pluggable transports library.
+//
+// Sample client usage:
+//
+// PtClientSetup([]string{"foo"}
+// ln, err := startSocksListener()
+// if err != nil {
+// panic(err.Error())
+// }
+// PtCmethod("foo", "socks4", ln.Addr())
+// PtCmethodsDone()
+//
+// Sample server usage:
+//
+// info := PtServerSetup([]string{"foo", "bar"})
+// for _, bindAddr := range info.BindAddrs {
+// ln, err := startListener(bindAddr.Addr)
+// if err != nil {
+// panic(err.Error())
+// }
+// PtSmethod(bindAddr.MethodName, ln.Addr())
+// }
+// PtSmethodsDone()
+
package main
import (
@@ -35,6 +59,8 @@ func escape(s string) string {
return buf.String()
}
+// Print a pluggable transports protocol line to stdout. The line consists of an
+// unescaped keyword, followed by any number of escaped strings.
func PtLine(keyword string, v ...string) {
var buf bytes.Buffer
buf.WriteString(keyword)
@@ -44,21 +70,56 @@ func PtLine(keyword string, v ...string) {
fmt.Println(buf.String())
}
+// All of the Pt*Error functions call os.Exit(1).
+
+// Emit an ENV-ERROR with explanation text.
func PtEnvError(msg string) {
PtLine("ENV-ERROR", msg)
os.Exit(1)
}
+// Emit a VERSION-ERROR with explanation text.
func PtVersionError(msg string) {
PtLine("VERSION-ERROR", msg)
os.Exit(1)
}
+// Emit a CMETHOD-ERROR with explanation text.
func PtCmethodError(methodName, msg string) {
PtLine("CMETHOD-ERROR", methodName, msg)
os.Exit(1)
}
+// Emit an SMETHOD-ERROR with explanation text.
+func PtSmethodError(methodName, msg string) {
+ PtLine("SMETHOD-ERROR", methodName, msg)
+ os.Exit(1)
+}
+
+// Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for
+// each listening client SOCKS port.
+func PtCmethod(name string, socks string, addr net.Addr) {
+ PtLine("CMETHOD", name, socks, addr.String())
+}
+
+// Emit a CMETHODS DONE line. Call this after opening all client listeners.
+func PtCmethodsDone() {
+ PtLine("CMETHODS", "DONE")
+}
+
+// Emit an SMETHOD line. Call this once for each listening server port.
+func PtSmethod(name string, addr net.Addr) {
+ PtLine("SMETHOD", name, addr.String())
+}
+
+// Emit an SMETHODS DONE line. Call this after opening all server listeners.
+func PtSmethodsDone() {
+ PtLine("SMETHODS", "DONE")
+}
+
+// Get a pluggable transports version offered by Tor and understood by us, if
+// any. The only version we understand is "1". This function reads the
+// environment variable TOR_PT_MANAGED_TRANSPORT_VER.
func PtGetManagedTransportVer() string {
const transportVersion = "1"
for _, offered := range strings.Split(getenvRequired("TOR_PT_MANAGED_TRANSPORT_VER"), ",") {
@@ -69,14 +130,17 @@ func PtGetManagedTransportVer() string {
return ""
}
-func PtGetClientTransports(supported []string) []string {
+// Get the intersection of the method names offered by Tor and those in
+// methodNames. This function reads the environment variable
+// TOR_PT_CLIENT_TRANSPORTS.
+func PtGetClientTransports(methodNames []string) []string {
clientTransports := getenvRequired("TOR_PT_CLIENT_TRANSPORTS")
if clientTransports == "*" {
- return supported
+ return methodNames
}
result := make([]string, 0)
for _, requested := range strings.Split(clientTransports, ",") {
- for _, methodName := range supported {
+ for _, methodName := range methodNames {
if requested == methodName {
result = append(result, methodName)
break
@@ -86,14 +150,9 @@ func PtGetClientTransports(supported []string) []string {
return result
}
-func PtCmethod(name string, socks string, addr net.Addr) {
- PtLine("CMETHOD", name, socks, addr.String())
-}
-
-func PtCmethodsDone() {
- PtLine("CMETHODS", "DONE")
-}
-
+// Check the client pluggable transports environments, emitting an error message
+// and exiting the program if any error is encountered. Returns a subset of
+// methodNames requested by Tor.
func PtClientSetup(methodNames []string) []string {
ver := PtGetManagedTransportVer()
if ver == "" {
@@ -111,19 +170,14 @@ func PtClientSetup(methodNames []string) []string {
return methods
}
-func PtSmethodError(methodName, msg string) {
- PtLine("SMETHOD-ERROR", methodName, msg)
- os.Exit(1)
-}
-
-func PtSmethod(name string, addr net.Addr) {
- PtLine("SMETHOD", name, addr.String())
-}
-
-func PtSmethodsDone() {
- PtLine("SMETHODS", "DONE")
+// A combination of a method name and an address, as extracted from
+// TOR_PT_SERVER_BINDADDR.
+type PtBindAddr struct {
+ MethodName string
+ Addr *net.TCPAddr
}
+// Resolve an address string into a net.TCPAddr.
func resolveBindAddr(bindAddr string) (*net.TCPAddr, error) {
addr, err := net.ResolveTCPAddr("tcp", bindAddr)
if err == nil {
@@ -140,16 +194,13 @@ func resolveBindAddr(bindAddr string) (*net.TCPAddr, error) {
return net.ResolveTCPAddr("tcp", bindAddr)
}
-type PtBindAddr struct {
- MethodName string
- Addr *net.TCPAddr
-}
-
-func filterBindAddrs(addrs []PtBindAddr, supported []string) []PtBindAddr {
+// Return a new slice, the members of which are those members of addrs having a
+// MethodName in methodsNames.
+func filterBindAddrs(addrs []PtBindAddr, methodNames []string) []PtBindAddr {
var result []PtBindAddr
for _, ba := range addrs {
- for _, methodName := range supported {
+ for _, methodName := range methodNames {
if ba.MethodName == methodName {
result = append(result, ba)
break
@@ -162,8 +213,8 @@ func filterBindAddrs(addrs []PtBindAddr, supported []string) []PtBindAddr {
// Return a map from method names to bind addresses. The map is the contents of
// TOR_PT_SERVER_BINDADDR, with keys filtered by TOR_PT_SERVER_TRANSPORTS, and
-// further filtered by methods that we know.
-func PtGetServerBindAddrs(supported []string) []PtBindAddr {
+// further filtered by the methods in methodNames.
+func PtGetServerBindAddrs(methodNames []string) []PtBindAddr {
var result []PtBindAddr
// Get the list of all requested bindaddrs.
@@ -191,16 +242,22 @@ func PtGetServerBindAddrs(supported []string) []PtBindAddr {
}
// Finally filter by what we understand.
- result = filterBindAddrs(result, supported)
+ result = filterBindAddrs(result, methodNames)
return result
}
+// This structure is returned by PtServerSetup. It consists of a list of
+// PtBindAddrs, along with a single address for the ORPort.
type PtServerInfo struct {
BindAddrs []PtBindAddr
OrAddr *net.TCPAddr
}
+// Check the server pluggable transports environments, emitting an error message
+// and exiting the program if any error is encountered. Resolves the various
+// requested bind addresses and the server ORPort. Returns a PtServerInfo
+// struct.
func PtServerSetup(methodNames []string) PtServerInfo {
var info PtServerInfo
var err error
diff --git a/websocket-transport/socks.go b/websocket-transport/socks.go
index 061869e..e8ef51f 100644
--- a/websocket-transport/socks.go
+++ b/websocket-transport/socks.go
@@ -1,3 +1,5 @@
+// SOCKS4a server library.
+
package main
import (
@@ -16,6 +18,7 @@ const (
socksRequestFailed = 0x5b
)
+// Read a SOCKS4a connect request. Returns a "host:port" string.
func ReadSocks4aConnect(s io.Reader) (string, error) {
r := bufio.NewReader(s)
@@ -57,6 +60,7 @@ func ReadSocks4aConnect(s io.Reader) (string, error) {
return fmt.Sprintf("%s:%d", host, port), nil
}
+// Send a SOCKS4a response with the given code and address.
func SendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error {
var resp [8]byte
resp[0] = socksResponseVersion
@@ -73,10 +77,12 @@ func SendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error {
var emptyAddr = net.TCPAddr{net.IPv4(0, 0, 0, 0), 0}
+// Send a SOCKS4a response code 0x5a.
func SendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error {
return SendSocks4aResponse(w, socksRequestGranted, addr)
}
+// Send a SOCKS4a response code 0x5b (with an all-zero address).
func SendSocks4aResponseFailed(w io.Writer) error {
return SendSocks4aResponse(w, socksRequestFailed, &emptyAddr)
}
diff --git a/websocket-transport/websocket.go b/websocket-transport/websocket.go
index cdb4b5d..7aca736 100644
--- a/websocket-transport/websocket.go
+++ b/websocket-transport/websocket.go
@@ -1,3 +1,19 @@
+// WebSocket library. Only the RFC 6455 variety of WebSocket is supported.
+//
+// Reading and writing is strictly per-frame (or per-message). There is no way
+// to partially read a frame. WebsocketConfig.MaxMessageSize affords control of
+// the maximum buffering of messages.
+//
+// Example usage:
+//
+// func doSomething(ws *Websocket) {
+// }
+// var config WebsocketConfig
+// config.Subprotocols = []string{"base64"}
+// config.MaxMessageSize = 2500
+// http.Handle("/", config.Handler(doSomething))
+// err = http.ListenAndServe(":8080", nil)
+
package main
import (
@@ -15,11 +31,17 @@ import (
"strings"
)
+// Settings for potential WebSocket connections. Subprotocols is a list of
+// supported subprotocols as in RFC 6455 section 1.9. When answering client
+// requests, the first of the client's requests subprotocols that is also in
+// this list (if any) will be used as the subprotocol for the connection.
+// MaxMessageSize is a limit on buffering messages.
type WebsocketConfig struct {
Subprotocols []string
MaxMessageSize uint64
}
+// Return the WebSocket's maximum message size, or a default maximum size.
func (config *WebsocketConfig) maxMessageSize() uint64 {
if config.MaxMessageSize == 0 {
return 64000
@@ -27,37 +49,47 @@ func (config *WebsocketConfig) maxMessageSize() uint64 {
return config.MaxMessageSize
}
-type Websocket struct {
- Conn net.Conn
- Bufrw *bufio.ReadWriter
- // Whether we are a client or a server implications for masking.
- IsClient bool
- MaxMessageSize uint64
- Subprotocol string
- messageBuf bytes.Buffer
-}
-
+// Representation of a WebSocket frame. The Payload is always without masking.
type WebsocketFrame struct {
Fin bool
Opcode byte
Payload []byte
}
+// Return true iff the frame's opcode says it is a control frame.
func (frame *WebsocketFrame) IsControl() bool {
return (frame.Opcode & 0x08) != 0
}
+// Representation of a WebSocket message. The Payload is always without masking.
type WebsocketMessage struct {
Opcode byte
Payload []byte
}
+// A WebSocket connection after hijacking from HTTP.
+type Websocket struct {
+ // Conn and ReadWriter from http.ResponseWriter.Hijack.
+ Conn net.Conn
+ Bufrw *bufio.ReadWriter
+ // Whether we are a client or a server has implications for masking.
+ IsClient bool
+ // Set from a parent WebsocketConfig.
+ MaxMessageSize int
+ // The single selected subprotocol after negotiation, or "".
+ Subprotocol string
+ // Buffer for message payloads, which may be interrupted by control
+ // messages.
+ messageBuf bytes.Buffer
+}
+
func applyMask(payload []byte, maskKey [4]byte) {
for i, _ := range payload {
payload[i] = payload[i] ^ maskKey[i%4]
}
}
+// Read a single frame from the WebSocket.
func (ws *Websocket) ReadFrame() (frame WebsocketFrame, err error) {
var b byte
err = binary.Read(ws.Bufrw, binary.BigEndian, &b)
@@ -122,6 +154,10 @@ func (ws *Websocket) ReadFrame() (frame WebsocketFrame, err error) {
return frame, nil
}
+// Read a single message from the WebSocket. Multiple fragmented frames are
+// combined into a single message before being returned. Non-control messages
+// may be interrupted by control frames. The control frames are returned as
+// individual messages before the message that they interrupt.
func (ws *Websocket) ReadMessage() (message WebsocketMessage, err error) {
var opcode byte = 0
for {
@@ -168,7 +204,8 @@ func (ws *Websocket) ReadMessage() (message WebsocketMessage, err error) {
return message, nil
}
-// Destructively masks payload in place if ws.IsClient.
+// Write a single frame to the WebSocket stream. Destructively masks payload in
+// place if ws.IsClient. Frames are always unfragmented.
func (ws *Websocket) WriteFrame(opcode byte, payload []byte) (err error) {
if opcode >= 16 {
err = errors.New(fmt.Sprintf("opcode %d is >= 16", opcode))
@@ -212,10 +249,14 @@ func (ws *Websocket) WriteFrame(opcode byte, payload []byte) (err error) {
return
}
+// Write a single message to the WebSocket stream. Destructively masks payload
+// in place if ws.IsClient. Messages are always sent as a single unfragmented
+// frame.
func (ws *Websocket) WriteMessage(opcode byte, payload []byte) (err error) {
return ws.WriteFrame(opcode, payload)
}
+// Split a strong on commas and trim whitespace.
func commaSplit(s string) []string {
var result []string
if strings.TrimSpace(s) == "" {
@@ -227,6 +268,7 @@ func commaSplit(s string) []string {
return result
}
+// Returns true iff one of the strings in haystack is needle.
func containsCase(haystack []string, needle string) bool {
for _, e := range haystack {
if strings.ToLower(e) == strings.ToLower(needle) {
@@ -236,6 +278,7 @@ func containsCase(haystack []string, needle string) bool {
return false
}
+// One-step SHA-1 hash of a string.
func sha1Hash(data string) []byte {
h := sha1.New()
h.Write([]byte(data))
@@ -250,11 +293,15 @@ func httpError(w http.ResponseWriter, bufrw *bufio.ReadWriter, code int) {
bufrw.Flush()
}
+// An implementation of http.Handler with a WebsocketConfig. The ServeHTTP
+// function calls websocketCallback assuming WebSocket HTTP negotiation is
+// successful.
type WebSocketHTTPHandler struct {
Config *WebsocketConfig
WebsocketCallback func(*Websocket)
}
+// Implements the http.Handler interface.
func (handler *WebSocketHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
conn, bufrw, err := w.(http.Hijacker).Hijack()
if err != nil {
@@ -362,6 +409,7 @@ func (handler *WebSocketHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.
handler.WebsocketCallback(&ws)
}
-func (config *WebsocketConfig) Handler(f func(*Websocket)) http.Handler {
- return &WebSocketHTTPHandler{config, f}
+// Return an http.Handler with the given callback function.
+func (config *WebsocketConfig) Handler(callback func(*Websocket)) http.Handler {
+ return &WebSocketHTTPHandler{config, callback}
}