Browse Source

Add `Obfs4PacketIAT` (equivalent to `iat-mode=2`).

I'm still uncertain how effective this is, so servers will not offer it
by default, though clients will prefer it over either of the burst
modes if it is available.

The code for it basically already existed in the form of the short
write obfuscation handler, so offering it is easy enough to do.
Yawning Angel 3 years ago
parent
commit
98b4767cee
4 changed files with 60 additions and 25 deletions
  1. 14 4
      basket2proxy/main.go
  2. 1 0
      basket2proxy/server.go
  3. 7 2
      common.go
  4. 38 19
      padding_obfs4.go

+ 14 - 4
basket2proxy/main.go

@@ -168,7 +168,7 @@ func overrideKEXMethods(s string) error {
 	return nil
 }
 
-func overridePaddingMethods(s string) error {
+func overridePaddingMethods(s string, isClient bool) error {
 	var methods []basket2.PaddingMethod
 
 	if s == "" {
@@ -177,6 +177,14 @@ func overridePaddingMethods(s string) error {
 		for _, m := range enabledPaddingMethods {
 			switch m {
 			case basket2.PaddingNull, basket2.PaddingTamaraw:
+				// PaddingNull - Unobfuscated.
+				// PaddingTamaraw - Extreme overhead.
+			case basket2.PaddingObfs4PacketIAT:
+				// PaddingObfs4PacketIAT - Clients should use it if available,
+				//   servers should not offer it by default.
+				if isClient {
+					methods = append(methods, m)
+				}
 			default:
 				methods = append(methods, m)
 			}
@@ -241,9 +249,6 @@ func main() {
 	if err := overrideKEXMethods(*kexMethodsStr); err != nil {
 		golog.Fatalf("%s: [ERROR]: Failed to set KEX methods: %v", execName, err)
 	}
-	if err := overridePaddingMethods(*paddingMethodsStr); err != nil {
-		golog.Fatalf("%s: [ERROR]: Failed to set padding methods: %v", execName, err)
-	}
 	if *bufferSizeArg != 0 {
 		copyBufferSize = *bufferSizeArg
 		if copyBufferSize < 8*1024 {
@@ -260,6 +265,11 @@ func main() {
 		golog.Fatalf("%s: [ERROR]: No state directory: %v", execName, err)
 	}
 
+	// Override the padding methods after it's know if this is a client or not.
+	if err := overridePaddingMethods(*paddingMethodsStr, isClient); err != nil {
+		golog.Fatalf("%s: [ERROR]: Failed to set padding methods: %v", execName, err)
+	}
+
 	// Bring file backed logging online.
 	if *enableLogging {
 		logFilePath := path.Join(stateDir, basket2proxyLogFile)

+ 1 - 0
basket2proxy/server.go

@@ -109,6 +109,7 @@ func (s *serverState) initPaddingParams() error {
 	methods := []basket2.PaddingMethod{
 		basket2.PaddingObfs4Burst,
 		basket2.PaddingObfs4BurstIAT,
+		basket2.PaddingObfs4PacketIAT,
 	}
 
 	paramsFile := path.Join(stateDir, bridgeParamsFile)

+ 7 - 2
common.go

@@ -80,6 +80,7 @@ var (
 
 	supportedPaddingMethods = []PaddingMethod{
 		PaddingTamaraw,
+		PaddingObfs4PacketIAT,
 		PaddingObfs4BurstIAT,
 		PaddingObfs4Burst,
 		PaddingNull,
@@ -124,6 +125,8 @@ func (m PaddingMethod) ToString() string {
 		return "Obfs4Burst"
 	case PaddingObfs4BurstIAT:
 		return "Obfs4BurstIAT"
+	case PaddingObfs4PacketIAT:
+		return "Obfs4PacketIAT"
 	case PaddingTamaraw:
 		return "Tamaraw"
 	default:
@@ -141,6 +144,8 @@ func PaddingMethodFromString(s string) PaddingMethod {
 		return PaddingObfs4Burst
 	case "Obfs4BurstIAT":
 		return PaddingObfs4BurstIAT
+	case "Obfs4PacketIAT":
+		return PaddingObfs4PacketIAT
 	case "Tamaraw":
 		return PaddingTamaraw
 	default:
@@ -397,7 +402,7 @@ func (c *commonConn) setPadding(method PaddingMethod, params []byte) error {
 	switch method {
 	case PaddingNull:
 		c.impl = newNullPadding(c)
-	case PaddingObfs4Burst, PaddingObfs4BurstIAT:
+	case PaddingObfs4Burst, PaddingObfs4BurstIAT, PaddingObfs4PacketIAT:
 		var err error
 		c.impl, err = newObfs4Padding(c, method, params)
 		if err != nil {
@@ -544,7 +549,7 @@ func DefaultPaddingParams(method PaddingMethod) ([]byte, error) {
 	switch method {
 	case PaddingNull, PaddingTamaraw:
 		return nil, nil
-	case PaddingObfs4Burst, PaddingObfs4BurstIAT:
+	case PaddingObfs4Burst, PaddingObfs4BurstIAT, PaddingObfs4PacketIAT:
 		return obfs4PaddingDefaultParams(method)
 	}
 	return nil, ErrInvalidPadding

+ 38 - 19
padding_obfs4.go

@@ -40,6 +40,12 @@ const (
 	// code thinks we are in the middle of a large burst.
 	PaddingObfs4BurstIAT PaddingMethod = 2
 
+	// PaddingObfs4PacketIAT is the obfs4 style padding algorithm
+	// approximately equivalent to the obfs4 `iat-mode=2` configuration.
+	// Writes are broken up into random sized packets, and randomized
+	// delay is inserted unconditionally.
+	PaddingObfs4PacketIAT PaddingMethod = 3
+
 	// Obfs4SeedLength is the length of the randomness to provide to the obfs4
 	// padding algoriths to parameterize the distributions.
 	Obfs4SeedLength = 32
@@ -58,16 +64,21 @@ type obfs4Padding struct {
 }
 
 func (p *obfs4Padding) shortWrite(b []byte) (n int, err error) {
-	// Special case len(p) being "short".
-	//
-	// This is kind of annoying to obfuscate, since sending 2 segments isn't
-	// that different from sending 1 segment, and I assume the forces of evil
-	// know how to count.
-	//
-	// So, attempt to be somewhat clever by disabling Nagle and sending short
-	// records sized to something from the distribution.
-	p.conn.setNagle(false)
-	defer p.conn.setNagle(true)
+	if p.method != PaddingObfs4PacketIAT {
+		// Special case len(p) being "short".
+		//
+		// This is kind of annoying to obfuscate, since sending 2 segments
+		// isn't that different from sending 1 segment, and I assume the
+		// forces of evil know how to count.
+		//
+		// So, attempt to be somewhat clever by disabling Nagle and sending
+		// short records sized to something from the distribution.
+		//
+		// These concerns naturally do not apply to the packetization
+		// based obfuscation method.
+		p.conn.setNagle(false)
+		defer p.conn.setNagle(true)
+	}
 
 	for remaining := len(b); remaining > 0; {
 		// Sample from the "short" distribution, which omits values less than
@@ -155,7 +166,7 @@ func (p *obfs4Padding) largeWrite(b []byte) (n int, err error) {
 }
 
 func (p *obfs4Padding) Write(b []byte) (n int, err error) {
-	if len(b) < p.conn.maxRecordSize {
+	if len(b) < p.conn.maxRecordSize || p.method == PaddingObfs4PacketIAT {
 		n, err = p.shortWrite(b)
 	} else {
 		n, err = p.largeWrite(b)
@@ -197,20 +208,28 @@ func newObfs4Padding(conn *commonConn, m PaddingMethod, seed []byte) (paddingImp
 		p.conn.enableReadDelay = true
 	}
 
-	// There's a fundemental mismatch between what our idea of a packet should
-	// be and what should be sent over the wire due to unavailable/inaccurate
-	// PMTU information, and variable length TCP headers (SACK options).
-	//
-	// So fuck it, enable Nagle's algorithm and hope that it helps to mask the
-	// disconnect.
-	conn.setNagle(true)
+	if m == PaddingObfs4PacketIAT {
+		// The packetized padding will never send large writes, and thus
+		// record size enforcement can be enabled, and we can skip messing
+		// with Nagle entirely.
+		p.conn.enforceRecordSize = true
+	} else {
+		// There's a fundemental mismatch between what our idea of a packet
+		//should // be and what should be sent over the wire due to
+		// unavailable/inaccurate PMTU information, and variable length TCP
+		// headers (SACK options).
+		//
+		// So fuck it, enable Nagle's algorithm and hope that it helps to
+		// mask the disconnect.
+		conn.setNagle(true)
+	}
 
 	return p, nil
 }
 
 func obfs4PaddingDefaultParams(method PaddingMethod) ([]byte, error) {
 	switch method {
-	case PaddingObfs4Burst, PaddingObfs4BurstIAT:
+	case PaddingObfs4Burst, PaddingObfs4BurstIAT, PaddingObfs4PacketIAT:
 		seed := make([]byte, Obfs4SeedLength)
 		if _, err := io.ReadFull(rand.Reader, seed); err != nil {
 			return nil, err