[tor-commits] [obfs4/master] Add the "meek_lite" transport, which does what one would expect.

yawning at torproject.org yawning at torproject.org
Thu Oct 29 17:33:46 UTC 2015


commit 611205be681322883a4d73dd00fcb13c4352fe53
Author: Yawning Angel <yawning at torproject.org>
Date:   Thu Oct 29 17:29:21 2015 +0000

    Add the "meek_lite" transport, which does what one would expect.
    
    This is a meek client only implementation, with the following
    differences with dcf's `meek-client`:
    
     - It is named `meek_lite` to differentiate it from the real thing.
     - It does not support using an external helper to normalize TLS
       signatures, so adversaries can look for someone using the Go
       TLS library to do HTTP.
     - It does the right thing with TOR_PT_PROXY, even when a helper is
       not present.
    
    Most of the credit goes to dcf, who's code I librerally cribbed and
    stole.  It is intended primarily as a "better than nothina" option
    for enviornments that do not or can not presently use an external
    Firefox helper.
---
 ChangeLog                   |    4 +
 doc/obfs4proxy.1            |    4 +-
 transports/meeklite/base.go |   89 +++++++++++
 transports/meeklite/meek.go |  358 +++++++++++++++++++++++++++++++++++++++++++
 transports/transports.go    |    2 +
 5 files changed, 455 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 774be88..1d04d8d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,10 @@
 Changes in version 0.0.6 - UNRELEASED:
  - Delay transport factory initialization till after logging has been
    initialized.
+ - Add a meek client implementation (WARNING: Does not support using a
+   helper to normalize TLS signatures).  The brave people that want to use
+   it can do so as the "meek_lite" transport, with identical bridge lines
+   to the real meek-client.
 
 Changes in version 0.0.5 - 2015-04-15:
  - Go vet/fmt fixes, and misc. code cleanups.  Patches by mvdan.
diff --git a/doc/obfs4proxy.1 b/doc/obfs4proxy.1
index 9fb5f28..13b89fd 100644
--- a/doc/obfs4proxy.1
+++ b/doc/obfs4proxy.1
@@ -1,4 +1,4 @@
-.TH OBFS4PROXY 1 "2015-04-03"
+.TH OBFS4PROXY 1 "2015-10-29"
 .SH NAME
 obfs4proxy \- pluggable transport proxy for Tor, implementing obfs4
 .SH SYNOPSIS
@@ -12,7 +12,7 @@ will see innocent-looking transformed traffic instead of the actual Tor
 traffic.
 .PP
 obfs4proxy implements the obfuscation protocols obfs2, obfs3, 
-ScrambleSuit (client only) and obfs4.
+ScrambleSuit (client only), meek (client only) and obfs4.
 .PP
 obfs4proxy is currently only supported as a managed pluggable transport
 spawned as a helper process via the \fBtor\fR daemon.
