summaryrefslogtreecommitdiff
path: root/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go')
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go714
1 files changed, 714 insertions, 0 deletions
diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go
new file mode 100644
index 0000000..420c466
--- /dev/null
+++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go
@@ -0,0 +1,714 @@
+/*
+ * 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 (
+ "bytes"
+ "crypto/sha256"
+ "flag"
+ "fmt"
+ "math/rand"
+ "net"
+ "strconv"
+ "syscall"
+ "time"
+
+ "golang.org/x/net/proxy"
+
+ "github.com/OperatorFoundation/obfs4/common/drbg"
+ "github.com/OperatorFoundation/obfs4/common/ntor"
+ "github.com/OperatorFoundation/obfs4/common/probdist"
+ "github.com/OperatorFoundation/obfs4/common/replayfilter"
+ "github.com/OperatorFoundation/shapeshifter-ipc"
+ "github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/framing"
+)
+
+const (
+ 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
+
+// Transport that uses the obfs4 protocol to shapeshift the application network traffic
+type Obfs4Transport struct {
+ dialer proxy.Dialer
+
+ serverFactory *Obfs4ServerFactory
+ clientArgs *Obfs4ClientArgs
+}
+
+type Obfs4ServerFactory struct {
+ args *pt.Args
+
+ nodeID *ntor.NodeID
+ identityKey *ntor.Keypair
+ lenSeed *drbg.Seed
+ iatSeed *drbg.Seed
+ iatMode int
+ replayFilter *replayfilter.ReplayFilter
+
+ closeDelayBytes int
+ closeDelay int
+}
+
+type Obfs4ClientArgs struct {
+ nodeID *ntor.NodeID
+ publicKey *ntor.PublicKey
+ sessionKey *ntor.Keypair
+ iatMode int
+}
+
+func NewObfs4Server(stateDir string) (*Obfs4Transport, error) {
+ args := make(pt.Args)
+ 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{&ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
+
+ return &Obfs4Transport{dialer: nil, serverFactory: sf, clientArgs: nil}, nil
+}
+
+func NewObfs4Client(certString string, iatMode int, dialer proxy.Dialer) (*Obfs4Transport, 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.
+ cert, err := serverCertFromString(certString)
+ if err != nil {
+ return nil, err
+ }
+ nodeID, publicKey = cert.unpack()
+
+ // 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 &Obfs4Transport{dialer: dialer, serverFactory: nil, clientArgs: &Obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}}, nil
+}
+
+// Create outgoing transport connection
+func (transport *Obfs4Transport) Dial(address string) (net.Conn, error) {
+ dialFn := transport.dialer.Dial
+ conn, dialErr := dialFn("tcp", address)
+ if dialErr != nil {
+ return nil, dialErr
+ }
+
+ dialConn := conn
+ transportConn, err := newObfs4ClientConn(conn, transport.clientArgs)
+ if err != nil {
+ dialConn.Close()
+ return nil, err
+ }
+
+ return transportConn, nil
+}
+
+//begin code added from optimizer
+type Transport struct {
+ CertString string
+ IatMode int
+ Address string
+ Dialer proxy.Dialer
+}
+
+func (transport Transport) Dial() (net.Conn, error) {
+ Obfs4Transport, err := NewObfs4Client(transport.CertString, transport.IatMode, transport.Dialer)
+ if err != nil {
+ return nil, err
+ }
+ conn, err := Obfs4Transport.Dial(transport.Address)
+ if err != nil {
+ return nil, err
+ }
+ return conn, nil
+}
+
+//end code added from optimizer
+// Create listener for incoming transport connection
+func (transport *Obfs4Transport) Listen(address string) net.Listener {
+ addr, resolveErr := pt.ResolveAddr(address)
+ if resolveErr != nil {
+ fmt.Println(resolveErr.Error())
+ return nil
+ }
+
+ ln, err := net.ListenTCP("tcp", addr)
+ if err != nil {
+ fmt.Println(err.Error())
+ return nil
+ }
+
+ return newObfs4TransportListener(transport.serverFactory, ln)
+}
+
+func (transport *Obfs4Transport) Close() error {
+ return nil
+}
+
+// End methods that implement the base.Transport interface
+
+// Listener that accepts connections using the obfs4 transport to communicate
+type obfs4TransportListener struct {
+ serverFactory *Obfs4ServerFactory
+
+ listener *net.TCPListener
+}
+
+// Private initializer for the obfs4 listener.
+// You get a new listener instance by calling the Listen method on the Transport.
+func newObfs4TransportListener(sf *Obfs4ServerFactory, listener *net.TCPListener) *obfs4TransportListener {
+ return &obfs4TransportListener{serverFactory: sf, listener: listener}
+}
+
+// Methods that implement the net.Listener interface
+
+// Listener for underlying network connection
+func (listener *obfs4TransportListener) NetworkListener() net.Listener {
+ return listener.listener
+}
+
+// Accept waits for and returns the next connection to the listener.
+func (listener *obfs4TransportListener) Accept() (net.Conn, error) {
+ conn, err := listener.listener.Accept()
+ if err != nil {
+ return nil, err
+ }
+
+ return newObfs4ServerConn(conn, listener.serverFactory)
+}
+
+func (listener *obfs4TransportListener) Addr() net.Addr {
+ interfaces, _ := net.Interfaces()
+ addrs, _ := interfaces[0].Addrs()
+ return addrs[0]
+}
+
+// Close closes the transport listener.
+// Any blocked Accept operations will be unblocked and return errors.
+func (listener *obfs4TransportListener) Close() error {
+ return listener.listener.Close()
+}
+
+// End methods that implement the net.Listener interface
+
+// Implementation of net.Conn, which also requires implementing net.Conn
+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
+}
+
+// Private initializer methods
+
+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 newObfs4ServerConn(conn net.Conn, sf *Obfs4ServerFactory) (*obfs4Conn, 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
+}
+
+// End initializer methods
+
+// Methods that implement the net.Conn interface
+func (transportConn *obfs4Conn) NetworkConn() net.Conn {
+ return transportConn.Conn
+}
+
+// End methods that implement the net.Conn interface
+
+// Methods implementing net.Conn
+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) Close() error {
+ return conn.Conn.Close()
+}
+
+func (conn *obfs4Conn) LocalAddr() net.Addr {
+ return conn.Conn.LocalAddr()
+}
+
+func (conn *obfs4Conn) RemoteAddr() net.Addr {
+ return conn.Conn.RemoteAddr()
+}
+
+func (conn *obfs4Conn) SetDeadline(t time.Time) error {
+ return syscall.ENOTSUP
+}
+
+func (conn *obfs4Conn) SetReadDeadline(t time.Time) error {
+ return conn.Conn.SetReadDeadline(t)
+}
+
+func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
+ return syscall.ENOTSUP
+}
+
+// End of methods implementing net.Conn
+
+// Private methods implementing the obfs4 protocol
+
+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) 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")
+}
+
+// End private methods implementing the obfs4 protocol
+
+// Force type checks to make sure that instances conform to interfaces
+var _ net.Listener = (*obfs4TransportListener)(nil)
+var _ net.Conn = (*obfs4Conn)(nil)