|
|
@@ -0,0 +1,647 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2014, 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 obfs4 provides an implementation of the Tor Project's obfs4
|
|
|
+// obfuscation protocol.
|
|
|
+package obfs4 // import "gitlab.com/yawning/obfs4.git/transports/obfs4"
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "crypto/sha256"
|
|
|
+ "flag"
|
|
|
+ "fmt"
|
|
|
+ "math/rand"
|
|
|
+ "net"
|
|
|
+ "strconv"
|
|
|
+ "syscall"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "git.torproject.org/pluggable-transports/goptlib.git"
|
|
|
+ "gitlab.com/yawning/obfs4.git/common/drbg"
|
|
|
+ "gitlab.com/yawning/obfs4.git/common/ntor"
|
|
|
+ "gitlab.com/yawning/obfs4.git/common/probdist"
|
|
|
+ "gitlab.com/yawning/obfs4.git/common/replayfilter"
|
|
|
+ "gitlab.com/yawning/obfs4.git/transports/base"
|
|
|
+ "gitlab.com/yawning/obfs4.git/transports/obfs4/framing"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ transportName = "obfs4"
|
|
|
+
|
|
|
+ nodeIDArg = "node-id"
|
|
|
+ publicKeyArg = "public-key"
|
|
|
+ privateKeyArg = "private-key"
|
|
|
+ seedArg = "drbg-seed"
|
|
|
+ iatArg = "iat-mode"
|
|
|
+ certArg = "cert"
|
|
|
+
|
|
|
+ biasCmdArg = "obfs4-distBias"
|
|
|
+
|
|
|
+ seedLength = drbg.SeedLength
|
|
|
+ headerLength = framing.FrameOverhead + packetOverhead
|
|
|
+ clientHandshakeTimeout = time.Duration(60) * time.Second
|
|
|
+ serverHandshakeTimeout = time.Duration(30) * time.Second
|
|
|
+ replayTTL = time.Duration(3) * time.Hour
|
|
|
+
|
|
|
+ maxIATDelay = 100
|
|
|
+ maxCloseDelayBytes = maxHandshakeLength
|
|
|
+ maxCloseDelay = 60
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ iatNone = iota
|
|
|
+ iatEnabled
|
|
|
+ iatParanoid
|
|
|
+)
|
|
|
+
|
|
|
+// biasedDist controls if the probability table will be ScrambleSuit style or
|
|
|
+// uniformly distributed.
|
|
|
+var biasedDist bool
|
|
|
+
|
|
|
+type obfs4ClientArgs struct {
|
|
|
+ nodeID *ntor.NodeID
|
|
|
+ publicKey *ntor.PublicKey
|
|
|
+ sessionKey *ntor.Keypair
|
|
|
+ iatMode int
|
|
|
+}
|
|
|
+
|
|
|
+// Transport is the obfs4 implementation of the base.Transport interface.
|
|
|
+type Transport struct{}
|
|
|
+
|
|
|
+// Name returns the name of the obfs4 transport protocol.
|
|
|
+func (t *Transport) Name() string {
|
|
|
+ return transportName
|
|
|
+}
|
|
|
+
|
|
|
+// ClientFactory returns a new obfs4ClientFactory instance.
|
|
|
+func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
|
|
|
+ cf := &obfs4ClientFactory{transport: t}
|
|
|
+ return cf, nil
|
|
|
+}
|
|
|
+
|
|
|
+// ServerFactory returns a new obfs4ServerFactory instance.
|
|
|
+func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
|
|
|
+ st, err := serverStateFromArgs(stateDir, args)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ var iatSeed *drbg.Seed
|
|
|
+ if st.iatMode != iatNone {
|
|
|
+ iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
|
|
|
+ var err error
|
|
|
+ iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store the arguments that should appear in our descriptor for the clients.
|
|
|
+ ptArgs := pt.Args{}
|
|
|
+ ptArgs.Add(certArg, st.cert.String())
|
|
|
+ ptArgs.Add(iatArg, strconv.Itoa(st.iatMode))
|
|
|
+
|
|
|
+ // Initialize the replay filter.
|
|
|
+ filter, err := replayfilter.New(replayTTL)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Initialize the close thresholds for failed connections.
|
|
|
+ drbg, err := drbg.NewHashDrbg(st.drbgSeed)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ rng := rand.New(drbg)
|
|
|
+
|
|
|
+ sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
|
|
|
+ return sf, nil
|
|
|
+}
|
|
|
+
|
|
|
+type obfs4ClientFactory struct {
|
|
|
+ transport base.Transport
|
|
|
+}
|
|
|
+
|
|
|
+func (cf *obfs4ClientFactory) Transport() base.Transport {
|
|
|
+ return cf.transport
|
|
|
+}
|
|
|
+
|
|
|
+func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
|
|
|
+ var nodeID *ntor.NodeID
|
|
|
+ var publicKey *ntor.PublicKey
|
|
|
+
|
|
|
+ // The "new" (version >= 0.0.3) bridge lines use a unified "cert" argument
|
|
|
+ // for the Node ID and Public Key.
|
|
|
+ certStr, ok := args.Get(certArg)
|
|
|
+ if ok {
|
|
|
+ cert, err := serverCertFromString(certStr)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ nodeID, publicKey = cert.unpack()
|
|
|
+ } else {
|
|
|
+ // The "old" style (version <= 0.0.2) bridge lines use separate Node ID
|
|
|
+ // and Public Key arguments in Base16 encoding and are a UX disaster.
|
|
|
+ nodeIDStr, ok := args.Get(nodeIDArg)
|
|
|
+ if !ok {
|
|
|
+ return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
|
|
|
+ }
|
|
|
+ var err error
|
|
|
+ if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ publicKeyStr, ok := args.Get(publicKeyArg)
|
|
|
+ if !ok {
|
|
|
+ return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
|
|
|
+ }
|
|
|
+ if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // IAT config is common across the two bridge line formats.
|
|
|
+ iatStr, ok := args.Get(iatArg)
|
|
|
+ if !ok {
|
|
|
+ return nil, fmt.Errorf("missing argument '%s'", iatArg)
|
|
|
+ }
|
|
|
+ iatMode, err := strconv.Atoi(iatStr)
|
|
|
+ if err != nil || iatMode < iatNone || iatMode > iatParanoid {
|
|
|
+ return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate the session key pair before connectiong to hide the Elligator2
|
|
|
+ // rejection sampling from network observers.
|
|
|
+ sessionKey, err := ntor.NewKeypair(true)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return &obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (cf *obfs4ClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
|
|
|
+ // Validate args before bothering to open connection.
|
|
|
+ ca, ok := args.(*obfs4ClientArgs)
|
|
|
+ if !ok {
|
|
|
+ return nil, fmt.Errorf("invalid argument type for args")
|
|
|
+ }
|
|
|
+ conn, err := dialFn(network, addr)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ dialConn := conn
|
|
|
+ if conn, err = newObfs4ClientConn(conn, ca); err != nil {
|
|
|
+ dialConn.Close()
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return conn, nil
|
|
|
+}
|
|
|
+
|
|
|
+type obfs4ServerFactory struct {
|
|
|
+ transport base.Transport
|
|
|
+ args *pt.Args
|
|
|
+
|
|
|
+ nodeID *ntor.NodeID
|
|
|
+ identityKey *ntor.Keypair
|
|
|
+ lenSeed *drbg.Seed
|
|
|
+ iatSeed *drbg.Seed
|
|
|
+ iatMode int
|
|
|
+ replayFilter *replayfilter.ReplayFilter
|
|
|
+
|
|
|
+ closeDelayBytes int
|
|
|
+ closeDelay int
|
|
|
+}
|
|
|
+
|
|
|
+func (sf *obfs4ServerFactory) Transport() base.Transport {
|
|
|
+ return sf.transport
|
|
|
+}
|
|
|
+
|
|
|
+func (sf *obfs4ServerFactory) Args() *pt.Args {
|
|
|
+ return sf.args
|
|
|
+}
|
|
|
+
|
|
|
+func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
|
|
|
+ // Not much point in having a separate newObfs4ServerConn routine when
|
|
|
+ // wrapping requires using values from the factory instance.
|
|
|
+
|
|
|
+ // Generate the session keypair *before* consuming data from the peer, to
|
|
|
+ // attempt to mask the rejection sampling due to use of Elligator2. This
|
|
|
+ // might be futile, but the timing differential isn't very large on modern
|
|
|
+ // hardware, and there are far easier statistical attacks that can be
|
|
|
+ // mounted as a distinguisher.
|
|
|
+ sessionKey, err := ntor.NewKeypair(true)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist)
|
|
|
+ var iatDist *probdist.WeightedDist
|
|
|
+ if sf.iatSeed != nil {
|
|
|
+ iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
|
|
|
+ }
|
|
|
+
|
|
|
+ c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
|
|
|
+
|
|
|
+ startTime := time.Now()
|
|
|
+
|
|
|
+ if err = c.serverHandshake(sf, sessionKey); err != nil {
|
|
|
+ c.closeAfterDelay(sf, startTime)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return c, nil
|
|
|
+}
|
|
|
+
|
|
|
+type obfs4Conn struct {
|
|
|
+ net.Conn
|
|
|
+
|
|
|
+ isServer bool
|
|
|
+
|
|
|
+ lenDist *probdist.WeightedDist
|
|
|
+ iatDist *probdist.WeightedDist
|
|
|
+ iatMode int
|
|
|
+
|
|
|
+ receiveBuffer *bytes.Buffer
|
|
|
+ receiveDecodedBuffer *bytes.Buffer
|
|
|
+ readBuffer []byte
|
|
|
+
|
|
|
+ encoder *framing.Encoder
|
|
|
+ decoder *framing.Decoder
|
|
|
+}
|
|
|
+
|
|
|
+func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
|
|
|
+ // Generate the initial protocol polymorphism distribution(s).
|
|
|
+ var seed *drbg.Seed
|
|
|
+ if seed, err = drbg.NewSeed(); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
|
|
|
+ var iatDist *probdist.WeightedDist
|
|
|
+ if args.iatMode != iatNone {
|
|
|
+ var iatSeed *drbg.Seed
|
|
|
+ iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
|
|
|
+ if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ iatDist = probdist.New(iatSeed, 0, maxIATDelay, biasedDist)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Allocate the client structure.
|
|
|
+ c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
|
|
|
+
|
|
|
+ // Start the handshake timeout.
|
|
|
+ deadline := time.Now().Add(clientHandshakeTimeout)
|
|
|
+ if err = conn.SetDeadline(deadline); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Stop the handshake timeout.
|
|
|
+ if err = conn.SetDeadline(time.Time{}); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error {
|
|
|
+ if conn.isServer {
|
|
|
+ return fmt.Errorf("clientHandshake called on server connection")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate and send the client handshake.
|
|
|
+ hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey)
|
|
|
+ blob, err := hs.generateHandshake()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if _, err = conn.Conn.Write(blob); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Consume the server handshake.
|
|
|
+ var hsBuf [maxHandshakeLength]byte
|
|
|
+ for {
|
|
|
+ n, err := conn.Conn.Read(hsBuf[:])
|
|
|
+ if err != nil {
|
|
|
+ // The Read() could have returned data and an error, but there is
|
|
|
+ // no point in continuing on an EOF or whatever.
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ conn.receiveBuffer.Write(hsBuf[:n])
|
|
|
+
|
|
|
+ n, seed, err := hs.parseServerHandshake(conn.receiveBuffer.Bytes())
|
|
|
+ if err == ErrMarkNotFoundYet {
|
|
|
+ continue
|
|
|
+ } else if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ _ = conn.receiveBuffer.Next(n)
|
|
|
+
|
|
|
+ // Use the derived key material to intialize the link crypto.
|
|
|
+ okm := ntor.Kdf(seed, framing.KeyLength*2)
|
|
|
+ conn.encoder = framing.NewEncoder(okm[:framing.KeyLength])
|
|
|
+ conn.decoder = framing.NewDecoder(okm[framing.KeyLength:])
|
|
|
+
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.Keypair) error {
|
|
|
+ if !conn.isServer {
|
|
|
+ return fmt.Errorf("serverHandshake called on client connection")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate the server handshake, and arm the base timeout.
|
|
|
+ hs := newServerHandshake(sf.nodeID, sf.identityKey, sessionKey)
|
|
|
+ if err := conn.Conn.SetDeadline(time.Now().Add(serverHandshakeTimeout)); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Consume the client handshake.
|
|
|
+ var hsBuf [maxHandshakeLength]byte
|
|
|
+ for {
|
|
|
+ n, err := conn.Conn.Read(hsBuf[:])
|
|
|
+ if err != nil {
|
|
|
+ // The Read() could have returned data and an error, but there is
|
|
|
+ // no point in continuing on an EOF or whatever.
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ conn.receiveBuffer.Write(hsBuf[:n])
|
|
|
+
|
|
|
+ seed, err := hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes())
|
|
|
+ if err == ErrMarkNotFoundYet {
|
|
|
+ continue
|
|
|
+ } else if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ conn.receiveBuffer.Reset()
|
|
|
+
|
|
|
+ if err := conn.Conn.SetDeadline(time.Time{}); err != nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Use the derived key material to intialize the link crypto.
|
|
|
+ okm := ntor.Kdf(seed, framing.KeyLength*2)
|
|
|
+ conn.encoder = framing.NewEncoder(okm[framing.KeyLength:])
|
|
|
+ conn.decoder = framing.NewDecoder(okm[:framing.KeyLength])
|
|
|
+
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // Since the current and only implementation always sends a PRNG seed for
|
|
|
+ // the length obfuscation, this makes the amount of data received from the
|
|
|
+ // server inconsistent with the length sent from the client.
|
|
|
+ //
|
|
|
+ // Rebalance this by tweaking the client mimimum padding/server maximum
|
|
|
+ // padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed
|
|
|
+ // as part of the server response). See inlineSeedFrameLength in
|
|
|
+ // handshake_ntor.go.
|
|
|
+
|
|
|
+ // Generate/send the response.
|
|
|
+ blob, err := hs.generateHandshake()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ var frameBuf bytes.Buffer
|
|
|
+ if _, err = frameBuf.Write(blob); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Send the PRNG seed as the first packet.
|
|
|
+ if err := conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) Read(b []byte) (n int, err error) {
|
|
|
+ // If there is no payload from the previous Read() calls, consume data off
|
|
|
+ // the network. Not all data received is guaranteed to be usable payload,
|
|
|
+ // so do this in a loop till data is present or an error occurs.
|
|
|
+ for conn.receiveDecodedBuffer.Len() == 0 {
|
|
|
+ err = conn.readPackets()
|
|
|
+ if err == framing.ErrAgain {
|
|
|
+ // Don't proagate this back up the call stack if we happen to break
|
|
|
+ // out of the loop.
|
|
|
+ err = nil
|
|
|
+ continue
|
|
|
+ } else if err != nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Even if err is set, attempt to do the read anyway so that all decoded
|
|
|
+ // data gets relayed before the connection is torn down.
|
|
|
+ if conn.receiveDecodedBuffer.Len() > 0 {
|
|
|
+ var berr error
|
|
|
+ n, berr = conn.receiveDecodedBuffer.Read(b)
|
|
|
+ if err == nil {
|
|
|
+ // Only propagate berr if there are not more important (fatal)
|
|
|
+ // errors from the network/crypto/packet processing.
|
|
|
+ err = berr
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) Write(b []byte) (n int, err error) {
|
|
|
+ chopBuf := bytes.NewBuffer(b)
|
|
|
+ var payload [maxPacketPayloadLength]byte
|
|
|
+ var frameBuf bytes.Buffer
|
|
|
+
|
|
|
+ // Chop the pending data into payload frames.
|
|
|
+ for chopBuf.Len() > 0 {
|
|
|
+ // Send maximum sized frames.
|
|
|
+ rdLen := 0
|
|
|
+ rdLen, err = chopBuf.Read(payload[:])
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
+ } else if rdLen == 0 {
|
|
|
+ panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
|
|
|
+ }
|
|
|
+ n += rdLen
|
|
|
+
|
|
|
+ err = conn.makePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
|
|
|
+ if 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.iatMode != iatNone {
|
|
|
+ var iatFrame [framing.MaximumSegmentLength]byte
|
|
|
+ for frameBuf.Len() > 0 {
|
|
|
+ iatWrLen := 0
|
|
|
+
|
|
|
+ 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 {
|
|
|
+ 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(conn.iatDist.Sample() * 100)
|
|
|
+
|
|
|
+ // Write then sleep.
|
|
|
+ _, err = conn.Conn.Write(iatFrame[:iatWrLen])
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
+ }
|
|
|
+ time.Sleep(iatDelta * time.Microsecond)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _, err = conn.Conn.Write(frameBuf.Bytes())
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) SetDeadline(t time.Time) error {
|
|
|
+ return syscall.ENOTSUP
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
|
|
|
+ return syscall.ENOTSUP
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) {
|
|
|
+ // I-it's not like I w-wanna handshake with you or anything. B-b-baka!
|
|
|
+ defer conn.Conn.Close()
|
|
|
+
|
|
|
+ delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout
|
|
|
+ deadline := startTime.Add(delay)
|
|
|
+ if time.Now().After(deadline) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := conn.Conn.SetReadDeadline(deadline); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Consume and discard data on this connection until either the specified
|
|
|
+ // interval passes or a certain size has been reached.
|
|
|
+ discarded := 0
|
|
|
+ var buf [framing.MaximumSegmentLength]byte
|
|
|
+ for discarded < int(sf.closeDelayBytes) {
|
|
|
+ n, err := conn.Conn.Read(buf[:])
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ discarded += n
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) {
|
|
|
+ tailLen := burst.Len() % framing.MaximumSegmentLength
|
|
|
+
|
|
|
+ padLen := 0
|
|
|
+ if toPadTo >= tailLen {
|
|
|
+ padLen = toPadTo - tailLen
|
|
|
+ } else {
|
|
|
+ padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
|
|
|
+ }
|
|
|
+
|
|
|
+ if padLen > headerLength {
|
|
|
+ err = conn.makePacket(burst, packetTypePayload, []byte{},
|
|
|
+ uint16(padLen-headerLength))
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else if padLen > 0 {
|
|
|
+ err = conn.makePacket(burst, packetTypePayload, []byte{},
|
|
|
+ maxPacketPayloadLength)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ err = conn.makePacket(burst, packetTypePayload, []byte{},
|
|
|
+ uint16(padLen))
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func init() {
|
|
|
+ flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation")
|
|
|
+}
|
|
|
+
|
|
|
+var _ base.ClientFactory = (*obfs4ClientFactory)(nil)
|
|
|
+var _ base.ServerFactory = (*obfs4ServerFactory)(nil)
|
|
|
+var _ base.Transport = (*Transport)(nil)
|
|
|
+var _ net.Conn = (*obfs4Conn)(nil)
|