First Commit
Some checks failed
Quality check / Static analysis (push) Has been cancelled
Quality check / Tests (push) Has been cancelled

This commit is contained in:
Hayzam Sherif
2026-02-11 06:27:36 +05:30
commit 94e1e26cc3
56 changed files with 8530 additions and 0 deletions

162
analyzer/tcp/fet.go Normal file
View File

@@ -0,0 +1,162 @@
package tcp
import "git.difuse.io/Difuse/Mellaris/analyzer"
var _ analyzer.TCPAnalyzer = (*FETAnalyzer)(nil)
// FETAnalyzer stands for "Fully Encrypted Traffic" analyzer.
// It implements an algorithm to detect fully encrypted proxy protocols
// such as Shadowsocks, mentioned in the following paper:
// https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf
type FETAnalyzer struct{}
func (a *FETAnalyzer) Name() string {
return "fet"
}
func (a *FETAnalyzer) Limit() int {
// We only really look at the first packet
return 8192
}
func (a *FETAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newFETStream(logger)
}
type fetStream struct {
logger analyzer.Logger
}
func newFETStream(logger analyzer.Logger) *fetStream {
return &fetStream{logger: logger}
}
func (s *fetStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
ex1 := averagePopCount(data)
ex2 := isFirstSixPrintable(data)
ex3 := printablePercentage(data)
ex4 := contiguousPrintable(data)
ex5 := isTLSorHTTP(data)
exempt := (ex1 <= 3.4 || ex1 >= 4.6) || ex2 || ex3 > 0.5 || ex4 > 20 || ex5
return &analyzer.PropUpdate{
Type: analyzer.PropUpdateReplace,
M: analyzer.PropMap{
"ex1": ex1,
"ex2": ex2,
"ex3": ex3,
"ex4": ex4,
"ex5": ex5,
"yes": !exempt,
},
}, true
}
func (s *fetStream) Close(limited bool) *analyzer.PropUpdate {
return nil
}
func popCount(b byte) int {
count := 0
for b != 0 {
count += int(b & 1)
b >>= 1
}
return count
}
// averagePopCount returns the average popcount of the given bytes.
// This is the "Ex1" metric in the paper.
func averagePopCount(bytes []byte) float32 {
if len(bytes) == 0 {
return 0
}
total := 0
for _, b := range bytes {
total += popCount(b)
}
return float32(total) / float32(len(bytes))
}
// isFirstSixPrintable returns true if the first six bytes are printable ASCII.
// This is the "Ex2" metric in the paper.
func isFirstSixPrintable(bytes []byte) bool {
if len(bytes) < 6 {
return false
}
for i := range bytes[:6] {
if !isPrintable(bytes[i]) {
return false
}
}
return true
}
// printablePercentage returns the percentage of printable ASCII bytes.
// This is the "Ex3" metric in the paper.
func printablePercentage(bytes []byte) float32 {
if len(bytes) == 0 {
return 0
}
count := 0
for i := range bytes {
if isPrintable(bytes[i]) {
count++
}
}
return float32(count) / float32(len(bytes))
}
// contiguousPrintable returns the length of the longest contiguous sequence of
// printable ASCII bytes.
// This is the "Ex4" metric in the paper.
func contiguousPrintable(bytes []byte) int {
if len(bytes) == 0 {
return 0
}
maxCount := 0
current := 0
for i := range bytes {
if isPrintable(bytes[i]) {
current++
} else {
if current > maxCount {
maxCount = current
}
current = 0
}
}
if current > maxCount {
maxCount = current
}
return maxCount
}
// isTLSorHTTP returns true if the given bytes look like TLS or HTTP.
// This is the "Ex5" metric in the paper.
func isTLSorHTTP(bytes []byte) bool {
if len(bytes) < 3 {
return false
}
// "We observe that the GFW exempts any connection whose first
// three bytes match the following regular expression:
// [\x16-\x17]\x03[\x00-\x09]" - from the paper in Section 4.3
if bytes[0] >= 0x16 && bytes[0] <= 0x17 &&
bytes[1] == 0x03 && bytes[2] <= 0x09 {
return true
}
// HTTP request
str := string(bytes[:3])
return str == "GET" || str == "HEA" || str == "POS" ||
str == "PUT" || str == "DEL" || str == "CON" ||
str == "OPT" || str == "TRA" || str == "PAT"
}
func isPrintable(b byte) bool {
return b >= 0x20 && b <= 0x7e
}

193
analyzer/tcp/http.go Normal file
View File

