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:
+51
-6
@@ -1,6 +1,21 @@
|
||||
package tcp
|
||||
|
||||
import "github.com/apernet/OpenGFW/analyzer"
|
||||
import (
|
||||
"github.com/apernet/OpenGFW/analyzer"
|
||||
"github.com/apernet/OpenGFW/analyzer/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// fetMinDataLen is how many bytes we want before classifying a stream.
|
||||
// The metrics below are meaningless on a handful of bytes - a peer can
|
||||
// force a tiny first segment by advertising a small TCP receive window in
|
||||
// its SYN-ACK, which used to be enough to make us exempt the connection.
|
||||
fetMinDataLen = 16
|
||||
// fetMaxSegments bounds how long we wait for fetMinDataLen bytes. Without
|
||||
// it, a peer that sends one byte and then goes quiet would keep the stream
|
||||
// active - and therefore out of the connmark bypass - until the TCP timeout.
|
||||
fetMaxSegments = 4
|
||||
)
|
||||
|
||||
var _ analyzer.TCPAnalyzer = (*FETAnalyzer)(nil)
|
||||
|
||||
@@ -25,6 +40,8 @@ func (a *FETAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) anal
|
||||
|
||||
type fetStream struct {
|
||||
logger analyzer.Logger
|
||||
buf utils.ByteBuffer
|
||||
segs int
|
||||
}
|
||||
|
||||
func newFETStream(logger analyzer.Logger) *fetStream {
|
||||
@@ -38,6 +55,38 @@ func (s *fetStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyz
|
||||
if len(data) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if rev {
|
||||
// We classify what the client sends first. If the server speaks before
|
||||
// the client does, this isn't a handshake we can say anything about.
|
||||
if s.buf.Len() == 0 {
|
||||
return nil, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
s.segs++
|
||||
if s.buf.Len() == 0 && len(data) >= fetMinDataLen {
|
||||
// Enough to decide on, and nothing buffered yet - classify in place.
|
||||
return fetResult(data), true
|
||||
}
|
||||
s.buf.Append(data)
|
||||
if s.buf.Len() < fetMinDataLen && s.segs < fetMaxSegments {
|
||||
return nil, false
|
||||
}
|
||||
u = fetResult(s.buf.Buf)
|
||||
s.buf.Reset()
|
||||
return u, true
|
||||
}
|
||||
|
||||
func (s *fetStream) Close(limited bool) *analyzer.PropUpdate {
|
||||
if s.buf.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
u := fetResult(s.buf.Buf)
|
||||
s.buf.Reset()
|
||||
return u
|
||||
}
|
||||
|
||||
func fetResult(data []byte) *analyzer.PropUpdate {
|
||||
ex1 := averagePopCount(data)
|
||||
ex2 := isFirstSixPrintable(data)
|
||||
ex3 := printablePercentage(data)
|
||||
@@ -54,11 +103,7 @@ func (s *fetStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyz
|
||||
"ex5": ex5,
|
||||
"yes": !exempt,
|
||||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
func (s *fetStream) Close(limited bool) *analyzer.PropUpdate {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func popCount(b byte) int {
|
||||
|
||||
Reference in New Issue
Block a user