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
180 lines
4.2 KiB
Go
180 lines
4.2 KiB
Go
package tcp
|
|
|
|
import (
|
|
"unicode/utf8"
|
|
|
|
"github.com/apernet/OpenGFW/analyzer"
|
|
"github.com/apernet/OpenGFW/analyzer/utils"
|
|
)
|
|
|
|
const (
|
|
minecraftMaxHandshakePacketLen = 2048
|
|
minecraftMaxServerAddrLen = 255 * 4
|
|
|
|
minecraftPacketHandshake = 0
|
|
|
|
minecraftNextStateStatus = 1
|
|
minecraftNextStateLogin = 2
|
|
minecraftNextStateTransfer = 3
|
|
)
|
|
|
|
var _ analyzer.TCPAnalyzer = (*MinecraftAnalyzer)(nil)
|
|
|
|
type MinecraftAnalyzer struct{}
|
|
|
|
func (a *MinecraftAnalyzer) Name() string {
|
|
return "minecraft"
|
|
}
|
|
|
|
func (a *MinecraftAnalyzer) Limit() int {
|
|
return minecraftMaxHandshakePacketLen + 5
|
|
}
|
|
|
|
func (a *MinecraftAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
|
|
return newMinecraftStream(logger)
|
|
}
|
|
|
|
type minecraftStream struct {
|
|
logger analyzer.Logger
|
|
reqBuf *utils.ByteBuffer
|
|
}
|
|
|
|
func newMinecraftStream(logger analyzer.Logger) *minecraftStream {
|
|
return &minecraftStream{logger: logger, reqBuf: &utils.ByteBuffer{}}
|
|
}
|
|
|
|
func (s *minecraftStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
|
|
if skip != 0 {
|
|
return minecraftResult(false, false, nil), true
|
|
}
|
|
if len(data) == 0 {
|
|
return nil, false
|
|
}
|
|
if rev {
|
|
return nil, false
|
|
}
|
|
|
|
s.reqBuf.Append(data)
|
|
m, needMore, valid := parseMinecraftHandshake(s.reqBuf.Buf)
|
|
if needMore {
|
|
return minecraftResult(false, true, nil), false
|
|
}
|
|
if !valid {
|
|
return minecraftResult(false, false, nil), true
|
|
}
|
|
|
|
return minecraftResult(true, false, m), true
|
|
}
|
|
|
|
func (s *minecraftStream) Close(limited bool) *analyzer.PropUpdate {
|
|
s.reqBuf.Reset()
|
|
return minecraftResult(false, false, nil)
|
|
}
|
|
|
|
func minecraftResult(yes, candidate bool, m analyzer.PropMap) *analyzer.PropUpdate {
|
|
if m == nil {
|
|
m = analyzer.PropMap{}
|
|
}
|
|
m["yes"] = yes
|
|
m["candidate"] = candidate
|
|
return &analyzer.PropUpdate{
|
|
Type: analyzer.PropUpdateReplace,
|
|
M: m,
|
|
}
|
|
}
|
|
|
|
func parseMinecraftHandshake(buf []byte) (m analyzer.PropMap, needMore, valid bool) {
|
|
packetLen, off, ok, invalid := readMinecraftVarInt(buf, 0)
|
|
if invalid || packetLen <= 0 || packetLen > minecraftMaxHandshakePacketLen {
|
|
return nil, false, false
|
|
}
|
|
if !ok {
|
|
return nil, true, false
|
|
}
|
|
|
|
packetEnd := off + int(packetLen)
|
|
if len(buf) < packetEnd {
|
|
return nil, true, false
|
|
}
|
|
|
|
packet := buf[off:packetEnd]
|
|
packetID, pos, ok, invalid := readMinecraftVarInt(packet, 0)
|
|
if invalid || !ok || packetID != minecraftPacketHandshake {
|
|
return nil, false, false
|
|
}
|
|
|
|
protocolVersion, pos, ok, invalid := readMinecraftVarInt(packet, pos)
|
|
if invalid || !ok {
|
|
return nil, false, false
|
|
}
|
|
|
|
addrLen, pos, ok, invalid := readMinecraftVarInt(packet, pos)
|
|
if invalid || !ok || addrLen <= 0 || addrLen > minecraftMaxServerAddrLen {
|
|
return nil, false, false
|
|
}
|
|
|
|
addrEnd := pos + int(addrLen)
|
|
if addrEnd+2 > len(packet) {
|
|
return nil, false, false
|
|
}
|
|
addrBytes := packet[pos:addrEnd]
|
|
if !utf8.Valid(addrBytes) {
|
|
return nil, false, false
|
|
}
|
|
pos = addrEnd
|
|
|
|
serverPort := uint16(packet[pos])<<8 | uint16(packet[pos+1])
|
|
pos += 2
|
|
|
|
nextState, pos, ok, invalid := readMinecraftVarInt(packet, pos)
|
|
if invalid || !ok || !isMinecraftNextState(nextState) || pos != len(packet) {
|
|
return nil, false, false
|
|
}
|
|
|
|
return analyzer.PropMap{
|
|
"protocol": int(protocolVersion),
|
|
"server_addr": string(addrBytes),
|
|
"server_port": serverPort,
|
|
"next_state": int(nextState),
|
|
"next_state_name": minecraftNextStateName(nextState),
|
|
"packet_length": int(packetLen),
|
|
}, false, true
|
|
}
|
|
|
|
func readMinecraftVarInt(buf []byte, offset int) (value int32, next int, ok bool, invalid bool) {
|
|
var result int32
|
|
for i := 0; i < 5; i++ {
|
|
if offset+i >= len(buf) {
|
|
return 0, offset, false, false
|
|
}
|
|
b := buf[offset+i]
|
|
result |= int32(b&0x7f) << (7 * i)
|
|
if b&0x80 == 0 {
|
|
return result, offset + i + 1, true, false
|
|
}
|
|
}
|
|
return 0, offset, false, true
|
|
}
|
|
|
|
func isMinecraftNextState(state int32) bool {
|
|
switch state {
|
|
case minecraftNextStateStatus, minecraftNextStateLogin, minecraftNextStateTransfer:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func minecraftNextStateName(state int32) string {
|
|
switch state {
|
|
case minecraftNextStateStatus:
|
|
return "status"
|
|
case minecraftNextStateLogin:
|
|
return "login"
|
|
case minecraftNextStateTransfer:
|
|
return "transfer"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|