@@ -0,0 +1,193 @@
package tcp
import (
"bytes"
"strconv"
"strings"
"git.difuse.io/Difuse/Mellaris/analyzer"
"git.difuse.io/Difuse/Mellaris/analyzer/utils"
)
var _ analyzer.TCPAnalyzer = (*HTTPAnalyzer)(nil)
type HTTPAnalyzer struct{}
func (a *HTTPAnalyzer) Name() string {
return "http"
}
func (a *HTTPAnalyzer) Limit() int {
return 8192
}
func (a *HTTPAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newHTTPStream(logger)
}
type httpStream struct {
logger analyzer.Logger
reqBuf *utils.ByteBuffer
reqMap analyzer.PropMap
reqUpdated bool
reqLSM *utils.LinearStateMachine
reqDone bool
respBuf *utils.ByteBuffer
respMap analyzer.PropMap
respUpdated bool
respLSM *utils.LinearStateMachine
respDone bool
}
func newHTTPStream(logger analyzer.Logger) *httpStream {
s := &httpStream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}}
s.reqLSM = utils.NewLinearStateMachine(
s.parseRequestLine,
s.parseRequestHeaders,
)
s.respLSM = utils.NewLinearStateMachine(
s.parseResponseLine,
s.parseResponseHeaders,
)
return s
}
func (s *httpStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) {
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
var update *analyzer.PropUpdate
var cancelled bool
if rev {
s.respBuf.Append(data)
s.respUpdated = false
cancelled, s.respDone = s.respLSM.Run()
if s.respUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"resp": s.respMap},
}
s.respUpdated = false
}
} else {
s.reqBuf.Append(data)
s.reqUpdated = false
cancelled, s.reqDone = s.reqLSM.Run()
if s.reqUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"req": s.reqMap},
}
s.reqUpdated = false
}
}
return update, cancelled || (s.reqDone && s.respDone)
}
func (s *httpStream) parseRequestLine() utils.LSMAction {
// Find the end of the request line
line, ok := s.reqBuf.GetUntil([]byte("\r\n"), true, true)
if !ok {
// No end of line yet, but maybe we just need more data
return utils.LSMActionPause
}
fields := strings.Fields(string(line[:len(line)-2])) // Strip \r\n
if len(fields) != 3 {
// Invalid request line
return utils.LSMActionCancel
}
method := fields[0]
path := fields[1]
version := fields[2]
if !strings.HasPrefix(version, "HTTP/") {
// Invalid version
return utils.LSMActionCancel
}
s.reqMap = analyzer.PropMap{
"method": method,
"path": path,
"version": version,
}
s.reqUpdated = true
return utils.LSMActionNext
}
func (s *httpStream) parseResponseLine() utils.LSMAction {
// Find the end of the response line
line, ok := s.respBuf.GetUntil([]byte("\r\n"), true, true)
if !ok {
// No end of line yet, but maybe we just need more data
return utils.LSMActionPause
}
fields := strings.Fields(string(line[:len(line)-2])) // Strip \r\n
if len(fields) < 2 {
// Invalid response line
return utils.LSMActionCancel
}
version := fields[0]
status, _ := strconv.Atoi(fields[1])
if !strings.HasPrefix(version, "HTTP/") || status == 0 {
// Invalid version
return utils.LSMActionCancel
}
s.respMap = analyzer.PropMap{
"version": version,
"status": status,
}
s.respUpdated = true
return utils.LSMActionNext
}
func (s *httpStream) parseHeaders(buf *utils.ByteBuffer) (utils.LSMAction, analyzer.PropMap) {
// Find the end of headers
headers, ok := buf.GetUntil([]byte("\r\n\r\n"), true, true)
if !ok {
// No end of headers yet, but maybe we just need more data
return utils.LSMActionPause, nil
}
headers = headers[:len(headers)-4] // Strip \r\n\r\n
headerMap := make(analyzer.PropMap)
for _, line := range bytes.Split(headers, []byte("\r\n")) {
fields := bytes.SplitN(line, []byte(":"), 2)
if len(fields) != 2 {
// Invalid header
return utils.LSMActionCancel, nil
}
key := string(bytes.TrimSpace(fields[0]))
value := string(bytes.TrimSpace(fields[1]))
// Normalize header keys to lowercase
headerMap[strings.ToLower(key)] = value
}
return utils.LSMActionNext, headerMap
}
func (s *httpStream) parseRequestHeaders() utils.LSMAction {
action, headerMap := s.parseHeaders(s.reqBuf)
if action == utils.LSMActionNext {
s.reqMap["headers"] = headerMap
s.reqUpdated = true
}
return action
}
func (s *httpStream) parseResponseHeaders() utils.LSMAction {
action, headerMap := s.parseHeaders(s.respBuf)
if action == utils.LSMActionNext {
s.respMap["headers"] = headerMap
s.respUpdated = true
}
return action
}
func (s *httpStream) Close(limited bool) *analyzer.PropUpdate {
s.reqBuf.Reset()
s.respBuf.Reset()
s.reqMap = nil
s.respMap = nil
return nil
}

64
analyzer/tcp/http_test.go Normal file
View File

@@ -0,0 +1,64 @@
package tcp
import (
"reflect"
"strings"
"testing"
"git.difuse.io/Difuse/Mellaris/analyzer"
)
func TestHTTPParsing_Request(t *testing.T) {
testCases := map[string]analyzer.PropMap{
"GET / HTTP/1.1\r\n": {
"method": "GET", "path": "/", "version": "HTTP/1.1",
},
"POST /hello?a=1&b=2 HTTP/1.0\r\n": {
"method": "POST", "path": "/hello?a=1&b=2", "version": "HTTP/1.0",
},
"PUT /world HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody": {
"method": "PUT", "path": "/world", "version": "HTTP/1.1", "headers": analyzer.PropMap{"content-length": "4"},
},
"DELETE /goodbye HTTP/2.0\r\n": {
"method": "DELETE", "path": "/goodbye", "version": "HTTP/2.0",
},
}
for tc, want := range testCases {
t.Run(strings.Split(tc, " ")[0], func(t *testing.T) {
tc, want := tc, want
t.Parallel()
u, _ := newHTTPStream(nil).Feed(false, false, false, 0, []byte(tc))
got := u.M.Get("req")
if !reflect.DeepEqual(got, want) {
t.Errorf("\"%s\" parsed = %v, want %v", tc, got, want)
}
})
}
}
func TestHTTPParsing_Response(t *testing.T) {
testCases := map[string]analyzer.PropMap{
"HTTP/1.0 200 OK\r\nContent-Length: 4\r\n\r\nbody": {
"version": "HTTP/1.0", "status": 200,
"headers": analyzer.PropMap{"content-length": "4"},
},
"HTTP/2.0 204 No Content\r\n\r\n": {
"version": "HTTP/2.0", "status": 204,
},
}
for tc, want := range testCases {
t.Run(strings.Split(tc, " ")[0], func(t *testing.T) {
tc, want := tc, want
t.Parallel()
u, _ := newHTTPStream(nil).Feed(true, false, false, 0, []byte(tc))
got := u.M.Get("resp")
if !reflect.DeepEqual(got, want) {
t.Errorf("\"%s\" parsed = %v, want %v", tc, got, want)
}
})
}
}

