[tor-commits] [obfs4/master] Various IAT related changes.

yawning at torproject.org yawning at torproject.org
Wed Aug 27 13:02:05 UTC 2014


commit bfca92cf1627c2e380eb44f4314bb121935108ec
Author: Yawning Angel <yawning at torproject.org>
Date:   Wed Aug 27 12:30:11 2014 +0000

    Various IAT related changes.
    
     * Unbreak inbound TYPE_PRNG_SEED processing.
    
     * IAT obfuscation is now a per-bridge argument (iat-mode).
       * 0 (default) = Disabled.
       * 1 = Enabled, ScrambleSuit-style with bulk throughput optimizations.
       * 2 = Paranoid, Each IAT write will send a length sampled from the
         length distribution. (EXPENSIVE).
    
    The "iat-mode" argument is mandatory on the Bridge lines, and as a
    ServerTransportOption.  Old statefiles will continue to load and use
    the default value, edit it if your hat is made of tin foil.
---
 transports/obfs4/obfs4.go     |   86 ++++++++++++++++++++++++++++++-----------
 transports/obfs4/statefile.go |   22 ++++++++++-
 2 files changed, 85 insertions(+), 23 deletions(-)

diff --git a/transports/obfs4/obfs4.go b/transports/obfs4/obfs4.go
index f9b02ad..b862e5a 100644
--- a/transports/obfs4/obfs4.go
+++ b/transports/obfs4/obfs4.go
@@ -36,6 +36,7 @@ import (
 	"fmt"
 	"math/rand"
 	"net"
+	"strconv"
 	"syscall"
 	"time"
 
@@ -55,11 +56,11 @@ const (
 	publicKeyArg  = "public-key"
 	privateKeyArg = "private-key"
 	seedArg       = "drbg-seed"
+	iatArg        = "iat-mode"
 
-	iatCmdArg  = "obfs4-iatObfuscation"
 	biasCmdArg = "obfs4-distBias"
 
-	seedLength             = 32
+	seedLength             = drbg.SeedLength
 	headerLength           = framing.FrameOverhead + packetOverhead
 	clientHandshakeTimeout = time.Duration(60) * time.Second
 	serverHandshakeTimeout = time.Duration(30) * time.Second
@@ -70,8 +71,11 @@ const (
 	maxCloseDelay      = 60
 )
 
-// iatObfuscation controls if Inter-Arrival Time obfuscation will be enabled.
-var iatObfuscation bool
+const (
+	iatNone = iota
+	iatEnabled
+	iatParanoid
+)
 
 // biasedDist controls if the probability table will be ScrambleSuit style or
 // uniformly distributed.
@@ -81,6 +85,7 @@ type obfs4ClientArgs struct {
 	nodeID     *ntor.NodeID
 	publicKey  *ntor.PublicKey
 	sessionKey *ntor.Keypair
+	iatMode    int
 }
 
 // Transport is the obfs4 implementation of the base.Transport interface.
@@ -107,7 +112,7 @@ func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFa
 	}
 
 	var iatSeed *drbg.Seed
-	if iatObfuscation {
+	if st.iatMode != iatNone {
 		iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
 		iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
 		if err != nil {
@@ -119,6 +124,7 @@ func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFa
 	ptArgs := pt.Args{}
 	ptArgs.Add(nodeIDArg, st.nodeID.Hex())
 	ptArgs.Add(publicKeyArg, st.identityKey.Public().Hex())
+	ptArgs.Add(iatArg, strconv.Itoa(st.iatMode))
 
 	// Initialize the replay filter.
 	filter, err := replayfilter.New(replayTTL)
@@ -133,7 +139,7 @@ func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFa
 	}
 	rng := rand.New(drbg)
 
-	sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
+	sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
 	return sf, nil
 }
 
@@ -157,6 +163,15 @@ func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
 	if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil {
 		return nil, err
 	}
+	iatStr, ok := args.Get(iatArg)
+	if !ok {
+		return nil, fmt.Errorf("missing argument '%s'", iatArg)
+	}
+	var iatMode int
+	iatMode, err = strconv.Atoi(iatStr)
+	if err != nil || iatMode < iatNone || iatMode > iatParanoid {
+		return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode)
+	}
 
 	publicKeyStr, ok := args.Get(publicKeyArg)
 	if !ok {
@@ -174,7 +189,7 @@ func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
 		return nil, err
 	}
 
-	return &obfs4ClientArgs{nodeID, publicKey, sessionKey}, nil
+	return &obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}, nil
 }
 
 func (cf *obfs4ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
@@ -194,6 +209,7 @@ type obfs4ServerFactory struct {
 	identityKey  *ntor.Keypair
 	lenSeed      *drbg.Seed
 	iatSeed      *drbg.Seed
+	iatMode      int
 	replayFilter *replayfilter.ReplayFilter
 
 	closeDelayBytes int
@@ -228,7 +244,7 @@ func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
 		iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
 	}
 
-	c := &obfs4Conn{conn, true, lenDist, iatDist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
+	c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
 
 	startTime := time.Now()
 
@@ -247,6 +263,7 @@ type obfs4Conn struct {
 
 	lenDist *probdist.WeightedDist
 	iatDist *probdist.WeightedDist
+	iatMode int
 
 	receiveBuffer        *bytes.Buffer
 	receiveDecodedBuffer *bytes.Buffer
@@ -263,7 +280,7 @@ func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err
 	}
 	lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
 	var iatDist *probdist.WeightedDist
-	if iatObfuscation {
+	if args.iatMode != iatNone {
 		var iatSeed *drbg.Seed
 		iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
 		if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil {
@@ -273,7 +290,7 @@ func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err
 	}
 
 	// Allocate the client structure.
-	c = &obfs4Conn{conn, false, lenDist, iatDist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
+	c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
 
 	// Start the handshake timeout.
 	deadline := time.Now().Add(clientHandshakeTimeout)
@@ -466,24 +483,51 @@ func (conn *obfs4Conn) Write(b []byte) (n int, err error) {
 		}
 	}
 
-	// Add the length obfuscation padding.  In theory, this could be inlined
-	// with the last chopped packet for certain (most?) payload lenghts, but
-	// this is simpler.
-
-	if err = conn.padBurst(&frameBuf); err != nil {
-		return 0, err
+	if conn.iatMode != iatParanoid {
+		// For non-paranoid IAT, pad once per burst.  Paranoid IAT handles
+		// things differently.
+		if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil {
+			return 0, err
+		}
 	}
 
 	// Write the pending data onto the network.  Partial writes are fatal,
 	// because the frame encoder state is advanced, and the code doesn't keep
 	// frameBuf around.  In theory, write timeouts and whatnot could be
 	// supported if this wasn't the case, but that complicates the code.
-
-	if conn.iatDist != nil {
+	if conn.iatMode != iatNone {
 		var iatFrame [framing.MaximumSegmentLength]byte
 		for frameBuf.Len() > 0 {
 			iatWrLen := 0
-			iatWrLen, err = frameBuf.Read(iatFrame[:])
+
+			switch conn.iatMode {
+			case iatEnabled:
+				// Standard (ScrambleSuit-style) IAT obfuscation optimizes for
+				// bulk transport and will write ~MTU sized frames when
+				// possible.
+				iatWrLen, err = frameBuf.Read(iatFrame[:])
+
+			case iatParanoid:
+				// Paranoid IAT obfuscation throws performance out of the
+				// window and will sample the length distribution every time a
+				// write is scheduled.
+				targetLen := conn.lenDist.Sample()
+				if frameBuf.Len() < targetLen {
+					// There's not enough data buffered for the target write,
+					// so padding must be inserted.
+					if err = conn.padBurst(&frameBuf, targetLen); err != nil {
+						return 0, err
+					}
+					if frameBuf.Len() != targetLen {
+						// Ugh, padding came out to a value that required more
+						// than one frame, this is relatively unlikely so just
+						// resample since there's enough data to ensure that
+						// the next sample will be written.
+						continue
+					}
+				}
+				iatWrLen, err = frameBuf.Read(iatFrame[:targetLen])
+			}
 			if err != nil {
 				return 0, err
 			} else if iatWrLen == 0 {
@@ -543,9 +587,8 @@ func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Ti
 	}
 }
 
-func (conn *obfs4Conn) padBurst(burst *bytes.Buffer) (err error) {
+func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) {
 	tailLen := burst.Len() % framing.MaximumSegmentLength
-	toPadTo := conn.lenDist.Sample()
 
 	padLen := 0
 	if toPadTo >= tailLen {
@@ -577,7 +620,6 @@ func (conn *obfs4Conn) padBurst(burst *bytes.Buffer) (err error) {
 }
 
 func init() {
-	flag.BoolVar(&iatObfuscation, iatCmdArg, false, "Enable obfs4 IAT obfuscation (expensive)")
 	flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation")
 }
 
diff --git a/transports/obfs4/statefile.go b/transports/obfs4/statefile.go
index 7949dd9..8690178 100644
--- a/transports/obfs4/statefile.go
+++ b/transports/obfs4/statefile.go
@@ -33,6 +33,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
+	"strconv"
 
 	"git.torproject.org/pluggable-transports/goptlib.git"
 	"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
@@ -49,12 +50,14 @@ type jsonServerState struct {
 	PrivateKey string `json:"private-key"`
 	PublicKey  string `json:"public-key"`
 	DrbgSeed   string `json:"drbg-seed"`
+	IATMode    int    `json:"iat-mode"`
 }
 
 type obfs4ServerState struct {
 	nodeID      *ntor.NodeID
 	identityKey *ntor.Keypair
 	drbgSeed    *drbg.Seed
+	iatMode     int
 }
 
 func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) {
@@ -64,8 +67,9 @@ func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, err
 	js.NodeID, nodeIDOk = args.Get(nodeIDArg)
 	js.PrivateKey, privKeyOk = args.Get(privateKeyArg)
 	js.DrbgSeed, seedOk = args.Get(seedArg)
+	iatStr, iatOk := args.Get(iatArg)
 
-	if !privKeyOk && !nodeIDOk && !seedOk {
+	if !privKeyOk && !nodeIDOk && !seedOk && !iatOk {
 		if err := jsonServerStateFromFile(stateDir, &js); err != nil {
 			return nil, err
 		}
@@ -75,6 +79,16 @@ func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, err
 		return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
 	} else if !seedOk {
 		return nil, fmt.Errorf("missing argument '%s'", seedArg)
+	} else if !iatOk {
+		// Disable IAT if not specified.
+		return nil, fmt.Errorf("missing argument '%s'", iatArg)
+	} else {
+		// Parse and validate the iat-mode argument.
+		iatMode, err := strconv.Atoi(iatStr)
+		if err != nil {
+			return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr)
+		}
+		js.IATMode = iatMode
 	}
 
 	return serverStateFromJSONServerState(&js)
@@ -93,6 +107,10 @@ func serverStateFromJSONServerState(js *jsonServerState) (*obfs4ServerState, err
 	if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil {
 		return nil, err
 	}
+	if js.IATMode < iatNone || js.IATMode > iatParanoid {
+		return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode)
+	}
+	st.iatMode = js.IATMode
 
 	return st, nil
 }
@@ -132,12 +150,14 @@ func newJSONServerState(stateDir string, js *jsonServerState) (err error) {
 	if st.drbgSeed, err = drbg.NewSeed(); err != nil {
 		return
 	}
+	st.iatMode = iatNone
 
 	// Encode it into JSON format and write the state file.
 	js.NodeID = st.nodeID.Hex()
 	js.PrivateKey = st.identityKey.Private().Hex()
 	js.PublicKey = st.identityKey.Public().Hex()
 	js.DrbgSeed = st.drbgSeed.Hex()
+	js.IATMode = st.iatMode
 
 	var encoded []byte
 	if encoded, err = json.Marshal(js); err != nil {



More information about the tor-commits mailing list