Skip to content

Commit

Permalink
Doc comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
David Fifield committed May 8, 2014
1 parent dfea22d commit 0ef39a5
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 26 deletions.
33 changes: 26 additions & 7 deletions 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 <address>". 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 (
Expand Down
3 changes: 2 additions & 1 deletion meek-client/helper.go
Expand Up @@ -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 {
Expand Down
68 changes: 59 additions & 9 deletions 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 (
Expand All @@ -22,19 +50,35 @@ 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
)

var ptInfo pt.ClientInfo

// Store for command line options.
var options struct {
URL string
Front string
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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

Expand Down Expand Up @@ -183,14 +233,14 @@ func genSessionId() string {
return base64.StdEncoding.EncodeToString(buf)
}

// Callback for new SOCKS requests.
func handler(conn *pt.SocksConn) error {
handlerChan <- 1
defer func() {
handlerChan <- -1
}()

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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
49 changes: 41 additions & 8 deletions 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 (
Expand All @@ -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
)

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion 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
Expand Down

0 comments on commit 0ef39a5

Please sign in to comment.