feat(tcp): implement FETAnalyzer with segment buffering feat(tcp): enhance TCP engine with window scale detection test(tcp): add tests for Minecraft and FET analyzers docs: add documentation for Minecraft analyzer and TCP evasion
116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
package tcp
|
|
|
|
import (
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/apernet/OpenGFW/analyzer"
|
|
)
|
|
|
|
// feedFET runs a sequence of client-to-server segments through a fresh stream
|
|
// and returns the props it settled on, or nil if it never reached a verdict.
|
|
func feedFET(t *testing.T, segments ...[]byte) analyzer.PropMap {
|
|
t.Helper()
|
|
s := newFETStream(nil)
|
|
for _, seg := range segments {
|
|
u, done := s.Feed(false, false, false, 0, seg)
|
|
if done {
|
|
if u == nil {
|
|
return nil
|
|
}
|
|
return u.M
|
|
}
|
|
}
|
|
if u := s.Close(false); u != nil {
|
|
return u.M
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// encryptedSample returns deterministic high-entropy bytes, standing in for the
|
|
// first segment of a fully encrypted proxy protocol such as Shadowsocks.
|
|
func encryptedSample(n int) []byte {
|
|
r := rand.New(rand.NewSource(1))
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = byte(r.Intn(256))
|
|
}
|
|
return b
|
|
}
|
|
|
|
func tlsClientHelloSample() []byte {
|
|
return append([]byte{0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc}, encryptedSample(64)...)
|
|
}
|
|
|
|
// A peer can force a one-byte first segment by advertising a tiny TCP receive
|
|
// window in its SYN-ACK. The verdict must not depend on where the boundary
|
|
// falls: classifying the first segment alone used to exempt these streams.
|
|
func TestFETVerdictIsSegmentationIndependent(t *testing.T) {
|
|
samples := map[string][]byte{
|
|
"encrypted": encryptedSample(64),
|
|
"tls": tlsClientHelloSample(),
|
|
"http": []byte("GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n"),
|
|
}
|
|
for name, data := range samples {
|
|
t.Run(name, func(t *testing.T) {
|
|
whole := feedFET(t, data)
|
|
if whole == nil {
|
|
t.Fatal("no verdict on unsplit data")
|
|
}
|
|
split := feedFET(t, data[:1], data[1:])
|
|
if got, want := split.Get("yes"), whole.Get("yes"); got != want {
|
|
t.Errorf("split verdict yes = %v, unsplit = %v", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFETDetectsEncryptedFirstSegment(t *testing.T) {
|
|
data := encryptedSample(64)
|
|
if got := feedFET(t, data).Get("yes"); got != true {
|
|
t.Fatalf("unsplit: yes = %v, want true", got)
|
|
}
|
|
if got := feedFET(t, data[:1], data[1:]).Get("yes"); got != true {
|
|
t.Errorf("one-byte first segment: yes = %v, want true", got)
|
|
}
|
|
}
|
|
|
|
func TestFETExemptsTLS(t *testing.T) {
|
|
data := tlsClientHelloSample()
|
|
if got := feedFET(t, data).Get("yes"); got != false {
|
|
t.Fatalf("unsplit: yes = %v, want false", got)
|
|
}
|
|
if got := feedFET(t, data[:1], data[1:]).Get("yes"); got != false {
|
|
t.Errorf("one-byte first segment: yes = %v, want false", got)
|
|
}
|
|
}
|
|
|
|
// Waiting for more data must be bounded, or a peer that sends one byte and then
|
|
// stops would hold the stream open until the TCP timeout, keeping every packet
|
|
// of the connection in userspace.
|
|
func TestFETGivesUpAfterMaxSegments(t *testing.T) {
|
|
s := newFETStream(nil)
|
|
for i := 0; i < fetMaxSegments-1; i++ {
|
|
if _, done := s.Feed(false, false, false, 0, []byte{0x00}); done {
|
|
t.Fatalf("verdict reached after %d segments, want to keep waiting", i+1)
|
|
}
|
|
}
|
|
u, done := s.Feed(false, false, false, 0, []byte{0x00})
|
|
if !done || u == nil {
|
|
t.Fatalf("no verdict after %d segments", fetMaxSegments)
|
|
}
|
|
}
|
|
|
|
// The metrics only describe a client's opening bytes, so a server that speaks
|
|
// first should end the stream rather than be classified as if it were a client.
|
|
func TestFETServerSpeaksFirst(t *testing.T) {
|
|
s := newFETStream(nil)
|
|
u, done := s.Feed(true, false, false, 0, encryptedSample(64))
|
|
if !done {
|
|
t.Error("stream not done after server-first data")
|
|
}
|
|
if u != nil {
|
|
t.Errorf("props = %#v, want none", u.M)
|
|
}
|
|
}
|