508
analyzer/tcp/socks.go Normal file
View File

@@ -0,0 +1,508 @@
package tcp
import (
"net"
"git.difuse.io/Difuse/Mellaris/analyzer"
"git.difuse.io/Difuse/Mellaris/analyzer/utils"
)
const (
SocksInvalid = iota
Socks4
Socks4A
Socks5
Socks4Version = 0x04
Socks5Version = 0x05
Socks4ReplyVN = 0x00
Socks4CmdTCPConnect = 0x01
Socks4CmdTCPBind = 0x02
Socks4ReqGranted = 0x5A
Socks4ReqRejectOrFailed = 0x5B
Socks4ReqRejectIdentd = 0x5C
Socks4ReqRejectUser = 0x5D
Socks5CmdTCPConnect = 0x01
Socks5CmdTCPBind = 0x02
Socks5CmdUDPAssociate = 0x03
Socks5AuthNotRequired = 0x00
Socks5AuthPassword = 0x02
Socks5AuthNoMatchingMethod = 0xFF
Socks5AuthSuccess = 0x00
Socks5AuthFailure = 0x01
Socks5AddrTypeIPv4 = 0x01
Socks5AddrTypeDomain = 0x03
Socks5AddrTypeIPv6 = 0x04
)
var _ analyzer.Analyzer = (*SocksAnalyzer)(nil)
type SocksAnalyzer struct{}
func (a *SocksAnalyzer) Name() string {
return "socks"
}
func (a *SocksAnalyzer) Limit() int {
// Socks4 length limit cannot be predicted
return 0
}
func (a *SocksAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newSocksStream(logger)
}
type socksStream struct {
logger analyzer.Logger
reqBuf *utils.ByteBuffer
reqMap analyzer.PropMap
reqUpdated bool
reqLSM *utils.LinearStateMachine
reqDone bool
respBuf *utils.ByteBuffer
respMap analyzer.PropMap
respUpdated bool
respLSM *utils.LinearStateMachine
respDone bool
version int
authReqMethod int
authUsername string
authPassword string
authRespMethod int
}
func newSocksStream(logger analyzer.Logger) *socksStream {
s := &socksStream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}}
s.reqLSM = utils.NewLinearStateMachine(
s.parseSocksReqVersion,
)
s.respLSM = utils.NewLinearStateMachine(
s.parseSocksRespVersion,
)
return s
}
func (s *socksStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) {
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
var update *analyzer.PropUpdate
var cancelled bool
if rev {
s.respBuf.Append(data)
s.respUpdated = false
cancelled, s.respDone = s.respLSM.Run()
if s.respUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"resp": s.respMap},
}
s.respUpdated = false
}
} else {
s.reqBuf.Append(data)
s.reqUpdated = false
cancelled, s.reqDone = s.reqLSM.Run()
if s.reqUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{
"version": s.socksVersion(),
"req": s.reqMap,
},
}
s.reqUpdated = false
}
}
return update, cancelled || (s.reqDone && s.respDone)
}
func (s *socksStream) Close(limited bool) *analyzer.PropUpdate {
s.reqBuf.Reset()
s.respBuf.Reset()
s.reqMap = nil
s.respMap = nil
return nil
}
func (s *socksStream) parseSocksReqVersion() utils.LSMAction {
socksVer, ok := s.reqBuf.GetByte(true)
if !ok {
return utils.LSMActionPause
}
if socksVer != Socks4Version && socksVer != Socks5Version {
return utils.LSMActionCancel
}
s.reqMap = make(analyzer.PropMap)
s.reqUpdated = true
if socksVer == Socks4Version {
s.version = Socks4
s.reqLSM.AppendSteps(
s.parseSocks4ReqIpAndPort,
s.parseSocks4ReqUserId,
s.parseSocks4ReqHostname,
)
} else {
s.version = Socks5
s.reqLSM.AppendSteps(
s.parseSocks5ReqMethod,
s.parseSocks5ReqAuth,
s.parseSocks5ReqConnInfo,
)
}
return utils.LSMActionNext
}
func (s *socksStream) parseSocksRespVersion() utils.LSMAction {
socksVer, ok := s.respBuf.GetByte(true)
if !ok {
return utils.LSMActionPause
}
if (s.version == Socks4 || s.version == Socks4A) && socksVer != Socks4ReplyVN ||
s.version == Socks5 && socksVer != Socks5Version || s.version == SocksInvalid {
return utils.LSMActionCancel
}
if socksVer == Socks4ReplyVN {
s.respLSM.AppendSteps(
s.parseSocks4RespPacket,
)
} else {
s.respLSM.AppendSteps(
s.parseSocks5RespMethod,
s.parseSocks5RespAuth,
s.parseSocks5RespConnInfo,
)
}
return utils.LSMActionNext
}
func (s *socksStream) parseSocks5ReqMethod() utils.LSMAction {
nMethods, ok := s.reqBuf.GetByte(false)
if !ok {
return utils.LSMActionPause
}
methods, ok := s.reqBuf.Get(int(nMethods)+1, true)
if !ok {
return utils.LSMActionPause
}
// For convenience, we only take the first method we can process
s.authReqMethod = Socks5AuthNoMatchingMethod
for _, method := range methods[1:] {
switch method {
case Socks5AuthNotRequired:
s.authReqMethod = Socks5AuthNotRequired
return utils.LSMActionNext
case Socks5AuthPassword:
s.authReqMethod = Socks5AuthPassword
return utils.LSMActionNext
default:
// TODO: more auth method to support
}
}
return utils.LSMActionNext
}
func (s *socksStream) parseSocks5ReqAuth() utils.LSMAction {
switch s.authReqMethod {
case Socks5AuthNotRequired:
s.reqMap["auth"] = analyzer.PropMap{"method": s.authReqMethod}
case Socks5AuthPassword:
meta, ok := s.reqBuf.Get(2, false)
if !ok {
return utils.LSMActionPause
}
if meta[0] != 0x01 {
return utils.LSMActionCancel
}
usernameLen := int(meta[1])
meta, ok = s.reqBuf.Get(usernameLen+3, false)
if !ok {
return utils.LSMActionPause
}
passwordLen := int(meta[usernameLen+2])
meta, ok = s.reqBuf.Get(usernameLen+passwordLen+3, true)
if !ok {
return utils.LSMActionPause
}
s.authUsername = string(meta[2 : usernameLen+2])
s.authPassword = string(meta[usernameLen+3:])
s.reqMap["auth"] = analyzer.PropMap{
"method": s.authReqMethod,
"username": s.authUsername,
"password": s.authPassword,
}
default:
return utils.LSMActionCancel
}
s.reqUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks5ReqConnInfo() utils.LSMAction {
/* preInfo struct
+----+-----+-------+------+-------------+
|VER | CMD | RSV | ATYP | DST.ADDR(1) |
+----+-----+-------+------+-------------+
*/
preInfo, ok := s.reqBuf.Get(5, false)
if !ok {
return utils.LSMActionPause
}
// verify socks version
if preInfo[0] != Socks5Version {
return utils.LSMActionCancel
}
var pktLen int
switch int(preInfo[3]) {
case Socks5AddrTypeIPv4:
pktLen = 10
case Socks5AddrTypeDomain:
domainLen := int(preInfo[4])
pktLen = 7 + domainLen
case Socks5AddrTypeIPv6:
pktLen = 22
default:
return utils.LSMActionCancel
}
pkt, ok := s.reqBuf.Get(pktLen, true)
if !ok {
return utils.LSMActionPause
}
// parse cmd
cmd := int(pkt[1])
if cmd != Socks5CmdTCPConnect && cmd != Socks5CmdTCPBind && cmd != Socks5CmdUDPAssociate {
return utils.LSMActionCancel
}
s.reqMap["cmd"] = cmd
// parse addr type
addrType := int(pkt[3])
var addr string
switch addrType {
case Socks5AddrTypeIPv4:
addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String()
case Socks5AddrTypeDomain:
addr = string(pkt[5 : 5+pkt[4]])
case Socks5AddrTypeIPv6:
addr = net.IP(pkt[4 : 4+net.IPv6len]).String()
default:
return utils.LSMActionCancel
}
s.reqMap["addr_type"] = addrType
s.reqMap["addr"] = addr
// parse port
port := int(pkt[pktLen-2])<<8 | int(pkt[pktLen-1])
s.reqMap["port"] = port
s.reqUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks5RespMethod() utils.LSMAction {
method, ok := s.respBuf.Get(1, true)
if !ok {
return utils.LSMActionPause
}
s.authRespMethod = int(method[0])
s.respMap = make(analyzer.PropMap)
return utils.LSMActionNext
}
func (s *socksStream) parseSocks5RespAuth() utils.LSMAction {
switch s.authRespMethod {
case Socks5AuthNotRequired:
s.respMap["auth"] = analyzer.PropMap{"method": s.authRespMethod}
case Socks5AuthPassword:
authResp, ok := s.respBuf.Get(2, true)
if !ok {
return utils.LSMActionPause
}
if authResp[0] != 0x01 {
return utils.LSMActionCancel
}
authStatus := int(authResp[1])
s.respMap["auth"] = analyzer.PropMap{
"method": s.authRespMethod,
"status": authStatus,
}
default:
return utils.LSMActionCancel
}
s.respUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks5RespConnInfo() utils.LSMAction {
/* preInfo struct
+----+-----+-------+------+-------------+
|VER | REP | RSV | ATYP | BND.ADDR(1) |
+----+-----+-------+------+-------------+
*/
preInfo, ok := s.respBuf.Get(5, false)
if !ok {
return utils.LSMActionPause
}
// verify socks version
if preInfo[0] != Socks5Version {
return utils.LSMActionCancel
}
var pktLen int
switch int(preInfo[3]) {
case Socks5AddrTypeIPv4:
pktLen = 10
case Socks5AddrTypeDomain:
domainLen := int(preInfo[4])
pktLen = 7 + domainLen
case Socks5AddrTypeIPv6:
pktLen = 22
default:
return utils.LSMActionCancel
}
pkt, ok := s.respBuf.Get(pktLen, true)
if !ok {
return utils.LSMActionPause
}
// parse rep
rep := int(pkt[1])
s.respMap["rep"] = rep
// parse addr type
addrType := int(pkt[3])
var addr string
switch addrType {
case Socks5AddrTypeIPv4:
addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String()
case Socks5AddrTypeDomain:
addr = string(pkt[5 : 5+pkt[4]])
case Socks5AddrTypeIPv6:
addr = net.IP(pkt[4 : 4+net.IPv6len]).String()
default:
return utils.LSMActionCancel
}
s.respMap["addr_type"] = addrType
s.respMap["addr"] = addr
// parse port
port := int(pkt[pktLen-2])<<8 | int(pkt[pktLen-1])
s.respMap["port"] = port
s.respUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks4ReqIpAndPort() utils.LSMAction {
/* Following field will be parsed in this state:
+-----+----------+--------+
| CMD | DST.PORT | DST.IP |
+-----+----------+--------+
*/
pkt, ok := s.reqBuf.Get(7, true)
if !ok {
return utils.LSMActionPause
}
if pkt[0] != Socks4CmdTCPConnect && pkt[0] != Socks4CmdTCPBind {
return utils.LSMActionCancel
}
dstPort := uint16(pkt[1])<<8 | uint16(pkt[2])
dstIp := net.IPv4(pkt[3], pkt[4], pkt[5], pkt[6]).String()
// Socks4a extension
if pkt[3] == 0 && pkt[4] == 0 && pkt[5] == 0 {
s.version = Socks4A
}
s.reqMap["cmd"] = pkt[0]
s.reqMap["addr"] = dstIp
s.reqMap["addr_type"] = Socks5AddrTypeIPv4
s.reqMap["port"] = dstPort
s.reqUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks4ReqUserId() utils.LSMAction {
userIdSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true)
if !ok {
return utils.LSMActionPause
}
userId := string(userIdSlice[:len(userIdSlice)-1])
s.reqMap["auth"] = analyzer.PropMap{
"user_id": userId,
}
s.reqUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks4ReqHostname() utils.LSMAction {
// Only Socks4a support hostname
if s.version != Socks4A {
return utils.LSMActionNext
}
hostnameSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true)
if !ok {
return utils.LSMActionPause
}
hostname := string(hostnameSlice[:len(hostnameSlice)-1])
s.reqMap["addr"] = hostname
s.reqMap["addr_type"] = Socks5AddrTypeDomain
s.reqUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) parseSocks4RespPacket() utils.LSMAction {
pkt, ok := s.respBuf.Get(7, true)
if !ok {
return utils.LSMActionPause
}
if pkt[0] != Socks4ReqGranted &&
pkt[0] != Socks4ReqRejectOrFailed &&
pkt[0] != Socks4ReqRejectIdentd &&
pkt[0] != Socks4ReqRejectUser {
return utils.LSMActionCancel
}
dstPort := uint16(pkt[1])<<8 | uint16(pkt[2])
dstIp := net.IPv4(pkt[3], pkt[4], pkt[5], pkt[6]).String()
s.respMap = analyzer.PropMap{
"rep": pkt[0],
"addr": dstIp,
"addr_type": Socks5AddrTypeIPv4,
"port": dstPort,
}
s.respUpdated = true
return utils.LSMActionNext
}
func (s *socksStream) socksVersion() int {
switch s.version {
case Socks4, Socks4A:
return Socks4Version
case Socks5:
return Socks5Version
default:
return SocksInvalid
}
}

147
analyzer/tcp/ssh.go Normal file
View File

@@ -0,0 +1,147 @@
package tcp
import (
"strings"
"git.difuse.io/Difuse/Mellaris/analyzer"
"git.difuse.io/Difuse/Mellaris/analyzer/utils"
)
var _ analyzer.TCPAnalyzer = (*SSHAnalyzer)(nil)
type SSHAnalyzer struct{}
func (a *SSHAnalyzer) Name() string {
return "ssh"
}
func (a *SSHAnalyzer) Limit() int {
return 1024
}
func (a *SSHAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newSSHStream(logger)
}
type sshStream struct {
logger analyzer.Logger
clientBuf *utils.ByteBuffer
clientMap analyzer.PropMap
clientUpdated bool
clientLSM *utils.LinearStateMachine
clientDone bool
serverBuf *utils.ByteBuffer
serverMap analyzer.PropMap
serverUpdated bool
serverLSM *utils.LinearStateMachine
serverDone bool
}
func newSSHStream(logger analyzer.Logger) *sshStream {
s := &sshStream{logger: logger, clientBuf: &utils.ByteBuffer{}, serverBuf: &utils.ByteBuffer{}}
s.clientLSM = utils.NewLinearStateMachine(
s.parseClientExchangeLine,
)
s.serverLSM = utils.NewLinearStateMachine(
s.parseServerExchangeLine,
)
return s
}
func (s *sshStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
var update *analyzer.PropUpdate
var cancelled bool
if rev {
s.serverBuf.Append(data)
s.serverUpdated = false
cancelled, s.serverDone = s.serverLSM.Run()
if s.serverUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"server": s.serverMap},
}
s.serverUpdated = false
}
} else {
s.clientBuf.Append(data)
s.clientUpdated = false
cancelled, s.clientDone = s.clientLSM.Run()
if s.clientUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"client": s.clientMap},
}
s.clientUpdated = false
}
}
return update, cancelled || (s.clientDone && s.serverDone)
}
// parseExchangeLine parses the SSH Protocol Version Exchange string.
// See RFC 4253, section 4.2.
// "SSH-protoversion-softwareversion SP comments CR LF"
// The "comments" part (along with the SP) is optional.
func (s *sshStream) parseExchangeLine(buf *utils.ByteBuffer) (utils.LSMAction, analyzer.PropMap) {
// Find the end of the line
line, ok := buf.GetUntil([]byte("\r\n"), true, true)
if !ok {
// No end of line yet, but maybe we just need more data
return utils.LSMActionPause, nil
}
if !strings.HasPrefix(string(line), "SSH-") {
// Not SSH
return utils.LSMActionCancel, nil
}
fields := strings.Fields(string(line[:len(line)-2])) // Strip \r\n
if len(fields) < 1 || len(fields) > 2 {
// Invalid line
return utils.LSMActionCancel, nil
}
sshFields := strings.SplitN(fields[0], "-", 3)
if len(sshFields) != 3 {
// Invalid SSH version format
return utils.LSMActionCancel, nil
}
sMap := analyzer.PropMap{
"protocol": sshFields[1],
"software": sshFields[2],
}
if len(fields) == 2 {
sMap["comments"] = fields[1]
}
return utils.LSMActionNext, sMap
}
func (s *sshStream) parseClientExchangeLine() utils.LSMAction {
action, sMap := s.parseExchangeLine(s.clientBuf)
if action == utils.LSMActionNext {
s.clientMap = sMap
s.clientUpdated = true
}
return action
}
func (s *sshStream) parseServerExchangeLine() utils.LSMAction {
action, sMap := s.parseExchangeLine(s.serverBuf)
if action == utils.LSMActionNext {
s.serverMap = sMap
s.serverUpdated = true
}
return action
}
func (s *sshStream) Close(limited bool) *analyzer.PropUpdate {
s.clientBuf.Reset()
s.serverBuf.Reset()
s.clientMap = nil
s.serverMap = nil
return nil
}

