commit 611205be681322883a4d73dd00fcb13c4352fe53
Author: Yawning Angel <[email protected]>
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))

_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to