summaryrefslogtreecommitdiff
path: root/obfs4.go
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2014-05-13 02:31:37 +0000
committerYawning Angel <yawning@schwanenlied.me>2014-05-13 02:31:37 +0000
commit9bfdd77f722807a611d6910bbef45084360064a1 (patch)
tree1cbc9c840bad373368ada0f8322e29ea6b24adf8 /obfs4.go
parent51a8dd5a86eeca744e0add680b1f4796c4babe2b (diff)
Add preliminary support for packet length obfuscation.
The same algorithm as ScrambleSuit is used, except: * SipHash-2-4 in OFB mode is used to create the distribution. * The system CSPRNG is used when sampling the distribution. This fixes most of #3, all that remains is generating and sending a persistent distribution on the server side to the client.
Diffstat (limited to 'obfs4.go')
-rw-r--r--obfs4.go84
1 files changed, 66 insertions, 18 deletions
diff --git a/obfs4.go b/obfs4.go
index e69c7b7..cd6f75d 100644
--- a/obfs4.go
+++ b/obfs4.go
@@ -40,6 +40,7 @@ import (
)
const (
+ headerLength = framing.FrameOverhead + packetOverhead
defaultReadSize = framing.MaximumSegmentLength
connectionTimeout = time.Duration(15) * time.Second
@@ -54,6 +55,8 @@ const (
type Obfs4Conn struct {
conn net.Conn
+ probDist *wDist
+
encoder *framing.Encoder
decoder *framing.Decoder
@@ -67,22 +70,33 @@ type Obfs4Conn struct {
listener *Obfs4Listener
}
+func (c *Obfs4Conn) calcPadLen(burstLen int) int {
+ tailLen := burstLen % framing.MaximumSegmentLength
+ toPadTo := c.probDist.sample()
+
+ ret := 0
+ if toPadTo >= tailLen {
+ ret = toPadTo - tailLen
+ } else {
+ ret = (framing.MaximumSegmentLength - tailLen) + toPadTo
+ }
+
+ return ret
+}
+
func (c *Obfs4Conn) closeAfterDelay() {
// I-it's not like I w-wanna handshake with you or anything. B-b-baka!
defer c.conn.Close()
- delaySecs, err := randRange(minCloseInterval, maxCloseInterval)
- if err != nil {
- return
- }
- toDiscard, err := randRange(minCloseThreshold, maxCloseThreshold)
+ delaySecs := randRange(minCloseInterval, maxCloseInterval)
+ toDiscard := randRange(minCloseThreshold, maxCloseThreshold)
+
+ delay := time.Duration(delaySecs) * time.Second
+ err := c.conn.SetReadDeadline(time.Now().Add(delay))
if err != nil {
return
}
- delay := time.Duration(delaySecs) * time.Second
- err = c.conn.SetReadDeadline(time.Now().Add(delay))
-
// Consume and discard data on this connection until either the specified
// interval passes or a certain size has been reached.
discarded := 0
@@ -286,7 +300,6 @@ func (c *Obfs4Conn) Read(b []byte) (int, error) {
func (c *Obfs4Conn) Write(b []byte) (int, error) {
chopBuf := bytes.NewBuffer(b)
buf := make([]byte, maxPacketPayloadLength)
- pkt := make([]byte, framing.MaximumFramePayloadLength)
nSent := 0
var frameBuf bytes.Buffer
@@ -295,26 +308,52 @@ func (c *Obfs4Conn) Write(b []byte) (int, error) {
n, err := chopBuf.Read(buf)
if err != nil {
c.isOk = false
- return nSent, err
+ return 0, err
} else if n == 0 {
panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
}
nSent += n
- // Wrap the payload in a packet.
- n = makePacket(pkt[:], packetTypePayload, buf[:n], 0)
-
- // Encode the packet in an AEAD frame.
- _, frame, err := c.encoder.Encode(pkt[:n])
+ _, frame, err := c.makeAndEncryptPacket(packetTypePayload, buf[:n], 0)
if err != nil {
c.isOk = false
- return nSent, err
+ return 0, err
}
frameBuf.Write(frame)
}
- // TODO: Insert random padding.
+ // Insert random padding. In theory it's possible to inline padding for
+ // certain framesizes into the last AEAD packet, but always sending 1 or 2
+ // padding frames is considerably easier.
+ padLen := c.calcPadLen(frameBuf.Len())
+ if padLen > 0 {
+ if padLen > headerLength {
+ _, frame, err := c.makeAndEncryptPacket(packetTypePayload, []byte{},
+ uint16(padLen-headerLength))
+ if err != nil {
+ c.isOk = false
+ return 0, err
+ }
+ frameBuf.Write(frame)
+ } else {
+ _, frame, err := c.makeAndEncryptPacket(packetTypePayload, []byte{},
+ maxPacketPayloadLength)
+ if err != nil {
+ c.isOk = false
+ return 0, err
+ }
+ frameBuf.Write(frame)
+
+ _, frame, err = c.makeAndEncryptPacket(packetTypePayload, []byte{},
+ uint16(padLen))
+ if err != nil {
+ c.isOk = false
+ return 0, err
+ }
+ frameBuf.Write(frame)
+ }
+ }
// Send the frame(s).
_, err := c.conn.Write(frameBuf.Bytes())
@@ -323,7 +362,7 @@ func (c *Obfs4Conn) Write(b []byte) (int, error) {
// at this point. It's possible to keep frameBuf around, but fuck it.
// Someone that wants write timeouts can change this.
c.isOk = false
- return nSent, err // XXX: nSent is a dirty lie here.
+ return 0, err
}
return nSent, nil
@@ -384,6 +423,10 @@ func Dial(network, address, nodeID, publicKey string) (net.Conn, error) {
// Connect to the peer.
c := new(Obfs4Conn)
+ c.probDist, err = newWDist(nil, 0, framing.MaximumSegmentLength)
+ if err != nil {
+ return nil, err
+ }
c.conn, err = net.Dial(network, address)
if err != nil {
return nil, err
@@ -420,6 +463,11 @@ func (l *Obfs4Listener) Accept() (net.Conn, error) {
cObfs.conn = c
cObfs.isServer = true
cObfs.listener = l
+ cObfs.probDist, err = newWDist(nil, 0, framing.MaximumSegmentLength)
+ if err != nil {
+ c.Close()
+ return nil, err
+ }
return cObfs, nil
}