summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--obfs4.go54
-rw-r--r--obfs4proxy/obfs4proxy.go7
-rw-r--r--packet.go3
3 files changed, 53 insertions, 11 deletions
diff --git a/obfs4.go b/obfs4.go
index f32c222..3bbccb3 100644
--- a/obfs4.go
+++ b/obfs4.go
@@ -50,6 +50,8 @@ const (
maxCloseDelayBytes = maxHandshakeLength
maxCloseDelay = 60
+
+ maxIatDelay = 100
)
type connState int
@@ -67,6 +69,7 @@ type Obfs4Conn struct {
conn net.Conn
lenProbDist *wDist
+ iatProbDist *wDist
encoder *framing.Encoder
decoder *framing.Decoder
@@ -444,13 +447,38 @@ func (c *Obfs4Conn) Write(b []byte) (n int, err error) {
return 0, err
}
- // Send the frame(s).
- _, err = c.conn.Write(frameBuf.Bytes())
- if err != nil {
- // Partial writes are fatal because the frame encoder state is advanced
- // at this point. It's possible to keep frameBuf around, but fuck it.
- // Someone that wants write timeouts can change this.
- return 0, err
+ // Spit frame(s) onto the network.
+ //
+ // Partial writes are fatal because the frame encoder state is advanced
+ // at this point. It's possible to keep frameBuf around, but fuck it.
+ // Someone that wants write timeouts can change this.
+ if c.iatProbDist != nil {
+ var iatFrame [framing.MaximumSegmentLength]byte
+ for frameBuf.Len() > 0 {
+ iatWrLen := 0
+ iatWrLen, err = frameBuf.Read(iatFrame[:])
+ if err != nil {
+ return 0, err
+ } else if iatWrLen == 0 {
+ panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
+ }
+
+ // Calculate the delay. The delay resolution is 100 usec, leading
+ // to a maximum delay of 10 msec.
+ iatDelta := time.Duration(c.iatProbDist.sample() * 100)
+
+ // Write then sleep.
+ _, err = c.conn.Write(iatFrame[:iatWrLen])
+ if err != nil {
+ return 0, err
+ }
+ time.Sleep(iatDelta * time.Microsecond)
+ }
+ } else {
+ _, err = c.conn.Write(frameBuf.Bytes())
+ if err != nil {
+ return 0, err
+ }
}
return
@@ -508,7 +536,7 @@ func (c *Obfs4Conn) SetWriteDeadline(t time.Time) error {
// DialObfs4 connects to the remote address on the network, and handshakes with
// the peer's obfs4 Node ID and Identity Public Key. nodeID and publicKey are
// expected as strings containing the Base64 encoded values.
-func DialObfs4(network, address, nodeID, publicKey string) (*Obfs4Conn, error) {
+func DialObfs4(network, address, nodeID, publicKey string, iatObfuscation bool) (*Obfs4Conn, error) {
// Decode the node_id/public_key.
pub, err := ntor.PublicKeyFromBase64(publicKey)
if err != nil {
@@ -528,6 +556,9 @@ func DialObfs4(network, address, nodeID, publicKey string) (*Obfs4Conn, error) {
// Connect to the peer.
c := new(Obfs4Conn)
c.lenProbDist = newWDist(seed, 0, framing.MaximumSegmentLength)
+ if iatObfuscation {
+ c.iatProbDist = newWDist(seed, 0, maxIatDelay)
+ }
c.conn, err = net.Dial(network, address)
if err != nil {
return nil, err
@@ -554,6 +585,7 @@ type Obfs4Listener struct {
nodeID *ntor.NodeID
seed *DrbgSeed
+ iatObfuscation bool
closeDelayBytes int
closeDelay int
@@ -587,6 +619,9 @@ func (l *Obfs4Listener) AcceptObfs4() (*Obfs4Conn, error) {
cObfs.isServer = true
cObfs.listener = l
cObfs.lenProbDist = newWDist(l.seed, 0, framing.MaximumSegmentLength)
+ if l.iatObfuscation {
+ cObfs.iatProbDist = newWDist(l.seed, 0, maxIatDelay)
+ }
if err != nil {
c.Close()
return nil, err
@@ -629,7 +664,7 @@ func (l *Obfs4Listener) NodeID() string {
// ListenObfs4 annnounces on the network and address, and returns and
// Obfs4Listener. nodeId, privateKey and seed are expected as strings
// containing the Base64 encoded values.
-func ListenObfs4(network, laddr, nodeID, privateKey, seed string) (*Obfs4Listener, error) {
+func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation bool) (*Obfs4Listener, error) {
var err error
// Decode node_id/private_key.
@@ -655,6 +690,7 @@ func ListenObfs4(network, laddr, nodeID, privateKey, seed string) (*Obfs4Listene
rng := rand.New(newHashDrbg(l.seed))
l.closeDelayBytes = rng.Intn(maxCloseDelayBytes)
l.closeDelay = rng.Intn(maxCloseDelay)
+ l.iatObfuscation = iatObfuscation
// Start up the listener.
l.listener, err = net.Listen(network, laddr)
diff --git a/obfs4proxy/obfs4proxy.go b/obfs4proxy/obfs4proxy.go
index 3ad8785..86589bb 100644
--- a/obfs4proxy/obfs4proxy.go
+++ b/obfs4proxy/obfs4proxy.go
@@ -70,6 +70,7 @@ const (
)
var unsafeLogging bool
+var iatObfuscation bool
var ptListeners []net.Listener
// When a connection handler starts, +1 is written to this channel; when it
@@ -194,7 +195,7 @@ func serverSetup() bool {
// Initialize the listener.
ln, err := obfs4.ListenObfs4("tcp", bindaddr.Addr.String(), nodeID,
- privateKey, seed)
+ privateKey, seed, iatObfuscation)
if err != nil {
pt.SmethodError(bindaddr.MethodName, err.Error())
break
@@ -249,7 +250,8 @@ func clientHandler(conn *pt.SocksConn) error {
}()
defer logAndRecover(nil)
- remote, err := obfs4.DialObfs4("tcp", conn.Req.Target, nodeID, publicKey)
+ remote, err := obfs4.DialObfs4("tcp", conn.Req.Target, nodeID, publicKey,
+ iatObfuscation)
if err != nil {
log.Printf("[ERROR] client: %p: Handshake failed: %s", remote, err)
conn.Reject()
@@ -395,6 +397,7 @@ func main() {
// Some command line args.
genParams := flag.String("genServerParams", "", "Generate server params given a bridge fingerprint.")
doLogging := flag.Bool("enableLogging", false, "Log to TOR_PT_STATE_LOCATION/obfs4proxy.log")
+ flag.BoolVar(&iatObfuscation, "iatObfuscation", false, "Enable IAT obufscation (EXPENSIVE)")
flag.BoolVar(&unsafeLogging, "unsafeLogging", false, "Disable the address scrubber")
flag.Parse()
if *genParams != "" {
diff --git a/packet.go b/packet.go
index 4112c9b..78f6697 100644
--- a/packet.go
+++ b/packet.go
@@ -181,6 +181,9 @@ func (c *Obfs4Conn) consumeFramedPackets(w io.Writer) (n int, err error) {
break
}
c.lenProbDist.reset(seed)
+ if c.iatProbDist != nil {
+ c.iatProbDist.reset(seed)
+ }
}
default:
// Ignore unrecognised packet types.