226
analyzer/tcp/tls.go Normal file
View File

@@ -0,0 +1,226 @@
package tcp
import (
"git.difuse.io/Difuse/Mellaris/analyzer"
"git.difuse.io/Difuse/Mellaris/analyzer/internal"
"git.difuse.io/Difuse/Mellaris/analyzer/utils"
)
var _ analyzer.TCPAnalyzer = (*TLSAnalyzer)(nil)
type TLSAnalyzer struct{}
func (a *TLSAnalyzer) Name() string {
return "tls"
}
func (a *TLSAnalyzer) Limit() int {
return 8192
}
func (a *TLSAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newTLSStream(logger)
}
type tlsStream struct {
logger analyzer.Logger
reqBuf *utils.ByteBuffer
reqMap analyzer.PropMap
reqUpdated bool
reqLSM *utils.LinearStateMachine
reqDone bool
respBuf *utils.ByteBuffer
respMap analyzer.PropMap
respUpdated bool
respLSM *utils.LinearStateMachine
respDone bool
clientHelloLen int
serverHelloLen int
}
func newTLSStream(logger analyzer.Logger) *tlsStream {
s := &tlsStream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}}
s.reqLSM = utils.NewLinearStateMachine(
s.tlsClientHelloPreprocess,
s.parseClientHelloData,
)
s.respLSM = utils.NewLinearStateMachine(
s.tlsServerHelloPreprocess,
s.parseServerHelloData,
)
return s
}
func (s *tlsStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
var update *analyzer.PropUpdate
var cancelled bool
if rev {
s.respBuf.Append(data)
s.respUpdated = false
cancelled, s.respDone = s.respLSM.Run()
if s.respUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"resp": s.respMap},
}
s.respUpdated = false
}
} else {
s.reqBuf.Append(data)
s.reqUpdated = false
cancelled, s.reqDone = s.reqLSM.Run()
if s.reqUpdated {
update = &analyzer.PropUpdate{
Type: analyzer.PropUpdateMerge,
M: analyzer.PropMap{"req": s.reqMap},
}
s.reqUpdated = false
}
}
return update, cancelled || (s.reqDone && s.respDone)
}
// tlsClientHelloPreprocess validates ClientHello message.
//
// During validation, message header and first handshake header may be removed
// from `s.reqBuf`.
func (s *tlsStream) tlsClientHelloPreprocess() utils.LSMAction {
// headers size: content type (1 byte) + legacy protocol version (2 bytes) +
// + content length (2 bytes) + message type (1 byte) +
// + handshake length (3 bytes)
const headersSize = 9
// minimal data size: protocol version (2 bytes) + random (32 bytes) +
// + session ID (1 byte) + cipher suites (4 bytes) +
// + compression methods (2 bytes) + no extensions
const minDataSize = 41
header, ok := s.reqBuf.Get(headersSize, true)
if !ok {
// not a full header yet
return utils.LSMActionPause
}
if header[0] != internal.RecordTypeHandshake || header[5] != internal.TypeClientHello {
return utils.LSMActionCancel
}
s.clientHelloLen = int(header[6])<<16 | int(header[7])<<8 | int(header[8])
if s.clientHelloLen < minDataSize {
return utils.LSMActionCancel
}
// TODO: something is missing. See:
// const messageHeaderSize = 4
// fullMessageLen := int(header[3])<<8 | int(header[4])
// msgNo := fullMessageLen / int(messageHeaderSize+s.serverHelloLen)
// if msgNo != 1 {
// // what here?
// }
// if messageNo != int(messageNo) {
// // what here?
// }
return utils.LSMActionNext
}
// tlsServerHelloPreprocess validates ServerHello message.
//
// During validation, message header and first handshake header may be removed
// from `s.reqBuf`.
func (s *tlsStream) tlsServerHelloPreprocess() utils.LSMAction {
// header size: content type (1 byte) + legacy protocol version (2 byte) +
// + content length (2 byte) + message type (1 byte) +
// + handshake length (3 byte)
const headersSize = 9
// minimal data size: server version (2 byte) + random (32 byte) +
// + session ID (>=1 byte) + cipher suite (2 byte) +
// + compression method (1 byte) + no extensions
const minDataSize = 38
header, ok := s.respBuf.Get(headersSize, true)
if !ok {
// not a full header yet
return utils.LSMActionPause
}
if header[0] != internal.RecordTypeHandshake || header[5] != internal.TypeServerHello {
return utils.LSMActionCancel
}
s.serverHelloLen = int(header[6])<<16 | int(header[7])<<8 | int(header[8])
if s.serverHelloLen < minDataSize {
return utils.LSMActionCancel
}
// TODO: something is missing. See example:
// const messageHeaderSize = 4
// fullMessageLen := int(header[3])<<8 | int(header[4])
// msgNo := fullMessageLen / int(messageHeaderSize+s.serverHelloLen)
// if msgNo != 1 {
// // what here?
// }
// if messageNo != int(messageNo) {
// // what here?
// }
return utils.LSMActionNext
}
// parseClientHelloData converts valid ClientHello message data (without
// headers) into `analyzer.PropMap`.
//
// Parsing error may leave `s.reqBuf` in an unusable state.
func (s *tlsStream) parseClientHelloData() utils.LSMAction {
chBuf, ok := s.reqBuf.GetSubBuffer(s.clientHelloLen, true)
if !ok {
// Not a full client hello yet
return utils.LSMActionPause
}
m := internal.ParseTLSClientHelloMsgData(chBuf)
if m == nil {
return utils.LSMActionCancel
} else {
s.reqUpdated = true
s.reqMap = m
return utils.LSMActionNext
}
}
// parseServerHelloData converts valid ServerHello message data (without
// headers) into `analyzer.PropMap`.
//
// Parsing error may leave `s.respBuf` in an unusable state.
func (s *tlsStream) parseServerHelloData() utils.LSMAction {
shBuf, ok := s.respBuf.GetSubBuffer(s.serverHelloLen, true)
if !ok {
// Not a full server hello yet
return utils.LSMActionPause
}
m := internal.ParseTLSServerHelloMsgData(shBuf)
if m == nil {
return utils.LSMActionCancel
} else {
s.respUpdated = true
s.respMap = m
return utils.LSMActionNext
}
}
func (s *tlsStream) Close(limited bool) *analyzer.PropUpdate {
s.reqBuf.Reset()
s.respBuf.Reset()
s.reqMap = nil
s.respMap = nil
return nil
}

