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
120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
package tcp
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/apernet/OpenGFW/analyzer"
|
|
)
|
|
|
|
func TestMinecraftParsing_HandshakeLogin(t *testing.T) {
|
|
handshake := buildMinecraftHandshake(t, 767, "211.136.162.178", 25565, minecraftNextStateLogin)
|
|
want := analyzer.PropMap{
|
|
"yes": true,
|
|
"candidate": false,
|
|
"protocol": 767,
|
|
"server_addr": "211.136.162.178",
|
|
"server_port": uint16(25565),
|
|
"next_state": minecraftNextStateLogin,
|
|
"next_state_name": "login",
|
|
"packet_length": len(handshake) - 1,
|
|
}
|
|
|
|
u, done := newMinecraftStream(nil).Feed(false, false, false, 0, handshake)
|
|
if !done {
|
|
t.Fatal("stream not done after full handshake")
|
|
}
|
|
if !reflect.DeepEqual(u.M, want) {
|
|
t.Errorf("parsed = %#v, want %#v", u.M, want)
|
|
}
|
|
}
|
|
|
|
func TestMinecraftParsing_FragmentedHandshake(t *testing.T) {
|
|
handshake := buildMinecraftHandshake(t, 767, "mc.example.com", 25565, minecraftNextStateStatus)
|
|
s := newMinecraftStream(nil)
|
|
|
|
u, done := s.Feed(false, false, false, 0, handshake[:2])
|
|
if done {
|
|
t.Fatal("stream done on partial handshake")
|
|
}
|
|
if got := u.M.Get("candidate"); got != true {
|
|
t.Fatalf("candidate = %v, want true", got)
|
|
}
|
|
|
|
u, done = s.Feed(false, false, false, 0, handshake[2:])
|
|
if !done {
|
|
t.Fatal("stream not done after remaining handshake")
|
|
}
|
|
if got := u.M.Get("yes"); got != true {
|
|
t.Fatalf("yes = %v, want true", got)
|
|
}
|
|
if got := u.M.Get("server_addr"); got != "mc.example.com" {
|
|
t.Fatalf("server_addr = %v, want mc.example.com", got)
|
|
}
|
|
}
|
|
|
|
func TestMinecraftParsing_InvalidAfterCandidate(t *testing.T) {
|
|
s := newMinecraftStream(nil)
|
|
u, done := s.Feed(false, false, false, 0, []byte{0x04})
|
|
if done {
|
|
t.Fatal("stream done on partial packet")
|
|
}
|
|
if got := u.M.Get("candidate"); got != true {
|
|
t.Fatalf("candidate = %v, want true", got)
|
|
}
|
|
|
|
u, done = s.Feed(false, false, false, 0, []byte{0x01, 0x02, 0x03, 0x04})
|
|
if !done {
|
|
t.Fatal("stream not done on invalid packet")
|
|
}
|
|
if got := u.M.Get("candidate"); got != false {
|
|
t.Fatalf("candidate = %v, want false", got)
|
|
}
|
|
if got := u.M.Get("yes"); got != false {
|
|
t.Fatalf("yes = %v, want false", got)
|
|
}
|
|
}
|
|
|
|
func TestMinecraftParsing_RejectsInvalidUTF8Address(t *testing.T) {
|
|
packet := []byte{0x00}
|
|
packet = append(packet, encodeMinecraftVarInt(767)...)
|
|
packet = append(packet, 0x01, 0xff)
|
|
packet = append(packet, 0x63, 0xdd)
|
|
packet = append(packet, encodeMinecraftVarInt(minecraftNextStateLogin)...)
|
|
data := append(encodeMinecraftVarInt(int32(len(packet))), packet...)
|
|
|
|
u, done := newMinecraftStream(nil).Feed(false, false, false, 0, data)
|
|
if !done {
|
|
t.Fatal("stream not done on invalid UTF-8 address")
|
|
}
|
|
if got := u.M.Get("yes"); got != false {
|
|
t.Fatalf("yes = %v, want false", got)
|
|
}
|
|
}
|
|
|
|
func buildMinecraftHandshake(t *testing.T, protocol int32, addr string, port uint16, nextState int32) []byte {
|
|
t.Helper()
|
|
packet := []byte{0x00}
|
|
packet = append(packet, encodeMinecraftVarInt(protocol)...)
|
|
packet = append(packet, encodeMinecraftVarInt(int32(len(addr)))...)
|
|
packet = append(packet, []byte(addr)...)
|
|
packet = append(packet, byte(port>>8), byte(port))
|
|
packet = append(packet, encodeMinecraftVarInt(nextState)...)
|
|
return append(encodeMinecraftVarInt(int32(len(packet))), packet...)
|
|
}
|
|
|
|
func encodeMinecraftVarInt(value int32) []byte {
|
|
var out []byte
|
|
for {
|
|
b := byte(value & 0x7f)
|
|
value = int32(uint32(value) >> 7)
|
|
if value != 0 {
|
|
b |= 0x80
|
|
}
|
|
out = append(out, b)
|
|
if value == 0 {
|
|
return out
|
|
}
|
|
}
|
|
}
|