From 0ef39a5661475d3d414079284b2bbe3f24176f46 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Wed, 7 May 2014 19:47:15 -0700 Subject: [PATCH] Doc comments. --- .../meek-client-torbrowser.go | 33 +++++++-- meek-client/helper.go | 3 +- meek-client/meek-client.go | 68 ++++++++++++++++--- meek-server/meek-server.go | 49 ++++++++++--- .../terminateprocess-buffer.go | 3 +- 5 files changed, 130 insertions(+), 26 deletions(-) diff --git a/meek-client-torbrowser/meek-client-torbrowser.go b/meek-client-torbrowser/meek-client-torbrowser.go index 6ddcf2f..21ef4d9 100644 --- a/meek-client-torbrowser/meek-client-torbrowser.go +++ b/meek-client-torbrowser/meek-client-torbrowser.go @@ -1,12 +1,31 @@ -// Usage: -// meek-client-torbrowser --log meek-client-torbrowser.log -- meek-client --url=https://meek-reflect.appspot.com/ --front=www.google.com --log meek-client.log +// meek-client-torbrowser is an auxiliary program that helps with connecting +// meek-client to meek-http-helper running in Tor Browser. // -// The meek-client-torbrowser program starts a copy of Tor Browser running -// meek-http-helper in a special profile, and then starts meek-client set up to -// use the browser helper. +// Sample usage in torrc (exact paths depend on platform): +// ClientTransportPlugin meek exec ./meek-client-torbrowser --log meek-client-torbrowser.log -- ./meek-client --url=https://meek-reflect.appspot.com/ --front=www.google.com --log meek-client.log +// Everything up to "--" is options for this program. Everything following it is +// a meek-client command line. The command line for running firefox is implicit +// and hardcoded in this program. // -// Arguments to this program are passed unmodified to meek-client, with the -// addition of a --helper option pointing to the browser helper. +// This program, meek-client-torbrowser, starts a copy of firefox under the +// meek-http-helper profile, which must have configured the meek-http-helper +// extension. This program reads the stdout of firefox, looking for a special +// line with the listening port number of the extension, one that looks like +// "meek-http-helper: listen
". The meek-client command is then +// executed as given, except that a --helper option is added that points to the +// port number read from firefox. +// +// This program proxies stdin and stdout to and from meek-client, so it is +// actually meek-client that drives the pluggable transport negotiation with +// tor. +// +// The special --exit-on-stdin-eof is a special workaround for Windows. On +// Windows we don't get a detectable shutdown signal that allows us to kill the +// subprocesses we've started. Instead, use the --exit-on-stdin-eof option and +// run this program inside of terminateprocess-buffer. When +// terminateprocess-buffer is killed, it will close our stdin, and we can exit +// gracefully. --exit-on-stdin-eof and terminateprocess-buffer need to be used +// together. package main import ( diff --git a/meek-client/helper.go b/meek-client/helper.go index 98fd771..49423fb 100644 --- a/meek-client/helper.go +++ b/meek-client/helper.go @@ -29,7 +29,8 @@ type JSONResponse struct { Body []byte `json:"body"` } -// Ask a locally running browser extension to make the request for us. +// Do an HTTP roundtrip through the configured browser extension, using the +// payload data in buf and the request metadata in info. func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error) { s, err := net.DialTCP("tcp", nil, options.HelperAddr) if err != nil { diff --git a/meek-client/meek-client.go b/meek-client/meek-client.go index 229bfd2..b3cce7a 100644 --- a/meek-client/meek-client.go +++ b/meek-client/meek-client.go @@ -1,3 +1,31 @@ +// meek-client is the client transport plugin for the meek pluggable transport. +// +// Sample usage in torrc: +// Bridge meek 0.0.2.0:1 +// ClientTransportPlugin meek exec ./meek-client --url=https://meek-reflect.appspot.com/ --front=www.google.com --log meek-client.log +// The transport ignores the bridge address 0.0.2.0:1 and instead connects to +// the URL given by --url. When --front is given, the domain in the URL is +// replaced by the front domain for the purpose of the DNS lookup, TCP +// connection, and TLS SNI, but the HTTP Host header in the request will be the +// one in --url. (For example, in the configuration above, the connection will +// appear on the outside to be going to www.google.com, but it will actually be +// dispatched to meek-reflect.appspot.com by the Google frontend server.) +// +// Most user configuration can happen either through SOCKS args (i.e., args on a +// Bridge line) or through command line options. SOCKS args take precedence +// per-connection over command line options. For example, this configuration +// using SOCKS args: +// Bridge meek 0.0.2.0:1 url=https://meek-reflect.appspot.com/ front=www.google.com +// ClientTransportPlugin meek exec ./meek-client +// is the same as this one using command line options. +// Bridge meek 0.0.2.0:1 +// ClientTransportPlugin meek exec ./meek-client --url=https://meek-reflect.appspot.com/ --front=www.google.com +// The advantage of SOCKS args is that multiple Bridge lines can have different +// configurations, but it requires a newer tor. +// +// The --helper option prevents this program from doing any network operations +// itself. Rather, it will send all requests through a browser extension that +// makes HTTP requests. package main import ( @@ -22,12 +50,27 @@ import ( import "git.torproject.org/pluggable-transports/goptlib.git" const ( - ptMethodName = "meek" - sessionIdLength = 32 - maxPayloadLength = 0x10000 - initPollInterval = 100 * time.Millisecond - maxPollInterval = 5 * time.Second - pollIntervalMultiplier = 1.5 + ptMethodName = "meek" + // A session ID is a randomly generated string that identifies a + // long-lived session. We split a TCP stream across multiple HTTP + // requests, and those with the same session ID belong to the same + // stream. + sessionIdLength = 32 + // The size of the largest chunk of data we will read from the SOCKS + // port before forwarding it in a request, and the maximum size of a + // body we are willing to handle in a reply. + maxPayloadLength = 0x10000 + // We must poll the server to see if it has anything to send; there is + // no way for the server to push data back to us until we send an HTTP + // request. When a timer expires, we send a request even if it has an + // empty body. The interval starts at this value and then grows. + initPollInterval = 100 * time.Millisecond + // Maximum polling interval. + maxPollInterval = 5 * time.Second + // Geometric increase in the polling interval each time we fail to read + // data. + pollIntervalMultiplier = 1.5 + // Safety limits on interaction with the HTTP helper. maxHelperResponseLength = 10000000 helperReadTimeout = 60 * time.Second helperWriteTimeout = 2 * time.Second @@ -35,6 +78,7 @@ const ( var ptInfo pt.ClientInfo +// Store for command line options. var options struct { URL string Front string @@ -63,6 +107,8 @@ type RequestInfo struct { HTTPProxyURL *url.URL } +// Do an HTTP roundtrip using the payload data in buf and the request metadata +// in info. func roundTripWithHTTP(buf []byte, info *RequestInfo) (*http.Response, error) { tr := http.DefaultTransport if info.HTTPProxyURL != nil { @@ -82,6 +128,8 @@ func roundTripWithHTTP(buf []byte, info *RequestInfo) (*http.Response, error) { return tr.RoundTrip(req) } +// Send the data in buf to the remote URL, wait for a reply, and feed the reply +// body back into conn. func sendRecv(buf []byte, conn net.Conn, info *RequestInfo) (int64, error) { roundTrip := roundTripWithHTTP if options.HelperAddr != nil { @@ -100,6 +148,8 @@ func sendRecv(buf []byte, conn net.Conn, info *RequestInfo) (int64, error) { return io.Copy(conn, io.LimitReader(resp.Body, maxPayloadLength)) } +// Repeatedly read from conn, issue HTTP requests, and write the responses back +// to conn. func copyLoop(conn net.Conn, info *RequestInfo) error { var interval time.Duration @@ -183,6 +233,7 @@ func genSessionId() string { return base64.StdEncoding.EncodeToString(buf) } +// Callback for new SOCKS requests. func handler(conn *pt.SocksConn) error { handlerChan <- 1 defer func() { @@ -190,7 +241,6 @@ func handler(conn *pt.SocksConn) error { }() defer conn.Close() - // Ignore the IP address in the SOCKS request. err := conn.Grant(&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0}) if err != nil { return err @@ -333,7 +383,7 @@ func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - // wait for first signal + // Wait for first signal. sig = nil for sig == nil { select { @@ -352,7 +402,7 @@ func main() { return } - // wait for second signal or no more handlers + // Wait for second signal or no more handlers. sig = nil for sig == nil && numHandlers != 0 { select { diff --git a/meek-server/meek-server.go b/meek-server/meek-server.go index 30964b8..cdbf448 100644 --- a/meek-server/meek-server.go +++ b/meek-server/meek-server.go @@ -1,3 +1,11 @@ +// meek-server is the server transport plugin for the meek pluggable transport. +// It acts as an HTTP server, keeps track of session ids, and forwards received +// data to a local OR port. +// +// Sample usage in torrc: +// ServerTransportPlugin meek exec ./meek-server --port 8443 --cert cert.pem --key key.pem --log meek-server.log +// Plain HTTP usage: +// ServerTransportPlugin meek exec ./meek-server --port 8080 --disable-tls --log meek-server.log package main import ( @@ -20,14 +28,22 @@ import ( import "git.torproject.org/pluggable-transports/goptlib.git" const ( - ptMethodName = "meek" + ptMethodName = "meek" + // Reject session ids shorter than this, as a weak defense against + // client bugs that send an empty session id or something similarly + // likely to collide. minSessionIdLength = 32 - maxPayloadLength = 0x10000 - // How long we try to read something back from the ORPort before returning the - // response. + // The largest request body we are willing to process, and the largest + // chunk of data we'll send back in a response. + maxPayloadLength = 0x10000 + // How long we try to read something back from the OR port before + // returning the response. turnaroundTimeout = 10 * time.Millisecond - // Passed as ReadTimeout and WriteTimeout when constructing the http.Server. - readWriteTimeout = 20 * time.Second + // Passed as ReadTimeout and WriteTimeout when constructing the + // http.Server. + readWriteTimeout = 20 * time.Second + // Cull unused session ids (with their corresponding OR port connection) + // if we haven't seen any activity for this long. maxSessionStaleness = 120 * time.Second ) @@ -45,19 +61,27 @@ func httpInternalServerError(w http.ResponseWriter) { http.Error(w, "Internal server error.\n", http.StatusInternalServerError) } +// Every session id maps to an existing OR port connection, which we keep open +// between received requests. The first time we see a new session id, we create +// a new OR port connection. type Session struct { Or *net.TCPConn LastSeen time.Time } +// Mark a session as having been seen just now. func (session *Session) Touch() { session.LastSeen = time.Now() } +// Is this session old enough to be culled? func (session *Session) IsExpired() bool { return time.Since(session.LastSeen) > maxSessionStaleness } +// There is one state per HTTP listener. In the usual case there is just one +// listener, so there is just one global state. State also serves as the http +// Handler. type State struct { sessionMap map[string]*Session lock sync.Mutex @@ -85,6 +109,7 @@ func (state *State) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } +// Handle a GET request. This doesn't have any purpose apart from diagnostics. func (state *State) Get(w http.ResponseWriter, req *http.Request) { if path.Clean(req.URL.Path) != "/" { http.NotFound(w, req) @@ -95,6 +120,8 @@ func (state *State) Get(w http.ResponseWriter, req *http.Request) { w.Write([]byte("I’m just a happy little web server.\n")) } +// Look up a session by id, or create a new one (with its OR port connection) if +// it doesn't already exist. func (state *State) GetSession(sessionId string, req *http.Request) (*Session, error) { state.lock.Lock() defer state.lock.Unlock() @@ -115,6 +142,8 @@ func (state *State) GetSession(sessionId string, req *http.Request) (*Session, e return session, nil } +// Feed the body of req into the OR port, and write any data read from the OR +// port back to w. func transact(session *Session, w http.ResponseWriter, req *http.Request) error { body := http.MaxBytesReader(w, req.Body, maxPayloadLength+1) _, err := io.Copy(session.Or, body) @@ -140,6 +169,7 @@ func transact(session *Session, w http.ResponseWriter, req *http.Request) error return nil } +// Handle a POST request. Look up the session id and then do a transaction. func (state *State) Post(w http.ResponseWriter, req *http.Request) { sessionId := req.Header.Get("X-Session-Id") if len(sessionId) < minSessionIdLength { @@ -162,6 +192,8 @@ func (state *State) Post(w http.ResponseWriter, req *http.Request) { } } +// Remove a session from the map and closes its corresponding OR port +// connection. Does nothing if the session id is not known. func (state *State) CloseSession(sessionId string) { state.lock.Lock() defer state.lock.Unlock() @@ -173,6 +205,7 @@ func (state *State) CloseSession(sessionId string) { } } +// Loop forever, checking for expired sessions and removing them. func (state *State) ExpireSessions() { for { time.Sleep(maxSessionStaleness / 2) @@ -319,7 +352,7 @@ func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - // wait for first signal + // Wait for first signal. sig = nil for sig == nil { select { @@ -338,7 +371,7 @@ func main() { return } - // wait for second signal or no more handlers + // Wait for second signal or no more handlers. sig = nil for sig == nil && numHandlers != 0 { select { diff --git a/terminateprocess-buffer/terminateprocess-buffer.go b/terminateprocess-buffer/terminateprocess-buffer.go index 16a7297..f826159 100644 --- a/terminateprocess-buffer/terminateprocess-buffer.go +++ b/terminateprocess-buffer/terminateprocess-buffer.go @@ -1,4 +1,5 @@ -// This program is designed to sit between tor and a transport plugin on +// The terminateprocess-buffer program is designed to act as a +// TerminateProcess-absorbing buffer between tor and a transport plugin on // Windows. On Windows, transport plugins are killed with a TerminateProcess, // which doesn't give them a chance to clean up before exiting. // https://trac.torproject.org/projects/tor/ticket/9330