69
analyzer/tcp/tls_test.go Normal file
View File

@@ -0,0 +1,69 @@
package tcp
import (
"reflect"
"testing"
"git.difuse.io/Difuse/Mellaris/analyzer"
)
func TestTlsStreamParsing_ClientHello(t *testing.T) {
// example packet taken from <https://tls12.xargs.org/#client-hello/annotated>
clientHello := []byte{
0x16, 0x03, 0x01, 0x00, 0xa5, 0x01, 0x00, 0x00, 0xa1, 0x03, 0x03, 0x00,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x00, 0x20, 0xcc, 0xa8,
0xcc, 0xa9, 0xc0, 0x2f, 0xc0, 0x30, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x13,
0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f,
0x00, 0x35, 0xc0, 0x12, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x58, 0x00, 0x00,
0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70,
0x6c, 0x65, 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 0x2e, 0x6e,
0x65, 0x74, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00,
0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00,
0x10, 0x04, 0x01, 0x04, 0x03, 0x05, 0x01, 0x05, 0x03, 0x06, 0x01, 0x06,
0x03, 0x02, 0x01, 0x02, 0x03, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x12,
0x00, 0x00,
}
want := analyzer.PropMap{
"ciphers": []uint16{52392, 52393, 49199, 49200, 49195, 49196, 49171, 49161, 49172, 49162, 156, 157, 47, 53, 49170, 10},
"compression": []uint8{0},
"random": []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
"session": []uint8{},
"sni": "example.ulfheim.net",
"version": uint16(771),
}
s := newTLSStream(nil)
u, _ := s.Feed(false, false, false, 0, clientHello)
got := u.M.Get("req")
if !reflect.DeepEqual(got, want) {
t.Errorf("%d B parsed = %v, want %v", len(clientHello), got, want)
}
}
func TestTlsStreamParsing_ServerHello(t *testing.T) {
// example packet taken from <https://tls12.xargs.org/#server-hello/annotated>
serverHello := []byte{
0x16, 0x03, 0x03, 0x00, 0x31, 0x02, 0x00, 0x00, 0x2d, 0x03, 0x03, 0x70,
0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x00, 0xc0, 0x13, 0x00, 0x00,
0x05, 0xff, 0x01, 0x00, 0x01, 0x00,
}
want := analyzer.PropMap{
"cipher": uint16(49171),
"compression": uint8(0),
"random": []uint8{112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143},
"session": []uint8{},
"version": uint16(771),
}
s := newTLSStream(nil)
u, _ := s.Feed(true, false, false, 0, serverHello)
got := u.M.Get("resp")
if !reflect.DeepEqual(got, want) {
t.Errorf("%d B parsed = %v, want %v", len(serverHello), got, want)
}
}