diff --git a/transports/meeklite/base.go b/transports/meeklite/base.go
new file mode 100644
index 0000000..2a4cf80
--- /dev/null
+++ b/transports/meeklite/base.go
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2015, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package meeklite provides an implementation of the Meek circumvention
+// protocol.  Only a client implementation is provided, and no effort is
+// made to normalize the TLS fingerprint.
+//
+// It borrows quite liberally from the real meek-client code.
+package meeklite
+
+import (
+	"fmt"
+	"net"
+
+	"git.torproject.org/pluggable-transports/goptlib.git"
+	"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
+)
+
+const transportName = "meek_lite"
+
+// Transport is the Meek implementation of the base.Transport interface.
+type Transport struct{}
+
+// Name returns the name of the Meek transport protocol.
+func (t *Transport) Name() string {
+	return transportName
+}
+
+// ClientFactory returns a new meekClientFactory instance.
+func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
+	cf := &meekClientFactory{transport: t}
+	return cf, nil
+}
+
+// ServerFactory will one day return a new meekServerFactory instance.
+func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
+	// TODO: Fill this in eventually, though for servers people should
+	// just use the real thing.
+	return nil, fmt.Errorf("server not supported")
+}
+
+type meekClientFactory struct {
+	transport base.Transport
+}
+
+func (cf *meekClientFactory) Transport() base.Transport {
+	return cf.transport
+}
+
+func (cf *meekClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
+	return newClientArgs(args)
+}
+
+func (cf *meekClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
+	// Validate args before opening outgoing connection.
+	ca, ok := args.(*meekClientArgs)
+	if !ok {
+		return nil, fmt.Errorf("invalid argument type for args")
+	}
+
+	return newMeekConn(network, addr, dialFn, ca)
+}
+
+var _ base.ClientFactory = (*meekClientFactory)(nil)
+var _ base.Transport = (*Transport)(nil)
diff --git a/transports/meeklite/meek.go b/transports/meeklite/meek.go
new file mode 100644
index 0000000..5842704
--- /dev/null
+++ b/transports/meeklite/meek.go
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2015, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package meeklite
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	gourl "net/url"
+	"runtime"
+	"sync"
+	"time"
+
+	"git.torproject.org/pluggable-transports/goptlib.git"
+	"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
+)
+
+const (
+	urlArg   = "url"
+	frontArg = "front"
+
+	maxChanBacklog = 16
+
+	// Constants shamelessly stolen from meek-client.go...
+	maxPayloadLength       = 0x10000
+	initPollInterval       = 100 * time.Millisecond
+	maxPollInterval        = 5 * time.Second
+	pollIntervalMultiplier = 1.5
+	maxRetries             = 10
+	retryDelay             = 30 * time.Second
+)
+
+var (
+	// ErrNotSupported is the error returned for a unsupported operation.
+	ErrNotSupported = errors.New("meek_lite: operation not supported")
+
+	loopbackAddr = net.IPv4(127, 0, 0, 1)
+)
+
+type meekClientArgs struct {
+	url   *gourl.URL
+	front string
+}
+
+func (ca *meekClientArgs) Network() string {
+	return transportName
+}
+
+func (ca *meekClientArgs) String() string {
+	return transportName + ":" + ca.front + ":" + ca.url.String()
+}
+
+func newClientArgs(args *pt.Args) (ca *meekClientArgs, err error) {
+	ca = &meekClientArgs{}
+
+	// Parse the URL argument.
+	str, ok := args.Get(urlArg)
+	if !ok {
+		return nil, fmt.Errorf("missing argument '%s'", urlArg)
+	}
+	ca.url, err = gourl.Parse(str)
+	if err != nil {
+		return nil, fmt.Errorf("malformed url: '%s'", str)
+	}
+	switch ca.url.Scheme {
+	case "http", "https":
+	default:
+		return nil, fmt.Errorf("invalid scheme: '%s'", ca.url.Scheme)
+	}
+
+	// Parse the (optional) front argument.
+	ca.front, _ = args.Get(frontArg)
+
+	return ca, nil
+}
+
+type meekConn struct {
+	sync.Mutex
+
+	args      *meekClientArgs
+	sessionID string
+	transport *http.Transport
+
+	workerRunning   bool
+	workerWrChan    chan []byte
+	workerRdChan    chan []byte
+	workerCloseChan chan bool
+	rdBuf           *bytes.Buffer
+}
+
+func (c *meekConn) Read(p []byte) (n int, err error) {
+	// If there is data left over from the previous read,
+	// service the request using the buffered data.
+	if c.rdBuf != nil {
+		if c.rdBuf.Len() == 0 {
+			panic("empty read buffer")
+		}
+		n, err = c.rdBuf.Read(p)
+		if c.rdBuf.Len() == 0 {
+			c.rdBuf = nil
+		}
+		return
+	}
+
+	// Wait for the worker to enqueue more incoming data.
+	b, ok := <-c.workerRdChan
+	if !ok {
+		// Close() was called and the worker's shutting down.
+		return 0, io.ErrClosedPipe
+	}
+
+	// Ew, an extra copy, but who am I kidding, it's meek.
+	buf := bytes.NewBuffer(b)
+	n, err = buf.Read(p)
+	if buf.Len() > 0 {
+		// If there's data pending, stash the buffer so the next
+		// Read() call will use it to fulfuill the Read().
+		c.rdBuf = buf
+	}
+	return
+}
+
+func (c *meekConn) Write(b []byte) (n int, err error) {
+	// Check to see if the connection is actually open.
+	c.Lock()
+	closed := !c.workerRunning
+	c.Unlock()
+	if closed {
+		return 0, io.ErrClosedPipe
+	}
+
+	if len(b) > 0 {
+		// Copy the data to be written to a new slice, since
+		// we return immediately after queuing and the peer can
+		// happily reuse `b` before data has been sent.
+		toWrite := len(b)
+		b2 := make([]byte, toWrite)
+		copy(b2, b)
+		offset := 0
+		for toWrite > 0 {
+			// Chunk up the writes to keep them under the maximum
+			// payload length.
+			sz := toWrite
+			if sz > maxPayloadLength {
+				sz = maxPayloadLength
+			}
+
+			// Enqueue a properly sized subslice of our copy.
+			if ok := c.enqueueWrite(b2[offset : offset+sz]); !ok {
+				// Technically we did enqueue data, but the worker's
+				// got closed out from under us.
+				return 0, io.ErrClosedPipe
+			}
+			toWrite -= sz
+			offset += sz
+			runtime.Gosched()
+		}
+	}
+	return len(b), nil
+}
+
+func (c *meekConn) Close() error {
+	// Ensure that we do this once and only once.
+	c.Lock()
+	defer c.Unlock()
+	if !c.workerRunning {
+		return nil
+	}
+
+	// Tear down the worker.
+	c.workerRunning = false
+	c.workerCloseChan <- true
+
+	return nil
+}
+
+func (c *meekConn) LocalAddr() net.Addr {
+	return &net.IPAddr{IP: loopbackAddr}
+}
+
+func (c *meekConn) RemoteAddr() net.Addr {
+	return c.args
+}
+
+func (c *meekConn) SetDeadline(t time.Time) error {
+	return ErrNotSupported
+}
+
+func (c *meekConn) SetReadDeadline(t time.Time) error {
+	return ErrNotSupported
+}
+
+func (c *meekConn) SetWriteDeadline(t time.Time) error {
+	return ErrNotSupported
+}
+
+func (c *meekConn) enqueueWrite(b []byte) (ok bool) {
+	defer func() { recover() }()
+	c.workerWrChan <- b
+	return true
+}
+
+func (c *meekConn) roundTrip(sndBuf []byte) (recvBuf []byte, err error) {
+	var req *http.Request
+	var resp *http.Response
+
+	for retries := 0; retries < maxRetries; retries++ {
+		url := *c.args.url
+		host := url.Host
+		if c.args.front != "" {
+			url.Host = c.args.front
+		}
+		req, err = http.NewRequest("POST", url.String(), bytes.NewReader(sndBuf))
+		if err != nil {
+			return nil, err
+		}
+		if c.args.front != "" {
+			req.Host = host
+		}
+		req.Header.Set("X-Session-Id", c.sessionID)
+
+		resp, err = c.transport.RoundTrip(req)
+		if err != nil {
+			return nil, err
+		}
+		if resp.StatusCode != http.StatusOK {
+			err = fmt.Errorf("status code was %d, not %d", resp.StatusCode, http.StatusOK)
+			time.Sleep(retryDelay)
+		} else {
+			defer resp.Body.Close()
+			recvBuf, err = ioutil.ReadAll(io.LimitReader(resp.Body, maxPayloadLength))
+			return
+		}
+	}
+	return
+}
+
+func (c *meekConn) ioWorker() {
+	interval := initPollInterval
+loop:
+	for {
+		var sndBuf []byte
+		select {
+		case <-time.After(interval):
+			// If the poll interval has elapsed, issue a request.
+		case sndBuf = <-c.workerWrChan:
+			// If there is data pending a send, issue a request.
+		case _ = <-c.workerCloseChan:
+			break loop
+		}
+
+		// Issue a request.
+		rdBuf, err := c.roundTrip(sndBuf)
+		if err != nil {
+			// Welp, something went horrifically wrong.
+			break loop
+		}
+		if len(rdBuf) > 0 {
+			// Received data, enqueue the read.
+			c.workerRdChan <- rdBuf
+
+			// And poll immediately.
+			interval = 0
+		} else if sndBuf != nil {
+			// Sent data, poll immediately.
+			interval = 0
+		} else if interval == 0 {
+			// Neither sent nor received data, initialize the delay.
+			interval = initPollInterval
+		} else {
+			// Apply a multiplicative backoff.
+			interval = time.Duration(float64(interval) * pollIntervalMultiplier)
+			if interval > maxPollInterval {
+				interval = maxPollInterval
+			}
+		}
+
+		runtime.Gosched()
+	}
+
+	// Unblock callers waiting in Read() for data that will never arrive,
+	// and callers waiting in Write() for data that will never get sent.
+	close(c.workerRdChan)
+	close(c.workerWrChan)
+
+	// In case the close was done on an error condition, update the state
+	// variable so that further calls to Write() will fail.
+	c.Lock()
+	defer c.Unlock()
+	c.workerRunning = false
+}
+
+func newMeekConn(network, addr string, dialFn base.DialFunc, ca *meekClientArgs) (net.Conn, error) {
+	id, err := newSessionID()
+	if err != nil {
+		return nil, err
+	}
+
+	tr := &http.Transport{Dial: dialFn}
+	conn := &meekConn{
+		args:            ca,
+		sessionID:       id,
+		transport:       tr,
+		workerRunning:   true,
+		workerWrChan:    make(chan []byte, maxChanBacklog),
+		workerRdChan:    make(chan []byte, maxChanBacklog),
+		workerCloseChan: make(chan bool),
+	}
+
+	// Start the I/O worker.
+	go conn.ioWorker()
+
+	return conn, nil
+}
+
+func newSessionID() (string, error) {
+	var b [64]byte
+	if _, err := rand.Read(b[:]); err != nil {
+		return "", err
+	}
+	h := sha256.Sum256(b[:])
+	return hex.EncodeToString(h[:16]), nil
+}
+
+var _ net.Conn = (*meekConn)(nil)
+var _ net.Addr = (*meekClientArgs)(nil)
diff --git a/transports/transports.go b/transports/transports.go
index e35673b..51a3f08 100644
--- a/transports/transports.go
+++ b/transports/transports.go
@@ -34,6 +34,7 @@ import (
 	"sync"
 
 	"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
+	"git.torproject.org/pluggable-transports/obfs4.git/transports/meeklite"
 	"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs2"
 	"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs3"
 	"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4"
@@ -83,6 +84,7 @@ func Get(name string) base.Transport {
 
 // Init initializes all of the integrated transports.
 func Init() error {
+	Register(new(meeklite.Transport))
 	Register(new(obfs2.Transport))
 	Register(new(obfs3.Transport))
 	Register(new(obfs4.Transport))



More information about the tor-commits mailing list