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.
merge-requests/3/head
Yawning Angel 9 years ago
parent e52258edac
commit 611205be68

@ -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.

@ -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.

@ -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)

@ -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)

@ -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))

Loading…
Cancel
Save