517
analyzer/tcp/trojan.go Normal file
View File

@@ -0,0 +1,517 @@
package tcp
import (
"bytes"
"git.difuse.io/Difuse/Mellaris/analyzer"
)
var _ analyzer.TCPAnalyzer = (*TrojanAnalyzer)(nil)
// CCS stands for "Change Cipher Spec"
var ccsPattern = []byte{20, 3, 3, 0, 1, 1}
// TrojanAnalyzer uses length-based heuristics to detect Trojan traffic based on
// its "TLS-in-TLS" nature. The heuristics are trained using a decision tree with
// about 20k Trojan samples and 30k non-Trojan samples. The tree is then converted
// to code using a custom tool and inlined here (isTrojanSeq function).
// Accuracy: 1% false positive rate, 10% false negative rate.
// We do NOT recommend directly blocking all positive connections, as this may
// break legitimate TLS connections.
type TrojanAnalyzer struct{}
func (a *TrojanAnalyzer) Name() string {
return "trojan"
}
func (a *TrojanAnalyzer) Limit() int {
return 512000
}
func (a *TrojanAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newTrojanStream(logger)
}
type trojanStream struct {
logger analyzer.Logger
first bool
count bool
rev bool
seq [4]int
seqIndex int
}
func newTrojanStream(logger analyzer.Logger) *trojanStream {
return &trojanStream{logger: logger}
}
func (s *trojanStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
if s.first {
s.first = false
// Stop if it's not a valid TLS connection
if !(!rev && len(data) >= 3 && data[0] >= 0x16 && data[0] <= 0x17 &&
data[1] == 0x03 && data[2] <= 0x09) {
return nil, true
}
}
if !rev && !s.count && len(data) >= 6 && bytes.Equal(data[:6], ccsPattern) {
// Client Change Cipher Spec encountered, start counting
s.count = true
}
if s.count {
if rev == s.rev {
// Same direction as last time, just update the number
s.seq[s.seqIndex] += len(data)
} else {
// Different direction, bump the index
s.seqIndex += 1
if s.seqIndex == 4 {
return &analyzer.PropUpdate{
Type: analyzer.PropUpdateReplace,
M: analyzer.PropMap{
"seq": s.seq,
"yes": isTrojanSeq(s.seq),
},
}, true
}
s.seq[s.seqIndex] += len(data)
s.rev = rev
}
}
return nil, false
}
func (s *trojanStream) Close(limited bool) *analyzer.PropUpdate {
return nil
}
func isTrojanSeq(seq [4]int) bool {
length1 := seq[0]
length2 := seq[1]
length3 := seq[2]
length4 := seq[3]
if length2 <= 2431 {
if length2 <= 157 {
if length1 <= 156 {
if length3 <= 108 {
return false
} else {
return false
}
} else {
if length1 <= 892 {
if length3 <= 40 {
return false
} else {
if length3 <= 788 {
if length4 <= 185 {
if length1 <= 411 {
return true
} else {
return false
}
} else {
if length2 <= 112 {
return false
} else {
return true
}
}
} else {
if length3 <= 1346 {
if length1 <= 418 {
return false
} else {
return true
}
} else {
return false
}
}
}
} else {
if length2 <= 120 {
if length2 <= 63 {
return false
} else {
if length4 <= 653 {
return false
} else {
return false
}
}
} else {
return false
}
}
}
} else {
if length1 <= 206 {
if length1 <= 185 {
if length1 <= 171 {
return false
} else {
if length4 <= 211 {
return false
} else {
return false
}
}
} else {
if length2 <= 251 {
return true
} else {
return false
}
}
} else {
if length2 <= 286 {
if length1 <= 1123 {
if length3 <= 70 {
return false
} else {
if length1 <= 659 {
if length3 <= 370 {
return true
} else {
return false
}
} else {
if length4 <= 272 {
return false
} else {
return true
}
}
}
} else {
if length4 <= 537 {
if length2 <= 276 {
if length3 <= 1877 {
return false
} else {
return false
}
} else {
return false
}
} else {
if length1 <= 1466 {
if length1 <= 1435 {
return false
} else {
return true
}
} else {
if length2 <= 193 {
return false
} else {
return false
}
}
}
}
} else {
if length1 <= 284 {
if length1 <= 277 {
if length2 <= 726 {
return false
} else {
if length2 <= 768 {
return true
} else {
return false
}
}
} else {
if length2 <= 782 {
if length4 <= 783 {
return true
} else {
return false
}
} else {
return false
}
}
} else {
if length2 <= 492 {
if length2 <= 396 {
if length2 <= 322 {
return false
} else {
return false
}
} else {
if length4 <= 971 {
return false
} else {
return true
}
}
} else {
if length2 <= 2128 {
if length2 <= 1418 {
return false
} else {
return false
}
} else {
if length3 <= 103 {
return false
} else {
return false
}
}
}
}
}
}
}
} else {
if length2 <= 6232 {
if length3 <= 85 {
if length2 <= 3599 {
return false
} else {
if length1 <= 613 {
return false
} else {
return false
}
}
} else {
if length3 <= 220 {
if length4 <= 1173 {
if length1 <= 874 {
if length4 <= 337 {
if length4 <= 68 {
return true
} else {
return true
}
} else {
if length1 <= 667 {
return true
} else {
return true
}
}
} else {
if length3 <= 108 {
if length1 <= 1930 {
return true
} else {
return true
}
} else {
if length2 <= 5383 {
return false
} else {
return true
}
}
}
} else {
return false
}
} else {
if length1 <= 664 {
if length3 <= 411 {
if length3 <= 383 {
if length4 <= 346 {
return true
} else {
return false
}
} else {
if length1 <= 445 {
return true
} else {
return false
}
}
} else {
if length2 <= 3708 {
if length4 <= 307 {
return true
} else {
return false
}
} else {
if length2 <= 4656 {
return false
} else {
return false
}
}
}
} else {
if length1 <= 1055 {
if length3 <= 580 {
if length1 <= 724 {
return true
} else {
return false
}
} else {
if length1 <= 678 {
return false
} else {
return true
}
}
} else {
if length2 <= 5352 {
if length3 <= 1586 {
return false
} else {
return false
}
} else {
if length4 <= 2173 {
return true
} else {
return false
}
}
}
}
}
}
} else {
if length2 <= 9408 {
if length1 <= 670 {
if length4 <= 76 {
if length3 <= 175 {
return true
} else {
return true
}
} else {
if length2 <= 9072 {
if length3 <= 314 {
if length3 <= 179 {
return false
} else {
return false
}
} else {
if length4 <= 708 {
return false
} else {
return false
}
}
} else {
return true
}
}
} else {
if length1 <= 795 {
if length2 <= 6334 {
if length2 <= 6288 {
return true
} else {
return false
}
} else {
if length4 <= 6404 {
if length2 <= 8194 {
return true
} else {
return true
}
} else {
if length2 <= 8924 {
return false
} else {
return true
}
}
}
} else {
if length3 <= 732 {
if length1 <= 1397 {
if length3 <= 179 {
return false
} else {
return false
}
} else {
if length1 <= 1976 {
return false
} else {
return false
}
}
} else {
if length1 <= 2840 {
if length1 <= 2591 {
return false
} else {
return true
}
} else {
return false
}
}
}
}
} else {
if length4 <= 30 {
return false
} else {
if length2 <= 13314 {
if length4 <= 1786 {
if length2 <= 13018 {
if length4 <= 869 {
return false
} else {
return false
}
} else {
return true
}
} else {
if length3 <= 775 {
return false
} else {
return false
}
}
} else {
if length4 <= 73 {
return false
} else {
if length3 <= 640 {
if length3 <= 237 {
return false
} else {
return false
}
} else {
if length2 <= 43804 {
return false
} else {
return false
}
}
}
}
}
}
}
}
}