Browse Source

Optimized QUIC DPI

Rod Hynes 7 years ago
parent
commit
ba7008e049
1 changed files with 30 additions and 18 deletions
  1. 30 18
      psiphon/common/quic/obfuscator.go

+ 30 - 18
psiphon/common/quic/obfuscator.go

@@ -20,7 +20,6 @@
 package quic
 
 import (
-	"bytes"
 	"crypto/rand"
 	"crypto/sha256"
 	"fmt"
@@ -310,27 +309,40 @@ func (conn *ObfuscatedPacketConn) WriteTo(p []byte, addr net.Addr) (int, error)
 	return n, err
 }
 
-var fingerprints = [][]byte{
-	[]byte("CHLO"),
-	[]byte("SNI"),
-}
-
 func isQUIC(buffer []byte) bool {
 
-	// Note: we tested and rejected QUIC nDPI's detection:
-	// https://github.com/ntop/nDPI/blob/01bf295a19c19dc4f521ee40f0c478c794e1b5e4/src/lib/protocols/quic.c#L63
-	// nDPI assumes that, in the first client packet, the version is always present and starts with "Q"; and
-	// that certain Public Flags are set/unset. However v44 appears to send an initial packet with no version.
+	// As this function is called for every packet, it needs to be fast.
 	//
-	// In all currently supported versions, the first client packet contains the "CHLO" and "SNI" tags.
+	// In all currently supported versions, the first client packet contains
+	// the "CHLO" tag at one of the following offsets. The offset can vary for
+	// a single version.
 	//
-	// TODO: compute and look for tags in exact offsets
-
-	for _, fingerprint := range fingerprints {
-		if -1 == bytes.Index(buffer, fingerprint) {
-			return false
-		}
+	// Note that v44 does not include the "QUIC version" header field in its
+	// first client packet.
+	//
+	// As QUIC header parsing is complex, with many cases, we are not
+	// presently doing that, although this might improve accuracy as we should
+	// be able to identify the precise offset of "CHLO" based on header
+	// values.
+
+	if (len(buffer) >= 33 &&
+		buffer[29] == 'C' &&
+		buffer[30] == 'H' &&
+		buffer[31] == 'L' &&
+		buffer[32] == 'O') ||
+		(len(buffer) >= 35 &&
+			buffer[31] == 'C' &&
+			buffer[32] == 'H' &&
+			buffer[33] == 'L' &&
+			buffer[34] == 'O') ||
+		(len(buffer) >= 38 &&
+			buffer[34] == 'C' &&
+			buffer[35] == 'H' &&
+			buffer[36] == 'L' &&
+			buffer[37] == 'O') {
+
+		return true
 	}
 
-	return true
+	return false
 }