feat(tcp): add Minecraft analyzer for handshake detection
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
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user