summaryrefslogtreecommitdiff
path: root/transports
diff options
context:
space:
mode:
authorYawning Angel <yawning@torproject.org>2014-08-27 12:30:11 +0000
committerYawning Angel <yawning@torproject.org>2014-08-27 12:30:11 +0000
commitbfca92cf1627c2e380eb44f4314bb121935108ec (patch)
treecf6d781e67652231bf199c948ff7d1525ea542e0 /transports
parent94d3fe9a0802e2614d4a4b9297ed57b1979865fc (diff)
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.
Diffstat (limited to 'transports')
-rw-r--r--transports/obfs4/obfs4.go86
-rw-r--r--transports/obfs4/statefile.go22
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 {