diff --git a/go-gost/config.go b/go-gost/config.go index 1fa6ffc..9696351 100644 --- a/go-gost/config.go +++ b/go-gost/config.go @@ -8,8 +8,11 @@ import ( // Config 配置结构体 type Config struct { - Addr string `json:"addr"` // WebSocket地址 - Secret string `json:"secret"` // 密钥 + Addr string `json:"addr"` + Secret string `json:"secret"` + Http int `json:"http"` + Tls int `json:"tls"` + Socks int `json:"socks"` } // LoadConfig 加载配置文件 diff --git a/go-gost/gfw/analyzer/interface.go b/go-gost/gfw/analyzer/interface.go deleted file mode 100644 index 80ad418..0000000 --- a/go-gost/gfw/analyzer/interface.go +++ /dev/null @@ -1,131 +0,0 @@ -package analyzer - -import ( - "net" - "strings" -) - -type Analyzer interface { - // Name returns the name of the analyzer. - Name() string - // Limit returns the byte limit for this analyzer. - // For example, an analyzer can return 1000 to indicate that it only ever needs - // the first 1000 bytes of a stream to do its job. If the stream is still not - // done after 1000 bytes, the engine will stop feeding it data and close it. - // An analyzer can return 0 or a negative number to indicate that it does not - // have a hard limit. - // Note: for UDP streams, the engine always feeds entire packets, even if - // the packet is larger than the remaining quota or the limit itself. - Limit() int -} - -type Logger interface { - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Errorf(format string, args ...interface{}) -} - -type TCPAnalyzer interface { - Analyzer - // NewTCP returns a new TCPStream. - NewTCP(TCPInfo, Logger) TCPStream -} - -type TCPInfo struct { - // SrcIP is the source IP address. - SrcIP net.IP - // DstIP is the destination IP address. - DstIP net.IP - // SrcPort is the source port. - SrcPort uint16 - // DstPort is the destination port. - DstPort uint16 -} - -type TCPStream interface { - // Feed feeds a chunk of reassembled data to the stream. - // It returns a prop update containing the information extracted from the stream (can be nil), - // and whether the analyzer is "done" with this stream (i.e. no more data should be fed). - Feed(rev, start, end bool, skip int, data []byte) (u *PropUpdate, done bool) - // Close indicates that the stream is closed. - // Either the connection is closed, or the stream has reached its byte limit. - // Like Feed, it optionally returns a prop update. - Close(limited bool) *PropUpdate -} - -type UDPAnalyzer interface { - Analyzer - // NewUDP returns a new UDPStream. - NewUDP(UDPInfo, Logger) UDPStream -} - -type UDPInfo struct { - // SrcIP is the source IP address. - SrcIP net.IP - // DstIP is the destination IP address. - DstIP net.IP - // SrcPort is the source port. - SrcPort uint16 - // DstPort is the destination port. - DstPort uint16 -} - -type UDPStream interface { - // Feed feeds a new packet to the stream. - // It returns a prop update containing the information extracted from the stream (can be nil), - // and whether the analyzer is "done" with this stream (i.e. no more data should be fed). - Feed(rev bool, data []byte) (u *PropUpdate, done bool) - // Close indicates that the stream is closed. - // Either the connection is closed, or the stream has reached its byte limit. - // Like Feed, it optionally returns a prop update. - Close(limited bool) *PropUpdate -} - -type ( - PropMap map[string]interface{} - CombinedPropMap map[string]PropMap -) - -// Get returns the value of the property with the given key. -// The key can be a nested key, e.g. "foo.bar.baz". -// Returns nil if the key does not exist. -func (m PropMap) Get(key string) interface{} { - keys := strings.Split(key, ".") - if len(keys) == 0 { - return nil - } - var current interface{} = m - for _, k := range keys { - currentMap, ok := current.(PropMap) - if !ok { - return nil - } - current = currentMap[k] - } - return current -} - -// Get returns the value of the property with the given analyzer & key. -// The key can be a nested key, e.g. "foo.bar.baz". -// Returns nil if the key does not exist. -func (cm CombinedPropMap) Get(an string, key string) interface{} { - m, ok := cm[an] - if !ok { - return nil - } - return m.Get(key) -} - -type PropUpdateType int - -const ( - PropUpdateNone PropUpdateType = iota - PropUpdateMerge - PropUpdateReplace - PropUpdateDelete -) - -type PropUpdate struct { - Type PropUpdateType - M PropMap -} diff --git a/go-gost/gfw/analyzer/internal/tls.go b/go-gost/gfw/analyzer/internal/tls.go deleted file mode 100644 index c25605f..0000000 --- a/go-gost/gfw/analyzer/internal/tls.go +++ /dev/null @@ -1,224 +0,0 @@ -package internal - -import ( - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/utils" -) - -// TLS record types. -const ( - RecordTypeHandshake = 0x16 -) - -// TLS handshake message types. -const ( - TypeClientHello = 0x01 - TypeServerHello = 0x02 -) - -// TLS extension numbers. -const ( - extServerName = 0x0000 - extALPN = 0x0010 - extSupportedVersions = 0x002b - extEncryptedClientHello = 0xfe0d -) - -func ParseTLSClientHelloMsgData(chBuf *utils.ByteBuffer) analyzer.PropMap { - var ok bool - m := make(analyzer.PropMap) - // Version, random & session ID length combined are within 35 bytes, - // so no need for bounds checking - m["version"], _ = chBuf.GetUint16(false, true) - m["random"], _ = chBuf.Get(32, true) - sessionIDLen, _ := chBuf.GetByte(true) - m["session"], ok = chBuf.Get(int(sessionIDLen), true) - if !ok { - // Not enough data for session ID - return nil - } - cipherSuitesLen, ok := chBuf.GetUint16(false, true) - if !ok { - // Not enough data for cipher suites length - return nil - } - if cipherSuitesLen%2 != 0 { - // Cipher suites are 2 bytes each, so must be even - return nil - } - ciphers := make([]uint16, cipherSuitesLen/2) - for i := range ciphers { - ciphers[i], ok = chBuf.GetUint16(false, true) - if !ok { - return nil - } - } - m["ciphers"] = ciphers - compressionMethodsLen, ok := chBuf.GetByte(true) - if !ok { - // Not enough data for compression methods length - return nil - } - // Compression methods are 1 byte each, we just put a byte slice here - m["compression"], ok = chBuf.Get(int(compressionMethodsLen), true) - if !ok { - // Not enough data for compression methods - return nil - } - extsLen, ok := chBuf.GetUint16(false, true) - if !ok { - // No extensions, I guess it's possible? - return m - } - extBuf, ok := chBuf.GetSubBuffer(int(extsLen), true) - if !ok { - // Not enough data for extensions - return nil - } - for extBuf.Len() > 0 { - extType, ok := extBuf.GetUint16(false, true) - if !ok { - // Not enough data for extension type - return nil - } - extLen, ok := extBuf.GetUint16(false, true) - if !ok { - // Not enough data for extension length - return nil - } - extDataBuf, ok := extBuf.GetSubBuffer(int(extLen), true) - if !ok || !parseTLSExtensions(extType, extDataBuf, m) { - // Not enough data for extension data, or invalid extension - return nil - } - } - return m -} - -func ParseTLSServerHelloMsgData(shBuf *utils.ByteBuffer) analyzer.PropMap { - var ok bool - m := make(analyzer.PropMap) - // Version, random & session ID length combined are within 35 bytes, - // so no need for bounds checking - m["version"], _ = shBuf.GetUint16(false, true) - m["random"], _ = shBuf.Get(32, true) - sessionIDLen, _ := shBuf.GetByte(true) - m["session"], ok = shBuf.Get(int(sessionIDLen), true) - if !ok { - // Not enough data for session ID - return nil - } - cipherSuite, ok := shBuf.GetUint16(false, true) - if !ok { - // Not enough data for cipher suite - return nil - } - m["cipher"] = cipherSuite - compressionMethod, ok := shBuf.GetByte(true) - if !ok { - // Not enough data for compression method - return nil - } - m["compression"] = compressionMethod - extsLen, ok := shBuf.GetUint16(false, true) - if !ok { - // No extensions, I guess it's possible? - return m - } - extBuf, ok := shBuf.GetSubBuffer(int(extsLen), true) - if !ok { - // Not enough data for extensions - return nil - } - for extBuf.Len() > 0 { - extType, ok := extBuf.GetUint16(false, true) - if !ok { - // Not enough data for extension type - return nil - } - extLen, ok := extBuf.GetUint16(false, true) - if !ok { - // Not enough data for extension length - return nil - } - extDataBuf, ok := extBuf.GetSubBuffer(int(extLen), true) - if !ok || !parseTLSExtensions(extType, extDataBuf, m) { - // Not enough data for extension data, or invalid extension - return nil - } - } - return m -} - -func parseTLSExtensions(extType uint16, extDataBuf *utils.ByteBuffer, m analyzer.PropMap) bool { - switch extType { - case extServerName: - ok := extDataBuf.Skip(2) // Ignore list length, we only care about the first entry for now - if !ok { - // Not enough data for list length - return false - } - sniType, ok := extDataBuf.GetByte(true) - if !ok || sniType != 0 { - // Not enough data for SNI type, or not hostname - return false - } - sniLen, ok := extDataBuf.GetUint16(false, true) - if !ok { - // Not enough data for SNI length - return false - } - m["sni"], ok = extDataBuf.GetString(int(sniLen), true) - if !ok { - // Not enough data for SNI - return false - } - case extALPN: - ok := extDataBuf.Skip(2) // Ignore list length, as we read until the end - if !ok { - // Not enough data for list length - return false - } - var alpnList []string - for extDataBuf.Len() > 0 { - alpnLen, ok := extDataBuf.GetByte(true) - if !ok { - // Not enough data for ALPN length - return false - } - alpn, ok := extDataBuf.GetString(int(alpnLen), true) - if !ok { - // Not enough data for ALPN - return false - } - alpnList = append(alpnList, alpn) - } - m["alpn"] = alpnList - case extSupportedVersions: - if extDataBuf.Len() == 2 { - // Server only selects one version - m["supported_versions"], _ = extDataBuf.GetUint16(false, true) - } else { - // Client sends a list of versions - ok := extDataBuf.Skip(1) // Ignore list length, as we read until the end - if !ok { - // Not enough data for list length - return false - } - var versions []uint16 - for extDataBuf.Len() > 0 { - ver, ok := extDataBuf.GetUint16(false, true) - if !ok { - // Not enough data for version - return false - } - versions = append(versions, ver) - } - m["supported_versions"] = versions - } - case extEncryptedClientHello: - // We can't parse ECH for now, just set a flag - m["ech"] = true - } - return true -} diff --git a/go-gost/gfw/analyzer/tcp/fet.go b/go-gost/gfw/analyzer/tcp/fet.go deleted file mode 100644 index 8b727f5..0000000 --- a/go-gost/gfw/analyzer/tcp/fet.go +++ /dev/null @@ -1,162 +0,0 @@ -package tcp - -import "github.com/apernet/OpenGFW/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 -} diff --git a/go-gost/gfw/analyzer/tcp/http.go b/go-gost/gfw/analyzer/tcp/http.go deleted file mode 100644 index 9d5289f..0000000 --- a/go-gost/gfw/analyzer/tcp/http.go +++ /dev/null @@ -1,193 +0,0 @@ -package tcp - -import ( - "bytes" - "strconv" - "strings" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/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 -} diff --git a/go-gost/gfw/analyzer/tcp/socks.go b/go-gost/gfw/analyzer/tcp/socks.go deleted file mode 100644 index a242069..0000000 --- a/go-gost/gfw/analyzer/tcp/socks.go +++ /dev/null @@ -1,508 +0,0 @@ -package tcp - -import ( - "net" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/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 - } -} diff --git a/go-gost/gfw/analyzer/tcp/ssh.go b/go-gost/gfw/analyzer/tcp/ssh.go deleted file mode 100644 index a636441..0000000 --- a/go-gost/gfw/analyzer/tcp/ssh.go +++ /dev/null @@ -1,147 +0,0 @@ -package tcp - -import ( - "strings" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/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 -} diff --git a/go-gost/gfw/analyzer/tcp/tls.go b/go-gost/gfw/analyzer/tcp/tls.go deleted file mode 100644 index c5f1ea9..0000000 --- a/go-gost/gfw/analyzer/tcp/tls.go +++ /dev/null @@ -1,226 +0,0 @@ -package tcp - -import ( - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/internal" - "github.com/apernet/OpenGFW/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 -} diff --git a/go-gost/gfw/analyzer/tcp/trojan.go b/go-gost/gfw/analyzer/tcp/trojan.go deleted file mode 100644 index a91983b..0000000 --- a/go-gost/gfw/analyzer/tcp/trojan.go +++ /dev/null @@ -1,517 +0,0 @@ -package tcp - -import ( - "bytes" - - "github.com/apernet/OpenGFW/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 - } - } - } - } - } - } - } - } -} diff --git a/go-gost/gfw/analyzer/udp/dns.go b/go-gost/gfw/analyzer/udp/dns.go deleted file mode 100644 index 4008b09..0000000 --- a/go-gost/gfw/analyzer/udp/dns.go +++ /dev/null @@ -1,265 +0,0 @@ -package udp - -import ( - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/utils" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" -) - -const ( - dnsUDPInvalidCountThreshold = 4 -) - -// DNSAnalyzer is for both DNS over UDP and TCP. -var ( - _ analyzer.UDPAnalyzer = (*DNSAnalyzer)(nil) - _ analyzer.TCPAnalyzer = (*DNSAnalyzer)(nil) -) - -type DNSAnalyzer struct{} - -func (a *DNSAnalyzer) Name() string { - return "dns" -} - -func (a *DNSAnalyzer) Limit() int { - // DNS is a stateless protocol, with unlimited amount - // of back-and-forth exchanges. Don't limit it here. - return 0 -} - -func (a *DNSAnalyzer) NewUDP(info analyzer.UDPInfo, logger analyzer.Logger) analyzer.UDPStream { - return &dnsUDPStream{logger: logger} -} - -func (a *DNSAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { - s := &dnsTCPStream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}} - s.reqLSM = utils.NewLinearStateMachine( - s.getReqMessageLength, - s.getReqMessage, - ) - s.respLSM = utils.NewLinearStateMachine( - s.getRespMessageLength, - s.getRespMessage, - ) - return s -} - -type dnsUDPStream struct { - logger analyzer.Logger - invalidCount int -} - -func (s *dnsUDPStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, done bool) { - m := parseDNSMessage(data) - // To allow non-DNS UDP traffic to get offloaded, - // we consider a UDP stream invalid and "done" if - // it has more than a certain number of consecutive - // packets that are not valid DNS messages. - if m == nil { - s.invalidCount++ - return nil, s.invalidCount >= dnsUDPInvalidCountThreshold - } - s.invalidCount = 0 // Reset invalid count on valid DNS message - return &analyzer.PropUpdate{ - Type: analyzer.PropUpdateReplace, - M: m, - }, false -} - -func (s *dnsUDPStream) Close(limited bool) *analyzer.PropUpdate { - return nil -} - -type dnsTCPStream 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 - - reqMsgLen int - respMsgLen int -} - -func (s *dnsTCPStream) 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.PropUpdateReplace, - M: 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.PropUpdateReplace, - M: s.reqMap, - } - s.reqUpdated = false - } - } - return update, cancelled || (s.reqDone && s.respDone) -} - -func (s *dnsTCPStream) Close(limited bool) *analyzer.PropUpdate { - s.reqBuf.Reset() - s.respBuf.Reset() - s.reqMap = nil - s.respMap = nil - return nil -} - -func (s *dnsTCPStream) getReqMessageLength() utils.LSMAction { - bs, ok := s.reqBuf.Get(2, true) - if !ok { - return utils.LSMActionPause - } - s.reqMsgLen = int(bs[0])<<8 | int(bs[1]) - return utils.LSMActionNext -} - -func (s *dnsTCPStream) getRespMessageLength() utils.LSMAction { - bs, ok := s.respBuf.Get(2, true) - if !ok { - return utils.LSMActionPause - } - s.respMsgLen = int(bs[0])<<8 | int(bs[1]) - return utils.LSMActionNext -} - -func (s *dnsTCPStream) getReqMessage() utils.LSMAction { - bs, ok := s.reqBuf.Get(s.reqMsgLen, true) - if !ok { - return utils.LSMActionPause - } - m := parseDNSMessage(bs) - if m == nil { - // Invalid DNS message - return utils.LSMActionCancel - } - s.reqMap = m - s.reqUpdated = true - return utils.LSMActionReset -} - -func (s *dnsTCPStream) getRespMessage() utils.LSMAction { - bs, ok := s.respBuf.Get(s.respMsgLen, true) - if !ok { - return utils.LSMActionPause - } - m := parseDNSMessage(bs) - if m == nil { - // Invalid DNS message - return utils.LSMActionCancel - } - s.respMap = m - s.respUpdated = true - return utils.LSMActionReset -} - -func parseDNSMessage(msg []byte) analyzer.PropMap { - dns := &layers.DNS{} - err := dns.DecodeFromBytes(msg, gopacket.NilDecodeFeedback) - if err != nil { - // Not a DNS packet - return nil - } - m := analyzer.PropMap{ - "id": dns.ID, - "qr": dns.QR, - "opcode": dns.OpCode, - "aa": dns.AA, - "tc": dns.TC, - "rd": dns.RD, - "ra": dns.RA, - "z": dns.Z, - "rcode": dns.ResponseCode, - } - if len(dns.Questions) > 0 { - mQuestions := make([]analyzer.PropMap, len(dns.Questions)) - for i, q := range dns.Questions { - mQuestions[i] = analyzer.PropMap{ - "name": string(q.Name), - "type": q.Type, - "class": q.Class, - } - } - m["questions"] = mQuestions - } - if len(dns.Answers) > 0 { - mAnswers := make([]analyzer.PropMap, len(dns.Answers)) - for i, rr := range dns.Answers { - mAnswers[i] = dnsRRToPropMap(rr) - } - m["answers"] = mAnswers - } - if len(dns.Authorities) > 0 { - mAuthorities := make([]analyzer.PropMap, len(dns.Authorities)) - for i, rr := range dns.Authorities { - mAuthorities[i] = dnsRRToPropMap(rr) - } - m["authorities"] = mAuthorities - } - if len(dns.Additionals) > 0 { - mAdditionals := make([]analyzer.PropMap, len(dns.Additionals)) - for i, rr := range dns.Additionals { - mAdditionals[i] = dnsRRToPropMap(rr) - } - m["additionals"] = mAdditionals - } - return m -} - -func dnsRRToPropMap(rr layers.DNSResourceRecord) analyzer.PropMap { - m := analyzer.PropMap{ - "name": string(rr.Name), - "type": rr.Type, - "class": rr.Class, - "ttl": rr.TTL, - } - switch rr.Type { - // These are not everything, but is - // all we decided to support for now. - case layers.DNSTypeA: - m["a"] = rr.IP.String() - case layers.DNSTypeAAAA: - m["aaaa"] = rr.IP.String() - case layers.DNSTypeNS: - m["ns"] = string(rr.NS) - case layers.DNSTypeCNAME: - m["cname"] = string(rr.CNAME) - case layers.DNSTypePTR: - m["ptr"] = string(rr.PTR) - case layers.DNSTypeTXT: - m["txt"] = utils.ByteSlicesToStrings(rr.TXTs) - case layers.DNSTypeMX: - m["mx"] = string(rr.MX.Name) - } - return m -} diff --git a/go-gost/gfw/analyzer/udp/internal/quic/header.go b/go-gost/gfw/analyzer/udp/internal/quic/header.go deleted file mode 100644 index 791f023..0000000 --- a/go-gost/gfw/analyzer/udp/internal/quic/header.go +++ /dev/null @@ -1,105 +0,0 @@ -package quic - -import ( - "bytes" - "encoding/binary" - "errors" - "io" - - "github.com/quic-go/quic-go/quicvarint" -) - -// The Header represents a QUIC header. -type Header struct { - Type uint8 - Version uint32 - SrcConnectionID []byte - DestConnectionID []byte - Length int64 - Token []byte -} - -// ParseInitialHeader parses the initial packet of a QUIC connection, -// return the initial header and number of bytes read so far. -func ParseInitialHeader(data []byte) (*Header, int64, error) { - br := bytes.NewReader(data) - hdr, err := parseLongHeader(br) - if err != nil { - return nil, 0, err - } - n := int64(len(data) - br.Len()) - return hdr, n, nil -} - -func parseLongHeader(b *bytes.Reader) (*Header, error) { - typeByte, err := b.ReadByte() - if err != nil { - return nil, err - } - h := &Header{} - ver, err := beUint32(b) - if err != nil { - return nil, err - } - h.Version = ver - if h.Version != 0 && typeByte&0x40 == 0 { - return nil, errors.New("not a QUIC packet") - } - destConnIDLen, err := b.ReadByte() - if err != nil { - return nil, err - } - h.DestConnectionID = make([]byte, int(destConnIDLen)) - if err := readConnectionID(b, h.DestConnectionID); err != nil { - return nil, err - } - srcConnIDLen, err := b.ReadByte() - if err != nil { - return nil, err - } - h.SrcConnectionID = make([]byte, int(srcConnIDLen)) - if err := readConnectionID(b, h.SrcConnectionID); err != nil { - return nil, err - } - - initialPacketType := byte(0b00) - if h.Version == V2 { - initialPacketType = 0b01 - } - if (typeByte >> 4 & 0b11) == initialPacketType { - tokenLen, err := quicvarint.Read(b) - if err != nil { - return nil, err - } - if tokenLen > uint64(b.Len()) { - return nil, io.EOF - } - h.Token = make([]byte, tokenLen) - if _, err := io.ReadFull(b, h.Token); err != nil { - return nil, err - } - } - - pl, err := quicvarint.Read(b) - if err != nil { - return nil, err - } - h.Length = int64(pl) - return h, err -} - -func readConnectionID(r io.Reader, cid []byte) error { - _, err := io.ReadFull(r, cid) - if err == io.ErrUnexpectedEOF { - return io.EOF - } - return nil -} - -func beUint32(r io.Reader) (uint32, error) { - b := make([]byte, 4) - if _, err := io.ReadFull(r, b); err != nil { - return 0, err - } - return binary.BigEndian.Uint32(b), nil -} diff --git a/go-gost/gfw/analyzer/udp/internal/quic/packet_protector.go b/go-gost/gfw/analyzer/udp/internal/quic/packet_protector.go deleted file mode 100644 index 42de841..0000000 --- a/go-gost/gfw/analyzer/udp/internal/quic/packet_protector.go +++ /dev/null @@ -1,193 +0,0 @@ -package quic - -import ( - "crypto" - "crypto/aes" - "crypto/cipher" - "crypto/sha256" - "crypto/tls" - "encoding/binary" - "errors" - "fmt" - "hash" - - "golang.org/x/crypto/chacha20" - "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/cryptobyte" - "golang.org/x/crypto/hkdf" -) - -// NewProtectionKey creates a new ProtectionKey. -func NewProtectionKey(suite uint16, secret []byte, v uint32) (*ProtectionKey, error) { - return newProtectionKey(suite, secret, v) -} - -// NewInitialProtectionKey is like NewProtectionKey, but the returned protection key -// is used for encrypt/decrypt Initial Packet only. -// -// See: https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-initial-secrets -func NewInitialProtectionKey(secret []byte, v uint32) (*ProtectionKey, error) { - return NewProtectionKey(tls.TLS_AES_128_GCM_SHA256, secret, v) -} - -// NewPacketProtector creates a new PacketProtector. -func NewPacketProtector(key *ProtectionKey) *PacketProtector { - return &PacketProtector{key: key} -} - -// PacketProtector is used for protecting a QUIC packet. -// -// See: https://www.rfc-editor.org/rfc/rfc9001.html#name-packet-protection -type PacketProtector struct { - key *ProtectionKey -} - -// UnProtect decrypts a QUIC packet. -func (pp *PacketProtector) UnProtect(packet []byte, pnOffset, pnMax int64) ([]byte, error) { - if isLongHeader(packet[0]) && int64(len(packet)) < pnOffset+4+16 { - return nil, errors.New("packet with long header is too small") - } - - // https://www.rfc-editor.org/rfc/rfc9001.html#name-header-protection-sample - sampleOffset := pnOffset + 4 - sample := packet[sampleOffset : sampleOffset+16] - - // https://www.rfc-editor.org/rfc/rfc9001.html#name-header-protection-applicati - mask := pp.key.headerProtection(sample) - if isLongHeader(packet[0]) { - // Long header: 4 bits masked - packet[0] ^= mask[0] & 0x0f - } else { - // Short header: 5 bits masked - packet[0] ^= mask[0] & 0x1f - } - - pnLen := packet[0]&0x3 + 1 - pn := int64(0) - for i := uint8(0); i < pnLen; i++ { - packet[pnOffset:][i] ^= mask[1+i] - pn = (pn << 8) | int64(packet[pnOffset:][i]) - } - pn = decodePacketNumber(pnMax, pn, pnLen) - hdr := packet[:pnOffset+int64(pnLen)] - payload := packet[pnOffset:][pnLen:] - dec, err := pp.key.aead.Open(payload[:0], pp.key.nonce(pn), payload, hdr) - if err != nil { - return nil, fmt.Errorf("decryption failed: %w", err) - } - return dec, nil -} - -// ProtectionKey is the key used to protect a QUIC packet. -type ProtectionKey struct { - aead cipher.AEAD - headerProtection func(sample []byte) (mask []byte) - iv []byte -} - -// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-aead-usage -// -// "The 62 bits of the reconstructed QUIC packet number in network byte order are -// left-padded with zeros to the size of the IV. The exclusive OR of the padded -// packet number and the IV forms the AEAD nonce." -func (pk *ProtectionKey) nonce(pn int64) []byte { - nonce := make([]byte, len(pk.iv)) - binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(pn)) - for i := range pk.iv { - nonce[i] ^= pk.iv[i] - } - return nonce -} - -func newProtectionKey(suite uint16, secret []byte, v uint32) (*ProtectionKey, error) { - switch suite { - case tls.TLS_AES_128_GCM_SHA256: - key := hkdfExpandLabel(crypto.SHA256.New, secret, keyLabel(v), nil, 16) - c, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - aead, err := cipher.NewGCM(c) - if err != nil { - panic(err) - } - iv := hkdfExpandLabel(crypto.SHA256.New, secret, ivLabel(v), nil, aead.NonceSize()) - hpKey := hkdfExpandLabel(crypto.SHA256.New, secret, headerProtectionLabel(v), nil, 16) - hp, err := aes.NewCipher(hpKey) - if err != nil { - panic(err) - } - k := &ProtectionKey{} - k.aead = aead - // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-aes-based-header-protection - k.headerProtection = func(sample []byte) []byte { - mask := make([]byte, hp.BlockSize()) - hp.Encrypt(mask, sample) - return mask - } - k.iv = iv - return k, nil - case tls.TLS_CHACHA20_POLY1305_SHA256: - key := hkdfExpandLabel(crypto.SHA256.New, secret, keyLabel(v), nil, chacha20poly1305.KeySize) - aead, err := chacha20poly1305.New(key) - if err != nil { - return nil, err - } - iv := hkdfExpandLabel(crypto.SHA256.New, secret, ivLabel(v), nil, aead.NonceSize()) - hpKey := hkdfExpandLabel(sha256.New, secret, headerProtectionLabel(v), nil, chacha20.KeySize) - k := &ProtectionKey{} - k.aead = aead - // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-chacha20-based-header-prote - k.headerProtection = func(sample []byte) []byte { - nonce := sample[4:16] - c, err := chacha20.NewUnauthenticatedCipher(hpKey, nonce) - if err != nil { - panic(err) - } - c.SetCounter(binary.LittleEndian.Uint32(sample[:4])) - mask := make([]byte, 5) - c.XORKeyStream(mask, mask) - return mask - } - k.iv = iv - return k, nil - } - return nil, errors.New("not supported cipher suite") -} - -// decodePacketNumber decode the packet number after header protection removed. -// -// See: https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-32#section-appendix.a -func decodePacketNumber(largest, truncated int64, nbits uint8) int64 { - expected := largest + 1 - win := int64(1 << (nbits * 8)) - hwin := win / 2 - mask := win - 1 - candidate := (expected &^ mask) | truncated - switch { - case candidate <= expected-hwin && candidate < (1<<62)-win: - return candidate + win - case candidate > expected+hwin && candidate >= win: - return candidate - win - } - return candidate -} - -// Copied from crypto/tls/key_schedule.go. -func hkdfExpandLabel(hash func() hash.Hash, secret []byte, label string, context []byte, length int) []byte { - var hkdfLabel cryptobyte.Builder - hkdfLabel.AddUint16(uint16(length)) - hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte("tls13 ")) - b.AddBytes([]byte(label)) - }) - hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(context) - }) - out := make([]byte, length) - n, err := hkdf.Expand(hash, secret, hkdfLabel.BytesOrPanic()).Read(out) - if err != nil || n != length { - panic("quic: HKDF-Expand-Label invocation failed unexpectedly") - } - return out -} diff --git a/go-gost/gfw/analyzer/udp/internal/quic/payload.go b/go-gost/gfw/analyzer/udp/internal/quic/payload.go deleted file mode 100644 index 87a0179..0000000 --- a/go-gost/gfw/analyzer/udp/internal/quic/payload.go +++ /dev/null @@ -1,122 +0,0 @@ -package quic - -import ( - "bytes" - "crypto" - "errors" - "fmt" - "io" - "sort" - - "github.com/quic-go/quic-go/quicvarint" - "golang.org/x/crypto/hkdf" -) - -func ReadCryptoPayload(packet []byte) ([]byte, error) { - hdr, offset, err := ParseInitialHeader(packet) - if err != nil { - return nil, err - } - // Some sanity checks - if hdr.Version != V1 && hdr.Version != V2 { - return nil, fmt.Errorf("unsupported version: %x", hdr.Version) - } - if offset == 0 || hdr.Length == 0 { - return nil, errors.New("invalid packet") - } - - initialSecret := hkdf.Extract(crypto.SHA256.New, hdr.DestConnectionID, getSalt(hdr.Version)) - clientSecret := hkdfExpandLabel(crypto.SHA256.New, initialSecret, "client in", []byte{}, crypto.SHA256.Size()) - key, err := NewInitialProtectionKey(clientSecret, hdr.Version) - if err != nil { - return nil, fmt.Errorf("NewInitialProtectionKey: %w", err) - } - pp := NewPacketProtector(key) - // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-client-initial - // - // "The unprotected header includes the connection ID and a 4-byte packet number encoding for a packet number of 2" - if int64(len(packet)) < offset+hdr.Length { - return nil, fmt.Errorf("packet is too short: %d < %d", len(packet), offset+hdr.Length) - } - unProtectedPayload, err := pp.UnProtect(packet[:offset+hdr.Length], offset, 2) - if err != nil { - return nil, err - } - frs, err := extractCryptoFrames(bytes.NewReader(unProtectedPayload)) - if err != nil { - return nil, err - } - data := assembleCryptoFrames(frs) - if data == nil { - return nil, errors.New("unable to assemble crypto frames") - } - return data, nil -} - -const ( - paddingFrameType = 0x00 - pingFrameType = 0x01 - cryptoFrameType = 0x06 -) - -type cryptoFrame struct { - Offset int64 - Data []byte -} - -func extractCryptoFrames(r *bytes.Reader) ([]cryptoFrame, error) { - var frames []cryptoFrame - for r.Len() > 0 { - typ, err := quicvarint.Read(r) - if err != nil { - return nil, err - } - if typ == paddingFrameType || typ == pingFrameType { - continue - } - if typ != cryptoFrameType { - return nil, fmt.Errorf("encountered unexpected frame type: %d", typ) - } - var frame cryptoFrame - offset, err := quicvarint.Read(r) - if err != nil { - return nil, err - } - frame.Offset = int64(offset) - dataLen, err := quicvarint.Read(r) - if err != nil { - return nil, err - } - frame.Data = make([]byte, dataLen) - if _, err := io.ReadFull(r, frame.Data); err != nil { - return nil, err - } - frames = append(frames, frame) - } - return frames, nil -} - -// assembleCryptoFrames assembles multiple crypto frames into a single slice (if possible). -// It returns an error if the frames cannot be assembled. This can happen if the frames are not contiguous. -func assembleCryptoFrames(frames []cryptoFrame) []byte { - if len(frames) == 0 { - return nil - } - if len(frames) == 1 { - return frames[0].Data - } - // sort the frames by offset - sort.Slice(frames, func(i, j int) bool { return frames[i].Offset < frames[j].Offset }) - // check if the frames are contiguous - for i := 1; i < len(frames); i++ { - if frames[i].Offset != frames[i-1].Offset+int64(len(frames[i-1].Data)) { - return nil - } - } - // concatenate the frames - data := make([]byte, frames[len(frames)-1].Offset+int64(len(frames[len(frames)-1].Data))) - for _, frame := range frames { - copy(data[frame.Offset:], frame.Data) - } - return data -} diff --git a/go-gost/gfw/analyzer/udp/internal/quic/quic.go b/go-gost/gfw/analyzer/udp/internal/quic/quic.go deleted file mode 100644 index 1cfa103..0000000 --- a/go-gost/gfw/analyzer/udp/internal/quic/quic.go +++ /dev/null @@ -1,59 +0,0 @@ -package quic - -const ( - V1 uint32 = 0x1 - V2 uint32 = 0x6b3343cf - - hkdfLabelKeyV1 = "quic key" - hkdfLabelKeyV2 = "quicv2 key" - hkdfLabelIVV1 = "quic iv" - hkdfLabelIVV2 = "quicv2 iv" - hkdfLabelHPV1 = "quic hp" - hkdfLabelHPV2 = "quicv2 hp" -) - -var ( - quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99} - // https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets - quicSaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a} - // https://www.ietf.org/archive/id/draft-ietf-quic-v2-10.html#name-initial-salt-2 - quicSaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9} -) - -// isLongHeader reports whether b is the first byte of a long header packet. -func isLongHeader(b byte) bool { - return b&0x80 > 0 -} - -func getSalt(v uint32) []byte { - switch v { - case V1: - return quicSaltV1 - case V2: - return quicSaltV2 - } - return quicSaltOld -} - -func keyLabel(v uint32) string { - kl := hkdfLabelKeyV1 - if v == V2 { - kl = hkdfLabelKeyV2 - } - return kl -} - -func ivLabel(v uint32) string { - ivl := hkdfLabelIVV1 - if v == V2 { - ivl = hkdfLabelIVV2 - } - return ivl -} - -func headerProtectionLabel(v uint32) string { - if v == V2 { - return hkdfLabelHPV2 - } - return hkdfLabelHPV1 -} diff --git a/go-gost/gfw/analyzer/udp/openvpn.go b/go-gost/gfw/analyzer/udp/openvpn.go deleted file mode 100644 index 3853726..0000000 --- a/go-gost/gfw/analyzer/udp/openvpn.go +++ /dev/null @@ -1,384 +0,0 @@ -package udp - -import ( - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/utils" -) - -var ( - _ analyzer.UDPAnalyzer = (*OpenVPNAnalyzer)(nil) - _ analyzer.TCPAnalyzer = (*OpenVPNAnalyzer)(nil) -) - -var ( - _ analyzer.UDPStream = (*openvpnUDPStream)(nil) - _ analyzer.TCPStream = (*openvpnTCPStream)(nil) -) - -// Ref paper: -// https://www.usenix.org/system/files/sec22fall_xue-diwen.pdf - -// OpenVPN Opcodes definitions from: -// https://github.com/OpenVPN/openvpn/blob/master/src/openvpn/ssl_pkt.h -const ( - OpenVPNControlHardResetClientV1 = 1 - OpenVPNControlHardResetServerV1 = 2 - OpenVPNControlSoftResetV1 = 3 - OpenVPNControlV1 = 4 - OpenVPNAckV1 = 5 - OpenVPNDataV1 = 6 - OpenVPNControlHardResetClientV2 = 7 - OpenVPNControlHardResetServerV2 = 8 - OpenVPNDataV2 = 9 - OpenVPNControlHardResetClientV3 = 10 - OpenVPNControlWkcV1 = 11 -) - -const ( - OpenVPNMinPktLen = 6 - OpenVPNTCPPktDefaultLimit = 256 - OpenVPNUDPPktDefaultLimit = 256 -) - -type OpenVPNAnalyzer struct{} - -func (a *OpenVPNAnalyzer) Name() string { - return "openvpn" -} - -func (a *OpenVPNAnalyzer) Limit() int { - return 0 -} - -func (a *OpenVPNAnalyzer) NewUDP(info analyzer.UDPInfo, logger analyzer.Logger) analyzer.UDPStream { - return newOpenVPNUDPStream(logger) -} - -func (a *OpenVPNAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { - return newOpenVPNTCPStream(logger) -} - -type openvpnPkt struct { - pktLen uint16 // 16 bits, TCP proto only - opcode byte // 5 bits - _keyId byte // 3 bits, not used - - // We don't care about the rest of the packet - // payload []byte -} - -type openvpnStream struct { - logger analyzer.Logger - - reqUpdated bool - reqLSM *utils.LinearStateMachine - reqDone bool - - respUpdated bool - respLSM *utils.LinearStateMachine - respDone bool - - rxPktCnt int - txPktCnt int - pktLimit int - - reqPktParse func() (*openvpnPkt, utils.LSMAction) - respPktParse func() (*openvpnPkt, utils.LSMAction) - - lastOpcode byte -} - -func (o *openvpnStream) parseCtlHardResetClient() utils.LSMAction { - pkt, action := o.reqPktParse() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != OpenVPNControlHardResetClientV1 && - pkt.opcode != OpenVPNControlHardResetClientV2 && - pkt.opcode != OpenVPNControlHardResetClientV3 { - return utils.LSMActionCancel - } - o.lastOpcode = pkt.opcode - - return utils.LSMActionNext -} - -func (o *openvpnStream) parseCtlHardResetServer() utils.LSMAction { - if o.lastOpcode != OpenVPNControlHardResetClientV1 && - o.lastOpcode != OpenVPNControlHardResetClientV2 && - o.lastOpcode != OpenVPNControlHardResetClientV3 { - return utils.LSMActionCancel - } - - pkt, action := o.respPktParse() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != OpenVPNControlHardResetServerV1 && - pkt.opcode != OpenVPNControlHardResetServerV2 { - return utils.LSMActionCancel - } - o.lastOpcode = pkt.opcode - - return utils.LSMActionNext -} - -func (o *openvpnStream) parseReq() utils.LSMAction { - pkt, action := o.reqPktParse() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != OpenVPNControlSoftResetV1 && - pkt.opcode != OpenVPNControlV1 && - pkt.opcode != OpenVPNAckV1 && - pkt.opcode != OpenVPNDataV1 && - pkt.opcode != OpenVPNDataV2 && - pkt.opcode != OpenVPNControlWkcV1 { - return utils.LSMActionCancel - } - - o.txPktCnt += 1 - o.reqUpdated = true - - return utils.LSMActionPause -} - -func (o *openvpnStream) parseResp() utils.LSMAction { - pkt, action := o.respPktParse() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != OpenVPNControlSoftResetV1 && - pkt.opcode != OpenVPNControlV1 && - pkt.opcode != OpenVPNAckV1 && - pkt.opcode != OpenVPNDataV1 && - pkt.opcode != OpenVPNDataV2 && - pkt.opcode != OpenVPNControlWkcV1 { - return utils.LSMActionCancel - } - - o.rxPktCnt += 1 - o.respUpdated = true - - return utils.LSMActionPause -} - -type openvpnUDPStream struct { - openvpnStream - curPkt []byte - // We don't introduce `invalidCount` here to decrease the false positive rate - // invalidCount int -} - -func newOpenVPNUDPStream(logger analyzer.Logger) *openvpnUDPStream { - s := &openvpnUDPStream{ - openvpnStream: openvpnStream{ - logger: logger, - pktLimit: OpenVPNUDPPktDefaultLimit, - }, - } - s.respPktParse = s.parsePkt - s.reqPktParse = s.parsePkt - s.reqLSM = utils.NewLinearStateMachine( - s.parseCtlHardResetClient, - s.parseReq, - ) - s.respLSM = utils.NewLinearStateMachine( - s.parseCtlHardResetServer, - s.parseResp, - ) - return s -} - -func (o *openvpnUDPStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, d bool) { - if len(data) == 0 { - return nil, false - } - var update *analyzer.PropUpdate - var cancelled bool - o.curPkt = data - if rev { - o.respUpdated = false - cancelled, o.respDone = o.respLSM.Run() - if o.respUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateReplace, - M: analyzer.PropMap{"rx_pkt_cnt": o.rxPktCnt, "tx_pkt_cnt": o.txPktCnt}, - } - o.respUpdated = false - } - } else { - o.reqUpdated = false - cancelled, o.reqDone = o.reqLSM.Run() - if o.reqUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateReplace, - M: analyzer.PropMap{"rx_pkt_cnt": o.rxPktCnt, "tx_pkt_cnt": o.txPktCnt}, - } - o.reqUpdated = false - } - } - - return update, cancelled || (o.reqDone && o.respDone) || o.rxPktCnt+o.txPktCnt > o.pktLimit -} - -func (o *openvpnUDPStream) Close(limited bool) *analyzer.PropUpdate { - return nil -} - -// Parse OpenVPN UDP packet. -func (o *openvpnUDPStream) parsePkt() (p *openvpnPkt, action utils.LSMAction) { - if o.curPkt == nil { - return nil, utils.LSMActionPause - } - - if !OpenVPNCheckForValidOpcode(o.curPkt[0] >> 3) { - return nil, utils.LSMActionCancel - } - - // Parse packet header - p = &openvpnPkt{} - p.opcode = o.curPkt[0] >> 3 - p._keyId = o.curPkt[0] & 0x07 - - o.curPkt = nil - return p, utils.LSMActionNext -} - -type openvpnTCPStream struct { - openvpnStream - reqBuf *utils.ByteBuffer - respBuf *utils.ByteBuffer -} - -func newOpenVPNTCPStream(logger analyzer.Logger) *openvpnTCPStream { - s := &openvpnTCPStream{ - openvpnStream: openvpnStream{ - logger: logger, - pktLimit: OpenVPNTCPPktDefaultLimit, - }, - reqBuf: &utils.ByteBuffer{}, - respBuf: &utils.ByteBuffer{}, - } - s.respPktParse = func() (*openvpnPkt, utils.LSMAction) { - return s.parsePkt(true) - } - s.reqPktParse = func() (*openvpnPkt, utils.LSMAction) { - return s.parsePkt(false) - } - s.reqLSM = utils.NewLinearStateMachine( - s.parseCtlHardResetClient, - s.parseReq, - ) - s.respLSM = utils.NewLinearStateMachine( - s.parseCtlHardResetServer, - s.parseResp, - ) - return s -} - -func (o *openvpnTCPStream) 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 { - o.respBuf.Append(data) - o.respUpdated = false - cancelled, o.respDone = o.respLSM.Run() - if o.respUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateReplace, - M: analyzer.PropMap{"rx_pkt_cnt": o.rxPktCnt, "tx_pkt_cnt": o.txPktCnt}, - } - o.respUpdated = false - } - } else { - o.reqBuf.Append(data) - o.reqUpdated = false - cancelled, o.reqDone = o.reqLSM.Run() - if o.reqUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"rx_pkt_cnt": o.rxPktCnt, "tx_pkt_cnt": o.txPktCnt}, - } - o.reqUpdated = false - } - } - - return update, cancelled || (o.reqDone && o.respDone) || o.rxPktCnt+o.txPktCnt > o.pktLimit -} - -func (o *openvpnTCPStream) Close(limited bool) *analyzer.PropUpdate { - o.reqBuf.Reset() - o.respBuf.Reset() - return nil -} - -// Parse OpenVPN TCP packet. -func (o *openvpnTCPStream) parsePkt(rev bool) (p *openvpnPkt, action utils.LSMAction) { - var buffer *utils.ByteBuffer - if rev { - buffer = o.respBuf - } else { - buffer = o.reqBuf - } - - // Parse packet length - pktLen, ok := buffer.GetUint16(false, false) - if !ok { - return nil, utils.LSMActionPause - } - - if pktLen < OpenVPNMinPktLen { - return nil, utils.LSMActionCancel - } - - pktOp, ok := buffer.Get(3, false) - if !ok { - return nil, utils.LSMActionPause - } - if !OpenVPNCheckForValidOpcode(pktOp[2] >> 3) { - return nil, utils.LSMActionCancel - } - - pkt, ok := buffer.Get(int(pktLen)+2, true) - if !ok { - return nil, utils.LSMActionPause - } - pkt = pkt[2:] - - // Parse packet header - p = &openvpnPkt{} - p.pktLen = pktLen - p.opcode = pkt[0] >> 3 - p._keyId = pkt[0] & 0x07 - - return p, utils.LSMActionNext -} - -func OpenVPNCheckForValidOpcode(opcode byte) bool { - switch opcode { - case OpenVPNControlHardResetClientV1, - OpenVPNControlHardResetServerV1, - OpenVPNControlSoftResetV1, - OpenVPNControlV1, - OpenVPNAckV1, - OpenVPNDataV1, - OpenVPNControlHardResetClientV2, - OpenVPNControlHardResetServerV2, - OpenVPNDataV2, - OpenVPNControlHardResetClientV3, - OpenVPNControlWkcV1: - return true - } - return false -} diff --git a/go-gost/gfw/analyzer/udp/quic.go b/go-gost/gfw/analyzer/udp/quic.go deleted file mode 100644 index a1a9ef0..0000000 --- a/go-gost/gfw/analyzer/udp/quic.go +++ /dev/null @@ -1,81 +0,0 @@ -package udp - -import ( - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/internal" - "github.com/apernet/OpenGFW/analyzer/udp/internal/quic" - "github.com/apernet/OpenGFW/analyzer/utils" -) - -const ( - quicInvalidCountThreshold = 4 -) - -var ( - _ analyzer.UDPAnalyzer = (*QUICAnalyzer)(nil) - _ analyzer.UDPStream = (*quicStream)(nil) -) - -type QUICAnalyzer struct{} - -func (a *QUICAnalyzer) Name() string { - return "quic" -} - -func (a *QUICAnalyzer) Limit() int { - return 0 -} - -func (a *QUICAnalyzer) NewUDP(info analyzer.UDPInfo, logger analyzer.Logger) analyzer.UDPStream { - return &quicStream{logger: logger} -} - -type quicStream struct { - logger analyzer.Logger - invalidCount int -} - -func (s *quicStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, done bool) { - // 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 - - if rev { - // We don't support server direction for now - s.invalidCount++ - return nil, s.invalidCount >= quicInvalidCountThreshold - } - - pl, err := quic.ReadCryptoPayload(data) - if err != nil || len(pl) < 4 { // FIXME: isn't length checked inside quic.ReadCryptoPayload? Also, what about error handling? - s.invalidCount++ - return nil, s.invalidCount >= quicInvalidCountThreshold - } - - if pl[0] != internal.TypeClientHello { - s.invalidCount++ - return nil, s.invalidCount >= quicInvalidCountThreshold - } - - chLen := int(pl[1])<<16 | int(pl[2])<<8 | int(pl[3]) - if chLen < minDataSize { - s.invalidCount++ - return nil, s.invalidCount >= quicInvalidCountThreshold - } - - m := internal.ParseTLSClientHelloMsgData(&utils.ByteBuffer{Buf: pl[4:]}) - if m == nil { - s.invalidCount++ - return nil, s.invalidCount >= quicInvalidCountThreshold - } - - return &analyzer.PropUpdate{ - Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"req": m}, - }, true -} - -func (s *quicStream) Close(limited bool) *analyzer.PropUpdate { - return nil -} diff --git a/go-gost/gfw/analyzer/udp/wireguard.go b/go-gost/gfw/analyzer/udp/wireguard.go deleted file mode 100644 index b53390b..0000000 --- a/go-gost/gfw/analyzer/udp/wireguard.go +++ /dev/null @@ -1,217 +0,0 @@ -package udp - -import ( - "container/ring" - "encoding/binary" - "slices" - "sync" - - "github.com/apernet/OpenGFW/analyzer" -) - -var ( - _ analyzer.UDPAnalyzer = (*WireGuardAnalyzer)(nil) - _ analyzer.UDPStream = (*wireGuardUDPStream)(nil) -) - -const ( - wireguardUDPInvalidCountThreshold = 4 - wireguardRememberedIndexCount = 6 - wireguardPropKeyMessageType = "message_type" -) - -const ( - wireguardTypeHandshakeInitiation = 1 - wireguardTypeHandshakeResponse = 2 - wireguardTypeData = 4 - wireguardTypeCookieReply = 3 -) - -const ( - wireguardSizeHandshakeInitiation = 148 - wireguardSizeHandshakeResponse = 92 - wireguardMinSizePacketData = 32 // 16 bytes header + 16 bytes AEAD overhead - wireguardSizePacketCookieReply = 64 -) - -type WireGuardAnalyzer struct{} - -func (a *WireGuardAnalyzer) Name() string { - return "wireguard" -} - -func (a *WireGuardAnalyzer) Limit() int { - return 0 -} - -func (a *WireGuardAnalyzer) NewUDP(info analyzer.UDPInfo, logger analyzer.Logger) analyzer.UDPStream { - return newWireGuardUDPStream(logger) -} - -type wireGuardUDPStream struct { - logger analyzer.Logger - invalidCount int - rememberedIndexes *ring.Ring - rememberedIndexesLock sync.RWMutex -} - -func newWireGuardUDPStream(logger analyzer.Logger) *wireGuardUDPStream { - return &wireGuardUDPStream{ - logger: logger, - rememberedIndexes: ring.New(wireguardRememberedIndexCount), - } -} - -func (s *wireGuardUDPStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, done bool) { - m := s.parseWireGuardPacket(rev, data) - if m == nil { - s.invalidCount++ - return nil, s.invalidCount >= wireguardUDPInvalidCountThreshold - } - s.invalidCount = 0 // Reset invalid count on valid WireGuard packet - messageType := m[wireguardPropKeyMessageType].(byte) - propUpdateType := analyzer.PropUpdateMerge - if messageType == wireguardTypeHandshakeInitiation { - propUpdateType = analyzer.PropUpdateReplace - } - return &analyzer.PropUpdate{ - Type: propUpdateType, - M: m, - }, false -} - -func (s *wireGuardUDPStream) Close(limited bool) *analyzer.PropUpdate { - return nil -} - -func (s *wireGuardUDPStream) parseWireGuardPacket(rev bool, data []byte) analyzer.PropMap { - if len(data) < 4 { - return nil - } - if slices.Max(data[1:4]) != 0 { - return nil - } - - messageType := data[0] - var propKey string - var propValue analyzer.PropMap - switch messageType { - case wireguardTypeHandshakeInitiation: - propKey = "handshake_initiation" - propValue = s.parseWireGuardHandshakeInitiation(rev, data) - case wireguardTypeHandshakeResponse: - propKey = "handshake_response" - propValue = s.parseWireGuardHandshakeResponse(rev, data) - case wireguardTypeData: - propKey = "packet_data" - propValue = s.parseWireGuardPacketData(rev, data) - case wireguardTypeCookieReply: - propKey = "packet_cookie_reply" - propValue = s.parseWireGuardPacketCookieReply(rev, data) - } - if propValue == nil { - return nil - } - - m := make(analyzer.PropMap) - m[wireguardPropKeyMessageType] = messageType - m[propKey] = propValue - return m -} - -func (s *wireGuardUDPStream) parseWireGuardHandshakeInitiation(rev bool, data []byte) analyzer.PropMap { - if len(data) != wireguardSizeHandshakeInitiation { - return nil - } - m := make(analyzer.PropMap) - - senderIndex := binary.LittleEndian.Uint32(data[4:8]) - m["sender_index"] = senderIndex - s.putSenderIndex(rev, senderIndex) - - return m -} - -func (s *wireGuardUDPStream) parseWireGuardHandshakeResponse(rev bool, data []byte) analyzer.PropMap { - if len(data) != wireguardSizeHandshakeResponse { - return nil - } - m := make(analyzer.PropMap) - - senderIndex := binary.LittleEndian.Uint32(data[4:8]) - m["sender_index"] = senderIndex - s.putSenderIndex(rev, senderIndex) - - receiverIndex := binary.LittleEndian.Uint32(data[8:12]) - m["receiver_index"] = receiverIndex - m["receiver_index_matched"] = s.matchReceiverIndex(rev, receiverIndex) - - return m -} - -func (s *wireGuardUDPStream) parseWireGuardPacketData(rev bool, data []byte) analyzer.PropMap { - if len(data) < wireguardMinSizePacketData { - return nil - } - if len(data)%16 != 0 { - // WireGuard zero padding the packet to make the length a multiple of 16 - return nil - } - m := make(analyzer.PropMap) - - receiverIndex := binary.LittleEndian.Uint32(data[4:8]) - m["receiver_index"] = receiverIndex - m["receiver_index_matched"] = s.matchReceiverIndex(rev, receiverIndex) - - m["counter"] = binary.LittleEndian.Uint64(data[8:16]) - - return m -} - -func (s *wireGuardUDPStream) parseWireGuardPacketCookieReply(rev bool, data []byte) analyzer.PropMap { - if len(data) != wireguardSizePacketCookieReply { - return nil - } - m := make(analyzer.PropMap) - - receiverIndex := binary.LittleEndian.Uint32(data[4:8]) - m["receiver_index"] = receiverIndex - m["receiver_index_matched"] = s.matchReceiverIndex(rev, receiverIndex) - - return m -} - -type wireGuardIndex struct { - SenderIndex uint32 - Reverse bool -} - -func (s *wireGuardUDPStream) putSenderIndex(rev bool, senderIndex uint32) { - s.rememberedIndexesLock.Lock() - defer s.rememberedIndexesLock.Unlock() - - s.rememberedIndexes.Value = &wireGuardIndex{ - SenderIndex: senderIndex, - Reverse: rev, - } - s.rememberedIndexes = s.rememberedIndexes.Prev() -} - -func (s *wireGuardUDPStream) matchReceiverIndex(rev bool, receiverIndex uint32) bool { - s.rememberedIndexesLock.RLock() - defer s.rememberedIndexesLock.RUnlock() - - var found bool - ris := s.rememberedIndexes - for it := ris.Next(); it != ris; it = it.Next() { - if it.Value == nil { - break - } - wgidx := it.Value.(*wireGuardIndex) - if wgidx.Reverse == !rev && wgidx.SenderIndex == receiverIndex { - found = true - break - } - } - return found -} diff --git a/go-gost/gfw/analyzer/utils/bytebuffer.go b/go-gost/gfw/analyzer/utils/bytebuffer.go deleted file mode 100644 index 495e1e2..0000000 --- a/go-gost/gfw/analyzer/utils/bytebuffer.go +++ /dev/null @@ -1,99 +0,0 @@ -package utils - -import "bytes" - -type ByteBuffer struct { - Buf []byte -} - -func (b *ByteBuffer) Append(data []byte) { - b.Buf = append(b.Buf, data...) -} - -func (b *ByteBuffer) Len() int { - return len(b.Buf) -} - -func (b *ByteBuffer) Index(sep []byte) int { - return bytes.Index(b.Buf, sep) -} - -func (b *ByteBuffer) Get(length int, consume bool) (data []byte, ok bool) { - if len(b.Buf) < length { - return nil, false - } - data = b.Buf[:length] - if consume { - b.Buf = b.Buf[length:] - } - return data, true -} - -func (b *ByteBuffer) GetString(length int, consume bool) (string, bool) { - data, ok := b.Get(length, consume) - if !ok { - return "", false - } - return string(data), true -} - -func (b *ByteBuffer) GetByte(consume bool) (byte, bool) { - data, ok := b.Get(1, consume) - if !ok { - return 0, false - } - return data[0], true -} - -func (b *ByteBuffer) GetUint16(littleEndian, consume bool) (uint16, bool) { - data, ok := b.Get(2, consume) - if !ok { - return 0, false - } - if littleEndian { - return uint16(data[0]) | uint16(data[1])<<8, true - } - return uint16(data[1]) | uint16(data[0])<<8, true -} - -func (b *ByteBuffer) GetUint32(littleEndian, consume bool) (uint32, bool) { - data, ok := b.Get(4, consume) - if !ok { - return 0, false - } - if littleEndian { - return uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24, true - } - return uint32(data[3]) | uint32(data[2])<<8 | uint32(data[1])<<16 | uint32(data[0])<<24, true -} - -func (b *ByteBuffer) GetUntil(sep []byte, includeSep, consume bool) (data []byte, ok bool) { - index := b.Index(sep) - if index == -1 { - return nil, false - } - if includeSep { - index += len(sep) - } - return b.Get(index, consume) -} - -func (b *ByteBuffer) GetSubBuffer(length int, consume bool) (sub *ByteBuffer, ok bool) { - data, ok := b.Get(length, consume) - if !ok { - return nil, false - } - return &ByteBuffer{Buf: data}, true -} - -func (b *ByteBuffer) Skip(length int) bool { - if len(b.Buf) < length { - return false - } - b.Buf = b.Buf[length:] - return true -} - -func (b *ByteBuffer) Reset() { - b.Buf = nil -} diff --git a/go-gost/gfw/analyzer/utils/lsm.go b/go-gost/gfw/analyzer/utils/lsm.go deleted file mode 100644 index 096df60..0000000 --- a/go-gost/gfw/analyzer/utils/lsm.go +++ /dev/null @@ -1,54 +0,0 @@ -package utils - -type LSMAction int - -const ( - LSMActionPause LSMAction = iota - LSMActionNext - LSMActionReset - LSMActionCancel -) - -type LinearStateMachine struct { - Steps []func() LSMAction - - index int - cancelled bool -} - -func NewLinearStateMachine(steps ...func() LSMAction) *LinearStateMachine { - return &LinearStateMachine{ - Steps: steps, - } -} - -// Run runs the state machine until it pauses, finishes or is cancelled. -func (lsm *LinearStateMachine) Run() (cancelled bool, done bool) { - if lsm.index >= len(lsm.Steps) { - return lsm.cancelled, true - } - for lsm.index < len(lsm.Steps) { - action := lsm.Steps[lsm.index]() - switch action { - case LSMActionPause: - return false, false - case LSMActionNext: - lsm.index++ - case LSMActionReset: - lsm.index = 0 - case LSMActionCancel: - lsm.cancelled = true - return true, true - } - } - return false, true -} - -func (lsm *LinearStateMachine) AppendSteps(steps ...func() LSMAction) { - lsm.Steps = append(lsm.Steps, steps...) -} - -func (lsm *LinearStateMachine) Reset() { - lsm.index = 0 - lsm.cancelled = false -} diff --git a/go-gost/gfw/analyzer/utils/string.go b/go-gost/gfw/analyzer/utils/string.go deleted file mode 100644 index 9d278fb..0000000 --- a/go-gost/gfw/analyzer/utils/string.go +++ /dev/null @@ -1,9 +0,0 @@ -package utils - -func ByteSlicesToStrings(bss [][]byte) []string { - ss := make([]string, len(bss)) - for i, bs := range bss { - ss[i] = string(bs) - } - return ss -} diff --git a/go-gost/gfw/cmd/errors.go b/go-gost/gfw/cmd/errors.go deleted file mode 100644 index 3d0234a..0000000 --- a/go-gost/gfw/cmd/errors.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -import ( - "fmt" -) - -type configError struct { - Field string - Err error -} - -func (e configError) Error() string { - return fmt.Sprintf("invalid config: %s: %s", e.Field, e.Err) -} - -func (e configError) Unwrap() error { - return e.Err -} diff --git a/go-gost/gfw/cmd/root.go b/go-gost/gfw/cmd/root.go deleted file mode 100644 index 52bdcc8..0000000 --- a/go-gost/gfw/cmd/root.go +++ /dev/null @@ -1,544 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "os" - "os/signal" - "runtime" - "strings" - "syscall" - "time" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/tcp" - "github.com/apernet/OpenGFW/analyzer/udp" - "github.com/apernet/OpenGFW/engine" - "github.com/apernet/OpenGFW/io" - "github.com/apernet/OpenGFW/modifier" - modUDP "github.com/apernet/OpenGFW/modifier/udp" - "github.com/apernet/OpenGFW/ruleset" - "github.com/apernet/OpenGFW/ruleset/builtins/geo" - - "github.com/shirou/gopsutil/v3/cpu" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -const ( - appLogo = ` -░█▀█░█▀█░█▀▀░█▀█░█▀▀░█▀▀░█░█ -░█░█░█▀▀░█▀▀░█░█░█░█░█▀▀░█▄█ -░▀▀▀░▀░░░▀▀▀░▀░▀░▀▀▀░▀░░░▀░▀ -` - appDesc = "Open source network filtering and analysis software" - appAuthors = "Aperture Internet Laboratory " - - appLogLevelEnv = "OPENGFW_LOG_LEVEL" - appLogFormatEnv = "OPENGFW_LOG_FORMAT" -) - -var logger *zap.Logger - -// Flags -var ( - cfgFile string - pcapFile string - logLevel string - logFormat string -) - -var rootCmd = &cobra.Command{ - Use: "OpenGFW [flags] rule_file", - Short: appDesc, - Args: cobra.ExactArgs(1), - Run: runMain, -} - -var logLevelMap = map[string]zapcore.Level{ - "debug": zapcore.DebugLevel, - "info": zapcore.InfoLevel, - "warn": zapcore.WarnLevel, - "error": zapcore.ErrorLevel, -} - -var logFormatMap = map[string]zapcore.EncoderConfig{ - "console": { - TimeKey: "time", - LevelKey: "level", - NameKey: "logger", - MessageKey: "msg", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalColorLevelEncoder, - EncodeTime: zapcore.RFC3339TimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - }, - "json": { - TimeKey: "time", - LevelKey: "level", - NameKey: "logger", - MessageKey: "msg", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.LowercaseLevelEncoder, - EncodeTime: zapcore.EpochMillisTimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - }, -} - -// Analyzers & modifiers - -var analyzers = []analyzer.Analyzer{ - &tcp.FETAnalyzer{}, - &tcp.HTTPAnalyzer{}, - &tcp.SocksAnalyzer{}, - &tcp.SSHAnalyzer{}, - &tcp.TLSAnalyzer{}, - &tcp.TrojanAnalyzer{}, - &udp.DNSAnalyzer{}, - &udp.OpenVPNAnalyzer{}, - &udp.QUICAnalyzer{}, - &udp.WireGuardAnalyzer{}, -} - -var modifiers = []modifier.Modifier{ - &modUDP.DNSModifier{}, -} - -func Execute(args []string) { - rootCmd.SetArgs(args) - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - initFlags() - cobra.OnInitialize(initConfig) - cobra.OnInitialize(initLogger) // initLogger must come after initConfig as it depends on config -} - -func initFlags() { - rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file") - rootCmd.PersistentFlags().StringVarP(&pcapFile, "pcap", "p", "", "pcap file (optional)") - rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", envOrDefaultString(appLogLevelEnv, "info"), "log level") - rootCmd.PersistentFlags().StringVarP(&logFormat, "log-format", "f", envOrDefaultString(appLogFormatEnv, "console"), "log format") -} - -func initConfig() { - if cfgFile != "" { - viper.SetConfigFile(cfgFile) - } else { - viper.SetConfigName("config") - viper.SetConfigType("yaml") - viper.SupportedExts = append([]string{"yaml", "yml"}, viper.SupportedExts...) - viper.AddConfigPath(".") - viper.AddConfigPath("$HOME/.opengfw") - viper.AddConfigPath("/etc/opengfw") - } -} - -func initLogger() { - level, ok := logLevelMap[strings.ToLower(logLevel)] - if !ok { - fmt.Printf("unsupported log level: %s\n", logLevel) - os.Exit(1) - } - enc, ok := logFormatMap[strings.ToLower(logFormat)] - if !ok { - fmt.Printf("unsupported log format: %s\n", logFormat) - os.Exit(1) - } - c := zap.Config{ - Level: zap.NewAtomicLevelAt(level), - DisableCaller: true, - DisableStacktrace: true, - Encoding: strings.ToLower(logFormat), - EncoderConfig: enc, - OutputPaths: []string{"stderr"}, - ErrorOutputPaths: []string{"stderr"}, - } - var err error - logger, err = c.Build() - if err != nil { - fmt.Printf("failed to initialize logger: %s\n", err) - os.Exit(1) - } -} - -type cliConfig struct { - IO cliConfigIO `mapstructure:"io"` - Workers cliConfigWorkers `mapstructure:"workers"` - Ruleset cliConfigRuleset `mapstructure:"ruleset"` - Replay cliConfigReplay `mapstructure:"replay"` -} - -type cliConfigIO struct { - QueueSize uint32 `mapstructure:"queueSize"` - QueueNum *uint16 `mapstructure:"queueNum"` - Table string `mapstructure:"table"` - ConnMarkAccept uint32 `mapstructure:"connMarkAccept"` - ConnMarkDrop uint32 `mapstructure:"connMarkDrop"` - - ReadBuffer int `mapstructure:"rcvBuf"` - WriteBuffer int `mapstructure:"sndBuf"` - Local bool `mapstructure:"local"` - RST bool `mapstructure:"rst"` -} - -type cliConfigReplay struct { - Realtime bool `mapstructure:"realtime"` -} - -type cliConfigWorkers struct { - Count int `mapstructure:"count"` - QueueSize int `mapstructure:"queueSize"` - TCPMaxBufferedPagesTotal int `mapstructure:"tcpMaxBufferedPagesTotal"` - TCPMaxBufferedPagesPerConn int `mapstructure:"tcpMaxBufferedPagesPerConn"` - TCPTimeout time.Duration `mapstructure:"tcpTimeout"` - UDPMaxStreams int `mapstructure:"udpMaxStreams"` -} - -type cliConfigRuleset struct { - GeoIp string `mapstructure:"geoip"` - GeoSite string `mapstructure:"geosite"` -} - -func (c *cliConfig) fillLogger(config *engine.Config) error { - config.Logger = &engineLogger{} - return nil -} - -func (c *cliConfig) fillIO(config *engine.Config) error { - var ioImpl io.PacketIO - var err error - if pcapFile != "" { - // Setup IO for pcap file replay - logger.Info("replaying from pcap file", zap.String("pcap file", pcapFile)) - ioImpl, err = io.NewPcapPacketIO(io.PcapPacketIOConfig{ - PcapFile: pcapFile, - Realtime: c.Replay.Realtime, - }) - } else { - // Setup IO for nfqueue - ioImpl, err = io.NewNFQueuePacketIO(io.NFQueuePacketIOConfig{ - QueueSize: c.IO.QueueSize, - QueueNum: c.IO.QueueNum, - Table: c.IO.Table, - ConnMarkAccept: c.IO.ConnMarkAccept, - ConnMarkDrop: c.IO.ConnMarkDrop, - - ReadBuffer: c.IO.ReadBuffer, - WriteBuffer: c.IO.WriteBuffer, - Local: c.IO.Local, - RST: c.IO.RST, - }) - } - - if err != nil { - return configError{Field: "io", Err: err} - } - config.IO = ioImpl - return nil -} - -func (c *cliConfig) fillWorkers(config *engine.Config) error { - config.Workers = c.Workers.Count - config.WorkerQueueSize = c.Workers.QueueSize - config.WorkerTCPMaxBufferedPagesTotal = c.Workers.TCPMaxBufferedPagesTotal - config.WorkerTCPMaxBufferedPagesPerConn = c.Workers.TCPMaxBufferedPagesPerConn - config.WorkerTCPTimeout = c.Workers.TCPTimeout - config.WorkerUDPMaxStreams = c.Workers.UDPMaxStreams - return nil -} - -// Config validates the fields and returns a ready-to-use engine config. -// This does not include the ruleset. -func (c *cliConfig) Config() (*engine.Config, error) { - engineConfig := &engine.Config{} - fillers := []func(*engine.Config) error{ - c.fillLogger, - c.fillIO, - c.fillWorkers, - } - for _, f := range fillers { - if err := f(engineConfig); err != nil { - return nil, err - } - } - return engineConfig, nil -} - -func getCPUCores() int { - // 尝试获取物理CPU核心数 - if cpuInfo, err := cpu.Info(); err == nil && len(cpuInfo) > 0 { - // 获取第一个CPU的核心数 - cores := int(cpuInfo[0].Cores) - if cores > 0 { - return cores - } - } - // 如果获取失败,使用runtime.NumCPU()作为后备 - return runtime.NumCPU() -} - -func getDefaultConfig() cliConfig { - cores := getCPUCores() - queueSize := cores * 64 - if queueSize > 256 { - queueSize = 256 - } - - return cliConfig{ - IO: cliConfigIO{ - QueueSize: 1024, - QueueNum: nil, - Table: "", - ConnMarkAccept: 0, - ConnMarkDrop: 0, - ReadBuffer: 4194304, - WriteBuffer: 4194304, - Local: true, - RST: false, - }, - Workers: cliConfigWorkers{ - Count: cores, - QueueSize: queueSize, - TCPMaxBufferedPagesTotal: 65536, - TCPMaxBufferedPagesPerConn: 16, - TCPTimeout: 10 * time.Minute, - UDPMaxStreams: 4096, - }, - Replay: cliConfigReplay{ - Realtime: false, - }, - Ruleset: cliConfigRuleset{ - GeoIp: "", - GeoSite: "", - }, - } -} - -func runMain(cmd *cobra.Command, args []string) { - // Config - config := getDefaultConfig() - - // Try to read config file if specified, otherwise use defaults - if cfgFile != "" || viper.ConfigFileUsed() != "" { - if err := viper.ReadInConfig(); err != nil { - logger.Warn("failed to read config file, using defaults", zap.Error(err)) - } else { - if err := viper.Unmarshal(&config); err != nil { - logger.Fatal("failed to parse config", zap.Error(err)) - } - } - } - - engineConfig, err := config.Config() - if err != nil { - logger.Fatal("failed to parse config", zap.Error(err)) - } - defer engineConfig.IO.Close() // Make sure to close IO on exit - - // Ruleset - rawRs, err := ruleset.ExprRulesFromYAML(args[0]) - if err != nil { - logger.Fatal("failed to load rules", zap.Error(err)) - } - rsConfig := &ruleset.BuiltinConfig{ - Logger: &rulesetLogger{}, - GeoMatcher: geo.NewGeoMatcher(config.Ruleset.GeoSite, config.Ruleset.GeoIp), - ProtectedDialContext: engineConfig.IO.ProtectedDialContext, - } - rs, err := ruleset.CompileExprRules(rawRs, analyzers, modifiers, rsConfig) - if err != nil { - logger.Fatal("failed to compile rules", zap.Error(err)) - } - engineConfig.Ruleset = rs - - // Engine - en, err := engine.NewEngine(*engineConfig) - if err != nil { - logger.Fatal("failed to initialize engine", zap.Error(err)) - } - - // Signal handling - ctx, cancelFunc := context.WithCancel(context.Background()) - go func() { - // Graceful shutdown - shutdownChan := make(chan os.Signal, 1) - signal.Notify(shutdownChan, os.Interrupt, syscall.SIGTERM) - <-shutdownChan - logger.Info("shutting down gracefully...") - cancelFunc() - }() - go func() { - // Rule reload - reloadChan := make(chan os.Signal, 1) - signal.Notify(reloadChan, syscall.SIGHUP) - for { - <-reloadChan - logger.Info("reloading rules") - rawRs, err := ruleset.ExprRulesFromYAML(args[0]) - if err != nil { - logger.Error("failed to load rules, using old rules", zap.Error(err)) - continue - } - rs, err := ruleset.CompileExprRules(rawRs, analyzers, modifiers, rsConfig) - if err != nil { - logger.Error("failed to compile rules, using old rules", zap.Error(err)) - continue - } - err = en.UpdateRuleset(rs) - if err != nil { - logger.Error("failed to update ruleset", zap.Error(err)) - } else { - logger.Info("rules reloaded") - } - } - }() - - logger.Info("engine started") - logger.Info("engine exited", zap.Error(en.Run(ctx))) -} - -type engineLogger struct{} - -func (l *engineLogger) WorkerStart(id int) { - logger.Debug("worker started", zap.Int("id", id)) -} - -func (l *engineLogger) WorkerStop(id int) { - logger.Debug("worker stopped", zap.Int("id", id)) -} - -func (l *engineLogger) TCPStreamNew(workerID int, info ruleset.StreamInfo) { - logger.Debug("new TCP stream", - zap.Int("workerID", workerID), - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString())) -} - -func (l *engineLogger) TCPStreamPropUpdate(info ruleset.StreamInfo, close bool) { - logger.Debug("TCP stream property update", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.Any("props", info.Props), - zap.Bool("close", close)) -} - -func (l *engineLogger) TCPStreamAction(info ruleset.StreamInfo, action ruleset.Action, noMatch bool) { - if noMatch { - logger.Debug("TCP stream no match", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.String("action", action.String())) - } else { - logger.Info("TCP stream action", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.String("action", action.String())) - } -} - -func (l *engineLogger) TCPFlush(workerID, flushed, closed int) { - logger.Debug("TCP flush", - zap.Int("workerID", workerID), - zap.Int("flushed", flushed), - zap.Int("closed", closed)) -} - -func (l *engineLogger) UDPStreamNew(workerID int, info ruleset.StreamInfo) { - logger.Debug("new UDP stream", - zap.Int("workerID", workerID), - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString())) -} - -func (l *engineLogger) UDPStreamPropUpdate(info ruleset.StreamInfo, close bool) { - logger.Debug("UDP stream property update", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.Any("props", info.Props), - zap.Bool("close", close)) -} - -func (l *engineLogger) UDPStreamAction(info ruleset.StreamInfo, action ruleset.Action, noMatch bool) { - if noMatch { - logger.Debug("UDP stream no match", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.String("action", action.String())) - } else { - logger.Info("UDP stream action", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.String("action", action.String())) - } -} - -func (l *engineLogger) ModifyError(info ruleset.StreamInfo, err error) { - logger.Error("modify error", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.Error(err)) -} - -func (l *engineLogger) AnalyzerDebugf(streamID int64, name string, format string, args ...interface{}) { - logger.Debug("analyzer debug message", - zap.Int64("id", streamID), - zap.String("name", name), - zap.String("msg", fmt.Sprintf(format, args...))) -} - -func (l *engineLogger) AnalyzerInfof(streamID int64, name string, format string, args ...interface{}) { - logger.Info("analyzer info message", - zap.Int64("id", streamID), - zap.String("name", name), - zap.String("msg", fmt.Sprintf(format, args...))) -} - -func (l *engineLogger) AnalyzerErrorf(streamID int64, name string, format string, args ...interface{}) { - logger.Error("analyzer error message", - zap.Int64("id", streamID), - zap.String("name", name), - zap.String("msg", fmt.Sprintf(format, args...))) -} - -type rulesetLogger struct{} - -func (l *rulesetLogger) Log(info ruleset.StreamInfo, name string) { - logger.Info("ruleset log", - zap.String("name", name), - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.Any("props", info.Props)) -} - -func (l *rulesetLogger) MatchError(info ruleset.StreamInfo, name string, err error) { - logger.Error("ruleset match error", - zap.String("name", name), - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.Error(err)) -} - -func envOrDefaultString(key, def string) string { - if v := os.Getenv(key); v != "" { - return v - } - return def -} diff --git a/go-gost/gfw/engine/engine.go b/go-gost/gfw/engine/engine.go deleted file mode 100644 index 1270efb..0000000 --- a/go-gost/gfw/engine/engine.go +++ /dev/null @@ -1,125 +0,0 @@ -package engine - -import ( - "context" - "runtime" - - "github.com/apernet/OpenGFW/io" - "github.com/apernet/OpenGFW/ruleset" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" -) - -var _ Engine = (*engine)(nil) - -type engine struct { - logger Logger - io io.PacketIO - workers []*worker -} - -func NewEngine(config Config) (Engine, error) { - workerCount := config.Workers - if workerCount <= 0 { - workerCount = runtime.NumCPU() - } - var err error - workers := make([]*worker, workerCount) - for i := range workers { - workers[i], err = newWorker(workerConfig{ - ID: i, - ChanSize: config.WorkerQueueSize, - Logger: config.Logger, - Ruleset: config.Ruleset, - TCPMaxBufferedPagesTotal: config.WorkerTCPMaxBufferedPagesTotal, - TCPMaxBufferedPagesPerConn: config.WorkerTCPMaxBufferedPagesPerConn, - TCPTimeout: config.WorkerTCPTimeout, - UDPMaxStreams: config.WorkerUDPMaxStreams, - }) - if err != nil { - return nil, err - } - } - return &engine{ - logger: config.Logger, - io: config.IO, - workers: workers, - }, nil -} - -func (e *engine) UpdateRuleset(r ruleset.Ruleset) error { - for _, w := range e.workers { - if err := w.UpdateRuleset(r); err != nil { - return err - } - } - return nil -} - -func (e *engine) Run(ctx context.Context) error { - workerCtx, workerCancel := context.WithCancel(ctx) - defer workerCancel() // Stop workers - - // Register IO shutdown - ioCtx, ioCancel := context.WithCancel(ctx) - e.io.SetCancelFunc(ioCancel) - defer ioCancel() // Stop IO - - // Start workers - for _, w := range e.workers { - go w.Run(workerCtx) - } - - // Register IO callback - errChan := make(chan error, 1) - err := e.io.Register(ioCtx, func(p io.Packet, err error) bool { - if err != nil { - errChan <- err - return false - } - return e.dispatch(p) - }) - if err != nil { - return err - } - - // Block until IO errors or context is cancelled - select { - case err := <-errChan: - return err - case <-ctx.Done(): - return nil - case <-ioCtx.Done(): - return nil - } -} - -// dispatch dispatches a packet to a worker. -func (e *engine) dispatch(p io.Packet) bool { - data := p.Data() - ipVersion := data[0] >> 4 - var layerType gopacket.LayerType - if ipVersion == 4 { - layerType = layers.LayerTypeIPv4 - } else if ipVersion == 6 { - layerType = layers.LayerTypeIPv6 - } else { - // Unsupported network layer - _ = e.io.SetVerdict(p, io.VerdictAcceptStream, nil) - return true - } - // Convert to gopacket.Packet - packet := gopacket.NewPacket(data, layerType, gopacket.DecodeOptions{Lazy: true, NoCopy: true}) - packet.Metadata().Timestamp = p.Timestamp() - // Load balance by stream ID - index := p.StreamID() % uint32(len(e.workers)) - e.workers[index].Feed(&workerPacket{ - StreamID: p.StreamID(), - Packet: packet, - SetVerdict: func(v io.Verdict, b []byte) error { - return e.io.SetVerdict(p, v, b) - }, - }) - return true -} diff --git a/go-gost/gfw/engine/interface.go b/go-gost/gfw/engine/interface.go deleted file mode 100644 index af106f4..0000000 --- a/go-gost/gfw/engine/interface.go +++ /dev/null @@ -1,52 +0,0 @@ -package engine - -import ( - "context" - "time" - - "github.com/apernet/OpenGFW/io" - "github.com/apernet/OpenGFW/ruleset" -) - -// Engine is the main engine for OpenGFW. -type Engine interface { - // UpdateRuleset updates the ruleset. - UpdateRuleset(ruleset.Ruleset) error - // Run runs the engine, until an error occurs or the context is cancelled. - Run(context.Context) error -} - -// Config is the configuration for the engine. -type Config struct { - Logger Logger - IO io.PacketIO - Ruleset ruleset.Ruleset - - Workers int // Number of workers. Zero or negative means auto (number of CPU cores). - WorkerQueueSize int - WorkerTCPMaxBufferedPagesTotal int - WorkerTCPMaxBufferedPagesPerConn int - WorkerTCPTimeout time.Duration - WorkerUDPMaxStreams int -} - -// Logger is the combined logging interface for the engine, workers and analyzers. -type Logger interface { - WorkerStart(id int) - WorkerStop(id int) - - TCPStreamNew(workerID int, info ruleset.StreamInfo) - TCPStreamPropUpdate(info ruleset.StreamInfo, close bool) - TCPStreamAction(info ruleset.StreamInfo, action ruleset.Action, noMatch bool) - TCPFlush(workerID, flushed, closed int) - - UDPStreamNew(workerID int, info ruleset.StreamInfo) - UDPStreamPropUpdate(info ruleset.StreamInfo, close bool) - UDPStreamAction(info ruleset.StreamInfo, action ruleset.Action, noMatch bool) - - ModifyError(info ruleset.StreamInfo, err error) - - AnalyzerDebugf(streamID int64, name string, format string, args ...interface{}) - AnalyzerInfof(streamID int64, name string, format string, args ...interface{}) - AnalyzerErrorf(streamID int64, name string, format string, args ...interface{}) -} diff --git a/go-gost/gfw/engine/tcp.go b/go-gost/gfw/engine/tcp.go deleted file mode 100644 index 1874055..0000000 --- a/go-gost/gfw/engine/tcp.go +++ /dev/null @@ -1,229 +0,0 @@ -package engine - -import ( - "net" - "sync" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/io" - "github.com/apernet/OpenGFW/ruleset" - - "github.com/bwmarrin/snowflake" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/reassembly" -) - -// tcpVerdict is a subset of io.Verdict for TCP streams. -// We don't allow modifying or dropping a single packet -// for TCP streams for now, as it doesn't make much sense. -type tcpVerdict io.Verdict - -const ( - tcpVerdictAccept = tcpVerdict(io.VerdictAccept) - tcpVerdictAcceptStream = tcpVerdict(io.VerdictAcceptStream) - tcpVerdictDropStream = tcpVerdict(io.VerdictDropStream) -) - -type tcpContext struct { - *gopacket.PacketMetadata - Verdict tcpVerdict -} - -func (ctx *tcpContext) GetCaptureInfo() gopacket.CaptureInfo { - return ctx.CaptureInfo -} - -type tcpStreamFactory struct { - WorkerID int - Logger Logger - Node *snowflake.Node - - RulesetMutex sync.RWMutex - Ruleset ruleset.Ruleset -} - -func (f *tcpStreamFactory) New(ipFlow, tcpFlow gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream { - id := f.Node.Generate() - ipSrc, ipDst := net.IP(ipFlow.Src().Raw()), net.IP(ipFlow.Dst().Raw()) - info := ruleset.StreamInfo{ - ID: id.Int64(), - Protocol: ruleset.ProtocolTCP, - SrcIP: ipSrc, - DstIP: ipDst, - SrcPort: uint16(tcp.SrcPort), - DstPort: uint16(tcp.DstPort), - Props: make(analyzer.CombinedPropMap), - } - f.Logger.TCPStreamNew(f.WorkerID, info) - f.RulesetMutex.RLock() - rs := f.Ruleset - f.RulesetMutex.RUnlock() - ans := analyzersToTCPAnalyzers(rs.Analyzers(info)) - // Create entries for each analyzer - entries := make([]*tcpStreamEntry, 0, len(ans)) - for _, a := range ans { - entries = append(entries, &tcpStreamEntry{ - Name: a.Name(), - Stream: a.NewTCP(analyzer.TCPInfo{ - SrcIP: ipSrc, - DstIP: ipDst, - SrcPort: uint16(tcp.SrcPort), - DstPort: uint16(tcp.DstPort), - }, &analyzerLogger{ - StreamID: id.Int64(), - Name: a.Name(), - Logger: f.Logger, - }), - HasLimit: a.Limit() > 0, - Quota: a.Limit(), - }) - } - return &tcpStream{ - info: info, - virgin: true, - logger: f.Logger, - ruleset: rs, - activeEntries: entries, - } -} - -func (f *tcpStreamFactory) UpdateRuleset(r ruleset.Ruleset) error { - f.RulesetMutex.Lock() - defer f.RulesetMutex.Unlock() - f.Ruleset = r - return nil -} - -type tcpStream struct { - info ruleset.StreamInfo - virgin bool // true if no packets have been processed - logger Logger - ruleset ruleset.Ruleset - activeEntries []*tcpStreamEntry - doneEntries []*tcpStreamEntry - lastVerdict tcpVerdict -} - -type tcpStreamEntry struct { - Name string - Stream analyzer.TCPStream - HasLimit bool - Quota int -} - -func (s *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool { - if len(s.activeEntries) > 0 || s.virgin { - // Make sure every stream matches against the ruleset at least once, - // even if there are no activeEntries, as the ruleset may have built-in - // properties that need to be matched. - return true - } else { - ctx := ac.(*tcpContext) - ctx.Verdict = s.lastVerdict - return false - } -} - -func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) { - dir, start, end, skip := sg.Info() - rev := dir == reassembly.TCPDirServerToClient - avail, _ := sg.Lengths() - data := sg.Fetch(avail) - updated := false - for i := len(s.activeEntries) - 1; i >= 0; i-- { - // Important: reverse order so we can remove entries - entry := s.activeEntries[i] - update, closeUpdate, done := s.feedEntry(entry, rev, start, end, skip, data) - up1 := processPropUpdate(s.info.Props, entry.Name, update) - up2 := processPropUpdate(s.info.Props, entry.Name, closeUpdate) - updated = updated || up1 || up2 - if done { - s.activeEntries = append(s.activeEntries[:i], s.activeEntries[i+1:]...) - s.doneEntries = append(s.doneEntries, entry) - } - } - ctx := ac.(*tcpContext) - if updated || s.virgin { - s.virgin = false - s.logger.TCPStreamPropUpdate(s.info, false) - // Match properties against ruleset - result := s.ruleset.Match(s.info) - action := result.Action - if action != ruleset.ActionMaybe && action != ruleset.ActionModify { - verdict := actionToTCPVerdict(action) - s.lastVerdict = verdict - ctx.Verdict = verdict - s.logger.TCPStreamAction(s.info, action, false) - // Verdict issued, no need to process any more packets - s.closeActiveEntries() - } - } - if len(s.activeEntries) == 0 && ctx.Verdict == tcpVerdictAccept { - // All entries are done but no verdict issued, accept stream - s.lastVerdict = tcpVerdictAcceptStream - ctx.Verdict = tcpVerdictAcceptStream - s.logger.TCPStreamAction(s.info, ruleset.ActionAllow, true) - } -} - -func (s *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { - s.closeActiveEntries() - return true -} - -func (s *tcpStream) closeActiveEntries() { - // Signal close to all active entries & move them to doneEntries - updated := false - for _, entry := range s.activeEntries { - update := entry.Stream.Close(false) - up := processPropUpdate(s.info.Props, entry.Name, update) - updated = updated || up - } - if updated { - s.logger.TCPStreamPropUpdate(s.info, true) - } - s.doneEntries = append(s.doneEntries, s.activeEntries...) - s.activeEntries = nil -} - -func (s *tcpStream) feedEntry(entry *tcpStreamEntry, rev, start, end bool, skip int, data []byte) (update *analyzer.PropUpdate, closeUpdate *analyzer.PropUpdate, done bool) { - if !entry.HasLimit { - update, done = entry.Stream.Feed(rev, start, end, skip, data) - } else { - qData := data - if len(qData) > entry.Quota { - qData = qData[:entry.Quota] - } - update, done = entry.Stream.Feed(rev, start, end, skip, qData) - entry.Quota -= len(qData) - if entry.Quota <= 0 { - // Quota exhausted, signal close & move to doneEntries - closeUpdate = entry.Stream.Close(true) - done = true - } - } - return -} - -func analyzersToTCPAnalyzers(ans []analyzer.Analyzer) []analyzer.TCPAnalyzer { - tcpAns := make([]analyzer.TCPAnalyzer, 0, len(ans)) - for _, a := range ans { - if tcpM, ok := a.(analyzer.TCPAnalyzer); ok { - tcpAns = append(tcpAns, tcpM) - } - } - return tcpAns -} - -func actionToTCPVerdict(a ruleset.Action) tcpVerdict { - switch a { - case ruleset.ActionMaybe, ruleset.ActionAllow, ruleset.ActionModify: - return tcpVerdictAcceptStream - case ruleset.ActionBlock, ruleset.ActionDrop: - return tcpVerdictDropStream - default: - // Should never happen - return tcpVerdictAcceptStream - } -} diff --git a/go-gost/gfw/engine/udp.go b/go-gost/gfw/engine/udp.go deleted file mode 100644 index fe1f07c..0000000 --- a/go-gost/gfw/engine/udp.go +++ /dev/null @@ -1,299 +0,0 @@ -package engine - -import ( - "errors" - "net" - "sync" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/io" - "github.com/apernet/OpenGFW/modifier" - "github.com/apernet/OpenGFW/ruleset" - - "github.com/bwmarrin/snowflake" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - lru "github.com/hashicorp/golang-lru/v2" -) - -// udpVerdict is a subset of io.Verdict for UDP streams. -// For UDP, we support all verdicts. -type udpVerdict io.Verdict - -const ( - udpVerdictAccept = udpVerdict(io.VerdictAccept) - udpVerdictAcceptModify = udpVerdict(io.VerdictAcceptModify) - udpVerdictAcceptStream = udpVerdict(io.VerdictAcceptStream) - udpVerdictDrop = udpVerdict(io.VerdictDrop) - udpVerdictDropStream = udpVerdict(io.VerdictDropStream) -) - -var errInvalidModifier = errors.New("invalid modifier") - -type udpContext struct { - Verdict udpVerdict - Packet []byte -} - -type udpStreamFactory struct { - WorkerID int - Logger Logger - Node *snowflake.Node - - RulesetMutex sync.RWMutex - Ruleset ruleset.Ruleset -} - -func (f *udpStreamFactory) New(ipFlow, udpFlow gopacket.Flow, udp *layers.UDP, uc *udpContext) *udpStream { - id := f.Node.Generate() - ipSrc, ipDst := net.IP(ipFlow.Src().Raw()), net.IP(ipFlow.Dst().Raw()) - info := ruleset.StreamInfo{ - ID: id.Int64(), - Protocol: ruleset.ProtocolUDP, - SrcIP: ipSrc, - DstIP: ipDst, - SrcPort: uint16(udp.SrcPort), - DstPort: uint16(udp.DstPort), - Props: make(analyzer.CombinedPropMap), - } - f.Logger.UDPStreamNew(f.WorkerID, info) - f.RulesetMutex.RLock() - rs := f.Ruleset - f.RulesetMutex.RUnlock() - ans := analyzersToUDPAnalyzers(rs.Analyzers(info)) - // Create entries for each analyzer - entries := make([]*udpStreamEntry, 0, len(ans)) - for _, a := range ans { - entries = append(entries, &udpStreamEntry{ - Name: a.Name(), - Stream: a.NewUDP(analyzer.UDPInfo{ - SrcIP: ipSrc, - DstIP: ipDst, - SrcPort: uint16(udp.SrcPort), - DstPort: uint16(udp.DstPort), - }, &analyzerLogger{ - StreamID: id.Int64(), - Name: a.Name(), - Logger: f.Logger, - }), - HasLimit: a.Limit() > 0, - Quota: a.Limit(), - }) - } - return &udpStream{ - info: info, - virgin: true, - logger: f.Logger, - ruleset: rs, - activeEntries: entries, - } -} - -func (f *udpStreamFactory) UpdateRuleset(r ruleset.Ruleset) error { - f.RulesetMutex.Lock() - defer f.RulesetMutex.Unlock() - f.Ruleset = r - return nil -} - -type udpStreamManager struct { - factory *udpStreamFactory - streams *lru.Cache[uint32, *udpStreamValue] -} - -type udpStreamValue struct { - Stream *udpStream - IPFlow gopacket.Flow - UDPFlow gopacket.Flow -} - -func (v *udpStreamValue) Match(ipFlow, udpFlow gopacket.Flow) (ok, rev bool) { - fwd := v.IPFlow == ipFlow && v.UDPFlow == udpFlow - rev = v.IPFlow == ipFlow.Reverse() && v.UDPFlow == udpFlow.Reverse() - return fwd || rev, rev -} - -func newUDPStreamManager(factory *udpStreamFactory, maxStreams int) (*udpStreamManager, error) { - ss, err := lru.New[uint32, *udpStreamValue](maxStreams) - if err != nil { - return nil, err - } - return &udpStreamManager{ - factory: factory, - streams: ss, - }, nil -} - -func (m *udpStreamManager) MatchWithContext(streamID uint32, ipFlow gopacket.Flow, udp *layers.UDP, uc *udpContext) { - rev := false - value, ok := m.streams.Get(streamID) - if !ok { - // New stream - value = &udpStreamValue{ - Stream: m.factory.New(ipFlow, udp.TransportFlow(), udp, uc), - IPFlow: ipFlow, - UDPFlow: udp.TransportFlow(), - } - m.streams.Add(streamID, value) - } else { - // Stream ID exists, but is it really the same stream? - ok, rev = value.Match(ipFlow, udp.TransportFlow()) - if !ok { - // It's not - close the old stream & replace it with a new one - value.Stream.Close() - value = &udpStreamValue{ - Stream: m.factory.New(ipFlow, udp.TransportFlow(), udp, uc), - IPFlow: ipFlow, - UDPFlow: udp.TransportFlow(), - } - m.streams.Add(streamID, value) - } - } - if value.Stream.Accept(udp, rev, uc) { - value.Stream.Feed(udp, rev, uc) - } -} - -type udpStream struct { - info ruleset.StreamInfo - virgin bool // true if no packets have been processed - logger Logger - ruleset ruleset.Ruleset - activeEntries []*udpStreamEntry - doneEntries []*udpStreamEntry - lastVerdict udpVerdict -} - -type udpStreamEntry struct { - Name string - Stream analyzer.UDPStream - HasLimit bool - Quota int -} - -func (s *udpStream) Accept(udp *layers.UDP, rev bool, uc *udpContext) bool { - if len(s.activeEntries) > 0 || s.virgin { - // Make sure every stream matches against the ruleset at least once, - // even if there are no activeEntries, as the ruleset may have built-in - // properties that need to be matched. - return true - } else { - uc.Verdict = s.lastVerdict - return false - } -} - -func (s *udpStream) Feed(udp *layers.UDP, rev bool, uc *udpContext) { - updated := false - for i := len(s.activeEntries) - 1; i >= 0; i-- { - // Important: reverse order so we can remove entries - entry := s.activeEntries[i] - update, closeUpdate, done := s.feedEntry(entry, rev, udp.Payload) - up1 := processPropUpdate(s.info.Props, entry.Name, update) - up2 := processPropUpdate(s.info.Props, entry.Name, closeUpdate) - updated = updated || up1 || up2 - if done { - s.activeEntries = append(s.activeEntries[:i], s.activeEntries[i+1:]...) - s.doneEntries = append(s.doneEntries, entry) - } - } - if updated || s.virgin { - s.virgin = false - s.logger.UDPStreamPropUpdate(s.info, false) - // Match properties against ruleset - result := s.ruleset.Match(s.info) - action := result.Action - if action == ruleset.ActionModify { - // Call the modifier instance - udpMI, ok := result.ModInstance.(modifier.UDPModifierInstance) - if !ok { - // Not for UDP, fallback to maybe - s.logger.ModifyError(s.info, errInvalidModifier) - action = ruleset.ActionMaybe - } else { - var err error - uc.Packet, err = udpMI.Process(udp.Payload) - if err != nil { - // Modifier error, fallback to maybe - s.logger.ModifyError(s.info, err) - action = ruleset.ActionMaybe - } - } - } - if action != ruleset.ActionMaybe { - verdict, final := actionToUDPVerdict(action) - s.lastVerdict = verdict - uc.Verdict = verdict - s.logger.UDPStreamAction(s.info, action, false) - if final { - s.closeActiveEntries() - } - } - } - if len(s.activeEntries) == 0 && uc.Verdict == udpVerdictAccept { - // All entries are done but no verdict issued, accept stream - s.lastVerdict = udpVerdictAcceptStream - uc.Verdict = udpVerdictAcceptStream - s.logger.UDPStreamAction(s.info, ruleset.ActionAllow, true) - } -} - -func (s *udpStream) Close() { - s.closeActiveEntries() -} - -func (s *udpStream) closeActiveEntries() { - // Signal close to all active entries & move them to doneEntries - updated := false - for _, entry := range s.activeEntries { - update := entry.Stream.Close(false) - up := processPropUpdate(s.info.Props, entry.Name, update) - updated = updated || up - } - if updated { - s.logger.UDPStreamPropUpdate(s.info, true) - } - s.doneEntries = append(s.doneEntries, s.activeEntries...) - s.activeEntries = nil -} - -func (s *udpStream) feedEntry(entry *udpStreamEntry, rev bool, data []byte) (update *analyzer.PropUpdate, closeUpdate *analyzer.PropUpdate, done bool) { - update, done = entry.Stream.Feed(rev, data) - if entry.HasLimit { - entry.Quota -= len(data) - if entry.Quota <= 0 { - // Quota exhausted, signal close & move to doneEntries - closeUpdate = entry.Stream.Close(true) - done = true - } - } - return -} - -func analyzersToUDPAnalyzers(ans []analyzer.Analyzer) []analyzer.UDPAnalyzer { - udpAns := make([]analyzer.UDPAnalyzer, 0, len(ans)) - for _, a := range ans { - if udpM, ok := a.(analyzer.UDPAnalyzer); ok { - udpAns = append(udpAns, udpM) - } - } - return udpAns -} - -func actionToUDPVerdict(a ruleset.Action) (v udpVerdict, final bool) { - switch a { - case ruleset.ActionMaybe: - return udpVerdictAccept, false - case ruleset.ActionAllow: - return udpVerdictAcceptStream, true - case ruleset.ActionBlock: - return udpVerdictDropStream, true - case ruleset.ActionDrop: - return udpVerdictDrop, false - case ruleset.ActionModify: - return udpVerdictAcceptModify, false - default: - // Should never happen - return udpVerdictAccept, false - } -} diff --git a/go-gost/gfw/engine/utils.go b/go-gost/gfw/engine/utils.go deleted file mode 100644 index 9b86ade..0000000 --- a/go-gost/gfw/engine/utils.go +++ /dev/null @@ -1,50 +0,0 @@ -package engine - -import "github.com/apernet/OpenGFW/analyzer" - -var _ analyzer.Logger = (*analyzerLogger)(nil) - -type analyzerLogger struct { - StreamID int64 - Name string - Logger Logger -} - -func (l *analyzerLogger) Debugf(format string, args ...interface{}) { - l.Logger.AnalyzerDebugf(l.StreamID, l.Name, format, args...) -} - -func (l *analyzerLogger) Infof(format string, args ...interface{}) { - l.Logger.AnalyzerInfof(l.StreamID, l.Name, format, args...) -} - -func (l *analyzerLogger) Errorf(format string, args ...interface{}) { - l.Logger.AnalyzerErrorf(l.StreamID, l.Name, format, args...) -} - -func processPropUpdate(cpm analyzer.CombinedPropMap, name string, update *analyzer.PropUpdate) (updated bool) { - if update == nil || update.Type == analyzer.PropUpdateNone { - return false - } - switch update.Type { - case analyzer.PropUpdateMerge: - m := cpm[name] - if m == nil { - m = make(analyzer.PropMap, len(update.M)) - cpm[name] = m - } - for k, v := range update.M { - m[k] = v - } - return true - case analyzer.PropUpdateReplace: - cpm[name] = update.M - return true - case analyzer.PropUpdateDelete: - delete(cpm, name) - return true - default: - // Invalid update type, ignore for now - return false - } -} diff --git a/go-gost/gfw/engine/worker.go b/go-gost/gfw/engine/worker.go deleted file mode 100644 index 343bc57..0000000 --- a/go-gost/gfw/engine/worker.go +++ /dev/null @@ -1,206 +0,0 @@ -package engine - -import ( - "context" - "time" - - "github.com/apernet/OpenGFW/io" - "github.com/apernet/OpenGFW/ruleset" - - "github.com/bwmarrin/snowflake" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/reassembly" -) - -const ( - defaultChanSize = 64 - defaultTCPMaxBufferedPagesTotal = 65536 - defaultTCPMaxBufferedPagesPerConnection = 16 - defaultTCPTimeout = 10 * time.Minute - defaultUDPMaxStreams = 4096 - - tcpFlushInterval = 1 * time.Minute -) - -type workerPacket struct { - StreamID uint32 - Packet gopacket.Packet - SetVerdict func(io.Verdict, []byte) error -} - -type worker struct { - id int - packetChan chan *workerPacket - logger Logger - - tcpStreamFactory *tcpStreamFactory - tcpStreamPool *reassembly.StreamPool - tcpAssembler *reassembly.Assembler - tcpTimeout time.Duration - - udpStreamFactory *udpStreamFactory - udpStreamManager *udpStreamManager - - modSerializeBuffer gopacket.SerializeBuffer -} - -type workerConfig struct { - ID int - ChanSize int - Logger Logger - Ruleset ruleset.Ruleset - TCPMaxBufferedPagesTotal int - TCPMaxBufferedPagesPerConn int - TCPTimeout time.Duration - UDPMaxStreams int -} - -func (c *workerConfig) fillDefaults() { - if c.ChanSize <= 0 { - c.ChanSize = defaultChanSize - } - if c.TCPMaxBufferedPagesTotal <= 0 { - c.TCPMaxBufferedPagesTotal = defaultTCPMaxBufferedPagesTotal - } - if c.TCPMaxBufferedPagesPerConn <= 0 { - c.TCPMaxBufferedPagesPerConn = defaultTCPMaxBufferedPagesPerConnection - } - if c.TCPTimeout <= 0 { - c.TCPTimeout = defaultTCPTimeout - } - if c.UDPMaxStreams <= 0 { - c.UDPMaxStreams = defaultUDPMaxStreams - } -} - -func newWorker(config workerConfig) (*worker, error) { - config.fillDefaults() - sfNode, err := snowflake.NewNode(int64(config.ID)) - if err != nil { - return nil, err - } - tcpSF := &tcpStreamFactory{ - WorkerID: config.ID, - Logger: config.Logger, - Node: sfNode, - Ruleset: config.Ruleset, - } - tcpStreamPool := reassembly.NewStreamPool(tcpSF) - tcpAssembler := reassembly.NewAssembler(tcpStreamPool) - tcpAssembler.MaxBufferedPagesTotal = config.TCPMaxBufferedPagesTotal - tcpAssembler.MaxBufferedPagesPerConnection = config.TCPMaxBufferedPagesPerConn - udpSF := &udpStreamFactory{ - WorkerID: config.ID, - Logger: config.Logger, - Node: sfNode, - Ruleset: config.Ruleset, - } - udpSM, err := newUDPStreamManager(udpSF, config.UDPMaxStreams) - if err != nil { - return nil, err - } - return &worker{ - id: config.ID, - packetChan: make(chan *workerPacket, config.ChanSize), - logger: config.Logger, - tcpStreamFactory: tcpSF, - tcpStreamPool: tcpStreamPool, - tcpAssembler: tcpAssembler, - tcpTimeout: config.TCPTimeout, - udpStreamFactory: udpSF, - udpStreamManager: udpSM, - modSerializeBuffer: gopacket.NewSerializeBuffer(), - }, nil -} - -func (w *worker) Feed(p *workerPacket) { - w.packetChan <- p -} - -func (w *worker) Run(ctx context.Context) { - w.logger.WorkerStart(w.id) - defer w.logger.WorkerStop(w.id) - - tcpFlushTicker := time.NewTicker(tcpFlushInterval) - defer tcpFlushTicker.Stop() - - for { - select { - case <-ctx.Done(): - return - case wPkt := <-w.packetChan: - if wPkt == nil { - // Closed - return - } - v, b := w.handle(wPkt.StreamID, wPkt.Packet) - _ = wPkt.SetVerdict(v, b) - case <-tcpFlushTicker.C: - w.flushTCP(w.tcpTimeout) - } - } -} - -func (w *worker) UpdateRuleset(r ruleset.Ruleset) error { - if err := w.tcpStreamFactory.UpdateRuleset(r); err != nil { - return err - } - return w.udpStreamFactory.UpdateRuleset(r) -} - -func (w *worker) handle(streamID uint32, p gopacket.Packet) (io.Verdict, []byte) { - netLayer, trLayer := p.NetworkLayer(), p.TransportLayer() - if netLayer == nil || trLayer == nil { - // Invalid packet - return io.VerdictAccept, nil - } - ipFlow := netLayer.NetworkFlow() - switch tr := trLayer.(type) { - case *layers.TCP: - return w.handleTCP(ipFlow, p.Metadata(), tr), nil - case *layers.UDP: - v, modPayload := w.handleUDP(streamID, ipFlow, tr) - if v == io.VerdictAcceptModify && modPayload != nil { - tr.Payload = modPayload - _ = tr.SetNetworkLayerForChecksum(netLayer) - _ = w.modSerializeBuffer.Clear() - err := gopacket.SerializePacket(w.modSerializeBuffer, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, p) - if err != nil { - // Just accept without modification for now - return io.VerdictAccept, nil - } - return v, w.modSerializeBuffer.Bytes() - } - return v, nil - default: - // Unsupported protocol - return io.VerdictAccept, nil - } -} - -func (w *worker) handleTCP(ipFlow gopacket.Flow, pMeta *gopacket.PacketMetadata, tcp *layers.TCP) io.Verdict { - ctx := &tcpContext{ - PacketMetadata: pMeta, - Verdict: tcpVerdictAccept, - } - w.tcpAssembler.AssembleWithContext(ipFlow, tcp, ctx) - return io.Verdict(ctx.Verdict) -} - -func (w *worker) flushTCP(timeout time.Duration) { - flushed, closed := w.tcpAssembler.FlushCloseOlderThan(time.Now().Add(-timeout)) - w.logger.TCPFlush(w.id, flushed, closed) -} - -func (w *worker) handleUDP(streamID uint32, ipFlow gopacket.Flow, udp *layers.UDP) (io.Verdict, []byte) { - ctx := &udpContext{ - Verdict: udpVerdictAccept, - } - w.udpStreamManager.MatchWithContext(streamID, ipFlow, udp, ctx) - return io.Verdict(ctx.Verdict), ctx.Packet -} diff --git a/go-gost/gfw/go.mod b/go-gost/gfw/go.mod deleted file mode 100644 index 087447e..0000000 --- a/go-gost/gfw/go.mod +++ /dev/null @@ -1,47 +0,0 @@ -module github.com/apernet/OpenGFW - -go 1.21 - -require ( - github.com/bwmarrin/snowflake v0.3.0 - github.com/coreos/go-iptables v0.7.0 - github.com/expr-lang/expr v1.16.3 - github.com/florianl/go-nfqueue v1.3.2-0.20231218173729-f2bdeb033acf - github.com/google/gopacket v1.1.20-0.20220810144506-32ee38206866 - github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/mdlayher/netlink v1.6.0 - github.com/quic-go/quic-go v0.41.0 - github.com/shirou/gopsutil/v3 v3.24.1 - github.com/spf13/cobra v1.8.0 - github.com/spf13/viper v1.18.2 - go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.19.0 - golang.org/x/sys v0.17.0 - google.golang.org/protobuf v1.31.0 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mdlayher/socket v0.1.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/text v0.14.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect -) diff --git a/go-gost/gfw/go.sum b/go-gost/gfw/go.sum deleted file mode 100644 index 6906f9d..0000000 --- a/go-gost/gfw/go.sum +++ /dev/null @@ -1,169 +0,0 @@ -github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= -github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= -github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= -github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/expr-lang/expr v1.16.3 h1:NLldf786GffptcXNxxJx5dQ+FzeWDKChBDqOOwyK8to= -github.com/expr-lang/expr v1.16.3/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= -github.com/florianl/go-nfqueue v1.3.2-0.20231218173729-f2bdeb033acf h1:NqGS3vTHzVENbIfd87cXZwdpO6MB2R1PjHMJLi4Z3ow= -github.com/florianl/go-nfqueue v1.3.2-0.20231218173729-f2bdeb033acf/go.mod h1:eSnAor2YCfMCVYrVNEhkLGN/r1L+J4uDjc0EUy0tfq4= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gopacket v1.1.20-0.20220810144506-32ee38206866 h1:NaJi58bCZZh0jjPw78EqDZekPEfhlzYE01C5R+zh1tE= -github.com/google/gopacket v1.1.20-0.20220810144506-32ee38206866/go.mod h1:riddUzxTSBpJXk3qBHtYr4qOhFhT6k/1c0E3qkQjQpA= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= -github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= -github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI= -github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k= -github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go-gost/gfw/io/interface.go b/go-gost/gfw/io/interface.go deleted file mode 100644 index f996789..0000000 --- a/go-gost/gfw/io/interface.go +++ /dev/null @@ -1,62 +0,0 @@ -package io - -import ( - "context" - "net" - "time" -) - -type Verdict int - -const ( - // VerdictAccept accepts the packet, but continues to process the stream. - VerdictAccept Verdict = iota - // VerdictAcceptModify is like VerdictAccept, but replaces the packet with a new one. - VerdictAcceptModify - // VerdictAcceptStream accepts the packet and stops processing the stream. - VerdictAcceptStream - // VerdictDrop drops the packet, but does not block the stream. - VerdictDrop - // VerdictDropStream drops the packet and blocks the stream. - VerdictDropStream -) - -// Packet represents an IP packet. -type Packet interface { - // StreamID is the ID of the stream the packet belongs to. - StreamID() uint32 - // Timestamp is the time the packet was received. - Timestamp() time.Time - // Data is the raw packet data, starting with the IP header. - Data() []byte -} - -// PacketCallback is called for each packet received. -// Return false to "unregister" and stop receiving packets. -type PacketCallback func(Packet, error) bool - -type PacketIO interface { - // Register registers a callback to be called for each packet received. - // The callback should be called in one or more separate goroutines, - // and stop when the context is cancelled. - Register(context.Context, PacketCallback) error - // SetVerdict sets the verdict for a packet. - SetVerdict(Packet, Verdict, []byte) error - // ProtectedDialContext is like net.DialContext, but the connection is "protected" - // in the sense that the packets sent/received through the connection must bypass - // the packet IO and not be processed by the callback. - ProtectedDialContext(ctx context.Context, network, address string) (net.Conn, error) - // Close closes the packet IO. - Close() error - // SetCancelFunc gives packet IO access to context cancel function, enabling it to - // trigger a shutdown - SetCancelFunc(cancelFunc context.CancelFunc) error -} - -type ErrInvalidPacket struct { - Err error -} - -func (e *ErrInvalidPacket) Error() string { - return "invalid packet: " + e.Err.Error() -} diff --git a/go-gost/gfw/io/nfqueue.go b/go-gost/gfw/io/nfqueue.go deleted file mode 100644 index 20f0c11..0000000 --- a/go-gost/gfw/io/nfqueue.go +++ /dev/null @@ -1,480 +0,0 @@ -package io - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "net" - "os/exec" - "strconv" - "strings" - "syscall" - "time" - - "github.com/coreos/go-iptables/iptables" - "github.com/florianl/go-nfqueue" - "github.com/mdlayher/netlink" - "golang.org/x/sys/unix" -) - -const ( - nfqueueDefaultQueueNum = 100 - nfqueueMaxPacketLen = 0xFFFF - nfqueueDefaultQueueSize = 128 - - nfqueueDefaultConnMarkAccept = 1001 - - nftFamily = "inet" - nftDefaultTable = "opengfw" -) - -func (n *nfqueuePacketIO) generateNftRules() (*nftTableSpec, error) { - if n.local && n.rst { - return nil, errors.New("tcp rst is not supported in local mode") - } - table := &nftTableSpec{ - Family: nftFamily, - Table: n.table, - } - table.Defines = append(table.Defines, fmt.Sprintf("define ACCEPT_CTMARK=%d", n.connMarkAccept)) - table.Defines = append(table.Defines, fmt.Sprintf("define DROP_CTMARK=%d", n.connMarkDrop)) - table.Defines = append(table.Defines, fmt.Sprintf("define QUEUE_NUM=%d", n.queueNum)) - if n.local { - table.Chains = []nftChainSpec{ - {Chain: "INPUT", Header: "type filter hook input priority filter; policy accept;"}, - {Chain: "OUTPUT", Header: "type filter hook output priority filter; policy accept;"}, - } - } else { - table.Chains = []nftChainSpec{ - {Chain: "FORWARD", Header: "type filter hook forward priority filter; policy accept;"}, - } - } - for i := range table.Chains { - c := &table.Chains[i] - c.Rules = append(c.Rules, "meta mark $ACCEPT_CTMARK ct mark set $ACCEPT_CTMARK") // Bypass protected connections - c.Rules = append(c.Rules, "ct mark $ACCEPT_CTMARK counter accept") - if n.rst { - c.Rules = append(c.Rules, "ip protocol tcp ct mark $DROP_CTMARK counter reject with tcp reset") - } - c.Rules = append(c.Rules, "ct mark $DROP_CTMARK counter drop") - c.Rules = append(c.Rules, "counter queue num $QUEUE_NUM bypass") - } - return table, nil -} - -func (n *nfqueuePacketIO) generateIptRules() ([]iptRule, error) { - if n.local && n.rst { - return nil, errors.New("tcp rst is not supported in local mode") - } - var chains []string - if n.local { - chains = []string{"INPUT", "OUTPUT"} - } else { - chains = []string{"FORWARD"} - } - rules := make([]iptRule, 0, 4*len(chains)) - for _, chain := range chains { - // Bypass protected connections - rules = append(rules, iptRule{"filter", chain, []string{"-m", "mark", "--mark", strconv.Itoa(n.connMarkAccept), "-j", "CONNMARK", "--set-mark", strconv.Itoa(n.connMarkAccept)}}) - rules = append(rules, iptRule{"filter", chain, []string{"-m", "connmark", "--mark", strconv.Itoa(n.connMarkAccept), "-j", "ACCEPT"}}) - if n.rst { - rules = append(rules, iptRule{"filter", chain, []string{"-p", "tcp", "-m", "connmark", "--mark", strconv.Itoa(n.connMarkDrop), "-j", "REJECT", "--reject-with", "tcp-reset"}}) - } - rules = append(rules, iptRule{"filter", chain, []string{"-m", "connmark", "--mark", strconv.Itoa(n.connMarkDrop), "-j", "DROP"}}) - rules = append(rules, iptRule{"filter", chain, []string{"-j", "NFQUEUE", "--queue-num", strconv.Itoa(n.queueNum), "--queue-bypass"}}) - } - - return rules, nil -} - -var _ PacketIO = (*nfqueuePacketIO)(nil) - -var errNotNFQueuePacket = errors.New("not an NFQueue packet") - -type nfqueuePacketIO struct { - n *nfqueue.Nfqueue - local bool - rst bool - rSet bool // whether the nftables/iptables rules have been set - queueNum int - table string // nftable name - connMarkAccept int - connMarkDrop int - - // iptables not nil = use iptables instead of nftables - ipt4 *iptables.IPTables - ipt6 *iptables.IPTables - - protectedDialer *net.Dialer -} - -type NFQueuePacketIOConfig struct { - QueueSize uint32 - QueueNum *uint16 - Table string - ConnMarkAccept uint32 - ConnMarkDrop uint32 - - ReadBuffer int - WriteBuffer int - Local bool - RST bool -} - -func NewNFQueuePacketIO(config NFQueuePacketIOConfig) (PacketIO, error) { - if config.QueueSize == 0 { - config.QueueSize = nfqueueDefaultQueueSize - } - if config.QueueNum == nil { - queueNum := uint16(nfqueueDefaultQueueNum) - config.QueueNum = &queueNum - } - if config.Table == "" { - config.Table = nftDefaultTable - } - if config.ConnMarkAccept == 0 { - config.ConnMarkAccept = nfqueueDefaultConnMarkAccept - } - if config.ConnMarkDrop == 0 { - config.ConnMarkDrop = config.ConnMarkAccept + 1 - if config.ConnMarkDrop == 0 { - // Overflow - config.ConnMarkDrop = 1 - } - } - if config.ConnMarkAccept == config.ConnMarkDrop { - return nil, errors.New("connMarkAccept and connMarkDrop cannot be the same") - } - var ipt4, ipt6 *iptables.IPTables - var err error - if nftCheck() != nil { - // We prefer nftables, but if it's not available, fall back to iptables - ipt4, err = iptables.NewWithProtocol(iptables.ProtocolIPv4) - if err != nil { - return nil, err - } - ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6) - if err != nil { - return nil, err - } - } - n, err := nfqueue.Open(&nfqueue.Config{ - NfQueue: *config.QueueNum, - MaxPacketLen: nfqueueMaxPacketLen, - MaxQueueLen: config.QueueSize, - Copymode: nfqueue.NfQnlCopyPacket, - Flags: nfqueue.NfQaCfgFlagConntrack, - }) - if err != nil { - return nil, err - } - if config.ReadBuffer > 0 { - err = n.Con.SetReadBuffer(config.ReadBuffer) - if err != nil { - _ = n.Close() - return nil, err - } - } - if config.WriteBuffer > 0 { - err = n.Con.SetWriteBuffer(config.WriteBuffer) - if err != nil { - _ = n.Close() - return nil, err - } - } - return &nfqueuePacketIO{ - n: n, - local: config.Local, - rst: config.RST, - queueNum: int(*config.QueueNum), - table: config.Table, - connMarkAccept: int(config.ConnMarkAccept), - connMarkDrop: int(config.ConnMarkDrop), - ipt4: ipt4, - ipt6: ipt6, - protectedDialer: &net.Dialer{ - Control: func(network, address string, c syscall.RawConn) error { - var err error - cErr := c.Control(func(fd uintptr) { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.ConnMarkAccept)) - }) - if cErr != nil { - return cErr - } - return err - }, - }, - }, nil -} - -func (n *nfqueuePacketIO) Register(ctx context.Context, cb PacketCallback) error { - err := n.n.RegisterWithErrorFunc(ctx, - func(a nfqueue.Attribute) int { - if ok, verdict := n.packetAttributeSanityCheck(a); !ok { - if a.PacketID != nil { - _ = n.n.SetVerdict(*a.PacketID, verdict) - } - return 0 - } - p := &nfqueuePacket{ - id: *a.PacketID, - streamID: ctIDFromCtBytes(*a.Ct), - data: *a.Payload, - } - // Use timestamp from attribute if available, otherwise use current time as fallback - if a.Timestamp != nil { - p.timestamp = *a.Timestamp - } else { - p.timestamp = time.Now() - } - return okBoolToInt(cb(p, nil)) - }, - func(e error) int { - if opErr := (*netlink.OpError)(nil); errors.As(e, &opErr) { - if errors.Is(opErr.Err, unix.ENOBUFS) { - // Kernel buffer temporarily full, ignore - return 0 - } - } - return okBoolToInt(cb(nil, e)) - }) - if err != nil { - return err - } - if !n.rSet { - if n.ipt4 != nil { - err = n.setupIpt(false) - } else { - err = n.setupNft(false) - } - if err != nil { - return err - } - n.rSet = true - } - return nil -} - -func (n *nfqueuePacketIO) packetAttributeSanityCheck(a nfqueue.Attribute) (ok bool, verdict int) { - if a.PacketID == nil { - // Re-inject to NFQUEUE is actually not possible in this condition - return false, -1 - } - if a.Payload == nil || len(*a.Payload) < 20 { - // 20 is the minimum possible size of an IP packet - return false, nfqueue.NfDrop - } - if a.Ct == nil { - // Multicast packets may not have a conntrack, but only appear in local mode - if n.local { - return false, nfqueue.NfAccept - } - return false, nfqueue.NfDrop - } - return true, -1 -} - -func (n *nfqueuePacketIO) SetVerdict(p Packet, v Verdict, newPacket []byte) error { - nP, ok := p.(*nfqueuePacket) - if !ok { - return &ErrInvalidPacket{Err: errNotNFQueuePacket} - } - switch v { - case VerdictAccept: - return n.n.SetVerdict(nP.id, nfqueue.NfAccept) - case VerdictAcceptModify: - return n.n.SetVerdictModPacket(nP.id, nfqueue.NfAccept, newPacket) - case VerdictAcceptStream: - return n.n.SetVerdictWithConnMark(nP.id, nfqueue.NfAccept, n.connMarkAccept) - case VerdictDrop: - return n.n.SetVerdict(nP.id, nfqueue.NfDrop) - case VerdictDropStream: - return n.n.SetVerdictWithConnMark(nP.id, nfqueue.NfDrop, n.connMarkDrop) - default: - // Invalid verdict, ignore for now - return nil - } -} - -func (n *nfqueuePacketIO) ProtectedDialContext(ctx context.Context, network, address string) (net.Conn, error) { - return n.protectedDialer.DialContext(ctx, network, address) -} - -func (n *nfqueuePacketIO) Close() error { - if n.rSet { - if n.ipt4 != nil { - _ = n.setupIpt(true) - } else { - _ = n.setupNft(true) - } - n.rSet = false - } - return n.n.Close() -} - -// nfqueue IO does not issue shutdown -func (n *nfqueuePacketIO) SetCancelFunc(cancelFunc context.CancelFunc) error { - return nil -} - -func (n *nfqueuePacketIO) setupNft(remove bool) error { - rules, err := n.generateNftRules() - if err != nil { - return err - } - rulesText := rules.String() - if remove { - err = nftDelete(nftFamily, n.table) - } else { - // Delete first to make sure no leftover rules - _ = nftDelete(nftFamily, n.table) - err = nftAdd(rulesText) - } - if err != nil { - return err - } - return nil -} - -func (n *nfqueuePacketIO) setupIpt(remove bool) error { - rules, err := n.generateIptRules() - if err != nil { - return err - } - if remove { - err = iptsBatchDeleteIfExists([]*iptables.IPTables{n.ipt4, n.ipt6}, rules) - } else { - err = iptsBatchAppendUnique([]*iptables.IPTables{n.ipt4, n.ipt6}, rules) - } - if err != nil { - return err - } - return nil -} - -var _ Packet = (*nfqueuePacket)(nil) - -type nfqueuePacket struct { - id uint32 - streamID uint32 - timestamp time.Time - data []byte -} - -func (p *nfqueuePacket) StreamID() uint32 { - return p.streamID -} - -func (p *nfqueuePacket) Timestamp() time.Time { - return p.timestamp -} - -func (p *nfqueuePacket) Data() []byte { - return p.data -} - -func okBoolToInt(ok bool) int { - if ok { - return 0 - } else { - return 1 - } -} - -func nftCheck() error { - _, err := exec.LookPath("nft") - if err != nil { - return err - } - return nil -} - -func nftAdd(input string) error { - cmd := exec.Command("nft", "-f", "-") - cmd.Stdin = strings.NewReader(input) - return cmd.Run() -} - -func nftDelete(family, table string) error { - cmd := exec.Command("nft", "delete", "table", family, table) - return cmd.Run() -} - -type nftTableSpec struct { - Defines []string - Family, Table string - Chains []nftChainSpec -} - -func (t *nftTableSpec) String() string { - chains := make([]string, 0, len(t.Chains)) - for _, c := range t.Chains { - chains = append(chains, c.String()) - } - - return fmt.Sprintf(` -%s - -table %s %s { -%s -} -`, strings.Join(t.Defines, "\n"), t.Family, t.Table, strings.Join(chains, "")) -} - -type nftChainSpec struct { - Chain string - Header string - Rules []string -} - -func (c *nftChainSpec) String() string { - return fmt.Sprintf(` - chain %s { - %s - %s - } -`, c.Chain, c.Header, strings.Join(c.Rules, "\n\x20\x20\x20\x20")) -} - -type iptRule struct { - Table, Chain string - RuleSpec []string -} - -func iptsBatchAppendUnique(ipts []*iptables.IPTables, rules []iptRule) error { - for _, r := range rules { - for _, ipt := range ipts { - err := ipt.AppendUnique(r.Table, r.Chain, r.RuleSpec...) - if err != nil { - return err - } - } - } - return nil -} - -func iptsBatchDeleteIfExists(ipts []*iptables.IPTables, rules []iptRule) error { - for _, r := range rules { - for _, ipt := range ipts { - err := ipt.DeleteIfExists(r.Table, r.Chain, r.RuleSpec...) - if err != nil { - return err - } - } - } - return nil -} - -func ctIDFromCtBytes(ct []byte) uint32 { - ctAttrs, err := netlink.UnmarshalAttributes(ct) - if err != nil { - return 0 - } - for _, attr := range ctAttrs { - if attr.Type == 12 { // CTA_ID - return binary.BigEndian.Uint32(attr.Data) - } - } - return 0 -} diff --git a/go-gost/gfw/io/pcap.go b/go-gost/gfw/io/pcap.go deleted file mode 100644 index 9801f9c..0000000 --- a/go-gost/gfw/io/pcap.go +++ /dev/null @@ -1,136 +0,0 @@ -package io - -import ( - "context" - "hash/crc32" - "io" - "net" - "os" - "sort" - "strings" - "time" - - "github.com/google/gopacket" - "github.com/google/gopacket/pcapgo" -) - -var _ PacketIO = (*pcapPacketIO)(nil) - -type pcapPacketIO struct { - pcapFile io.ReadCloser - pcap *pcapgo.Reader - timeOffset *time.Duration - ioCancel context.CancelFunc - config PcapPacketIOConfig - - dialer *net.Dialer -} - -type PcapPacketIOConfig struct { - PcapFile string - Realtime bool -} - -func NewPcapPacketIO(config PcapPacketIOConfig) (PacketIO, error) { - pcapFile, err := os.Open(config.PcapFile) - if err != nil { - return nil, err - } - - handle, err := pcapgo.NewReader(pcapFile) - if err != nil { - return nil, err - } - - return &pcapPacketIO{ - pcapFile: pcapFile, - pcap: handle, - timeOffset: nil, - ioCancel: nil, - config: config, - dialer: &net.Dialer{}, - }, nil -} - -func (p *pcapPacketIO) Register(ctx context.Context, cb PacketCallback) error { - go func() { - packetSource := gopacket.NewPacketSource(p.pcap, p.pcap.LinkType()) - for packet := range packetSource.Packets() { - p.wait(packet) - - networkLayer := packet.NetworkLayer() - if networkLayer != nil { - src, dst := networkLayer.NetworkFlow().Endpoints() - endpoints := []string{src.String(), dst.String()} - sort.Strings(endpoints) - id := crc32.Checksum([]byte(strings.Join(endpoints, ",")), crc32.IEEETable) - - cb(&pcapPacket{ - streamID: id, - timestamp: packet.Metadata().Timestamp, - data: packet.LinkLayer().LayerPayload(), - }, nil) - } - } - // Give the workers a chance to finish everything - time.Sleep(time.Second) - // Stop the engine when all packets are finished - p.ioCancel() - }() - - return nil -} - -// A normal dialer is sufficient as pcap IO does not mess up with the networking -func (p *pcapPacketIO) ProtectedDialContext(ctx context.Context, network, address string) (net.Conn, error) { - return p.dialer.DialContext(ctx, network, address) -} - -func (p *pcapPacketIO) SetVerdict(pkt Packet, v Verdict, newPacket []byte) error { - return nil -} - -func (p *pcapPacketIO) SetCancelFunc(cancelFunc context.CancelFunc) error { - p.ioCancel = cancelFunc - return nil -} - -func (p *pcapPacketIO) Close() error { - return p.pcapFile.Close() -} - -// Intentionally slow down the replay -// In realtime mode, this is to match the timestamps in the capture -func (p *pcapPacketIO) wait(packet gopacket.Packet) { - if !p.config.Realtime { - return - } - - if p.timeOffset == nil { - offset := time.Since(packet.Metadata().Timestamp) - p.timeOffset = &offset - } else { - t := time.Until(packet.Metadata().Timestamp.Add(*p.timeOffset)) - time.Sleep(t) - } -} - -var _ Packet = (*pcapPacket)(nil) - -type pcapPacket struct { - streamID uint32 - timestamp time.Time - data []byte -} - -func (p *pcapPacket) StreamID() uint32 { - return p.streamID -} - -func (p *pcapPacket) Timestamp() time.Time { - return p.timestamp -} - -func (p *pcapPacket) Data() []byte { - return p.data -} diff --git a/go-gost/gfw/modifier/interface.go b/go-gost/gfw/modifier/interface.go deleted file mode 100644 index 0340a87..0000000 --- a/go-gost/gfw/modifier/interface.go +++ /dev/null @@ -1,32 +0,0 @@ -package modifier - -type Modifier interface { - // Name returns the name of the modifier. - Name() string - // New returns a new modifier instance. - New(args map[string]interface{}) (Instance, error) -} - -type Instance interface{} - -type UDPModifierInstance interface { - Instance - // Process takes a UDP packet and returns a modified UDP packet. - Process(data []byte) ([]byte, error) -} - -type ErrInvalidPacket struct { - Err error -} - -func (e *ErrInvalidPacket) Error() string { - return "invalid packet: " + e.Err.Error() -} - -type ErrInvalidArgs struct { - Err error -} - -func (e *ErrInvalidArgs) Error() string { - return "invalid args: " + e.Err.Error() -} diff --git a/go-gost/gfw/modifier/udp/dns.go b/go-gost/gfw/modifier/udp/dns.go deleted file mode 100644 index afab276..0000000 --- a/go-gost/gfw/modifier/udp/dns.go +++ /dev/null @@ -1,96 +0,0 @@ -package udp - -import ( - "errors" - "net" - - "github.com/apernet/OpenGFW/modifier" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" -) - -var _ modifier.Modifier = (*DNSModifier)(nil) - -var ( - errInvalidIP = errors.New("invalid ip") - errNotValidDNSResponse = errors.New("not a valid dns response") - errEmptyDNSQuestion = errors.New("empty dns question") -) - -type DNSModifier struct{} - -func (m *DNSModifier) Name() string { - return "dns" -} - -func (m *DNSModifier) New(args map[string]interface{}) (modifier.Instance, error) { - i := &dnsModifierInstance{} - aStr, ok := args["a"].(string) - if ok { - a := net.ParseIP(aStr).To4() - if a == nil { - return nil, &modifier.ErrInvalidArgs{Err: errInvalidIP} - } - i.A = a - } - aaaaStr, ok := args["aaaa"].(string) - if ok { - aaaa := net.ParseIP(aaaaStr).To16() - if aaaa == nil { - return nil, &modifier.ErrInvalidArgs{Err: errInvalidIP} - } - i.AAAA = aaaa - } - return i, nil -} - -var _ modifier.UDPModifierInstance = (*dnsModifierInstance)(nil) - -type dnsModifierInstance struct { - A net.IP - AAAA net.IP -} - -func (i *dnsModifierInstance) Process(data []byte) ([]byte, error) { - dns := &layers.DNS{} - err := dns.DecodeFromBytes(data, gopacket.NilDecodeFeedback) - if err != nil { - return nil, &modifier.ErrInvalidPacket{Err: err} - } - if !dns.QR || dns.ResponseCode != layers.DNSResponseCodeNoErr { - return nil, &modifier.ErrInvalidPacket{Err: errNotValidDNSResponse} - } - if len(dns.Questions) == 0 { - return nil, &modifier.ErrInvalidPacket{Err: errEmptyDNSQuestion} - } - // In practice, most if not all DNS clients only send one question - // per packet, so we don't care about the rest for now. - q := dns.Questions[0] - switch q.Type { - case layers.DNSTypeA: - if i.A != nil { - dns.Answers = []layers.DNSResourceRecord{{ - Name: q.Name, - Type: layers.DNSTypeA, - Class: layers.DNSClassIN, - IP: i.A, - }} - } - case layers.DNSTypeAAAA: - if i.AAAA != nil { - dns.Answers = []layers.DNSResourceRecord{{ - Name: q.Name, - Type: layers.DNSTypeAAAA, - Class: layers.DNSClassIN, - IP: i.AAAA, - }} - } - } - buf := gopacket.NewSerializeBuffer() // Modifiers must be safe for concurrent use, so we can't reuse the buffer - err = gopacket.SerializeLayers(buf, gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, dns) - return buf.Bytes(), err -} diff --git a/go-gost/gfw/ruleset/builtins/cidr.go b/go-gost/gfw/ruleset/builtins/cidr.go deleted file mode 100644 index 669d469..0000000 --- a/go-gost/gfw/ruleset/builtins/cidr.go +++ /dev/null @@ -1,18 +0,0 @@ -package builtins - -import ( - "net" -) - -func MatchCIDR(ip string, cidr *net.IPNet) bool { - ipAddr := net.ParseIP(ip) - if ipAddr == nil { - return false - } - return cidr.Contains(ipAddr) -} - -func CompileCIDR(cidr string) (*net.IPNet, error) { - _, ipNet, err := net.ParseCIDR(cidr) - return ipNet, err -} diff --git a/go-gost/gfw/ruleset/builtins/geo/geo_loader.go b/go-gost/gfw/ruleset/builtins/geo/geo_loader.go deleted file mode 100644 index 8e16509..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/geo_loader.go +++ /dev/null @@ -1,128 +0,0 @@ -package geo - -import ( - "io" - "net/http" - "os" - "time" - - "github.com/apernet/OpenGFW/ruleset/builtins/geo/v2geo" -) - -const ( - geoipFilename = "geoip.dat" - geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat" - geositeFilename = "geosite.dat" - geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" - - geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days -) - -var _ GeoLoader = (*V2GeoLoader)(nil) - -// V2GeoLoader provides the on-demand GeoIP/MatchGeoSite database -// loading functionality required by the ACL engine. -// Empty filenames = automatic download from built-in URLs. -type V2GeoLoader struct { - GeoIPFilename string - GeoSiteFilename string - UpdateInterval time.Duration - - DownloadFunc func(filename, url string) - DownloadErrFunc func(err error) - - geoipMap map[string]*v2geo.GeoIP - geositeMap map[string]*v2geo.GeoSite -} - -func NewDefaultGeoLoader(geoSiteFilename, geoIpFilename string) *V2GeoLoader { - return &V2GeoLoader{ - GeoIPFilename: geoIpFilename, - GeoSiteFilename: geoSiteFilename, - DownloadFunc: func(filename, url string) {}, - DownloadErrFunc: func(err error) {}, - } -} - -func (l *V2GeoLoader) shouldDownload(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return true - } - dt := time.Since(info.ModTime()) - if l.UpdateInterval == 0 { - return dt > geoDefaultUpdateInterval - } else { - return dt > l.UpdateInterval - } -} - -func (l *V2GeoLoader) download(filename, url string) error { - l.DownloadFunc(filename, url) - - resp, err := http.Get(url) - if err != nil { - l.DownloadErrFunc(err) - return err - } - defer resp.Body.Close() - - f, err := os.Create(filename) - if err != nil { - l.DownloadErrFunc(err) - return err - } - defer f.Close() - - _, err = io.Copy(f, resp.Body) - l.DownloadErrFunc(err) - return err -} - -func (l *V2GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) { - if l.geoipMap != nil { - return l.geoipMap, nil - } - autoDL := false - filename := l.GeoIPFilename - if filename == "" { - autoDL = true - filename = geoipFilename - } - if autoDL && l.shouldDownload(filename) { - err := l.download(filename, geoipURL) - if err != nil { - return nil, err - } - } - m, err := v2geo.LoadGeoIP(filename) - if err != nil { - return nil, err - } - l.geoipMap = m - return m, nil -} - -func (l *V2GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) { - if l.geositeMap != nil { - return l.geositeMap, nil - } - autoDL := false - filename := l.GeoSiteFilename - if filename == "" { - autoDL = true - filename = geositeFilename - } - if autoDL && l.shouldDownload(filename) { - err := l.download(filename, geositeURL) - if err != nil { - return nil, err - } - } - m, err := v2geo.LoadGeoSite(filename) - if err != nil { - return nil, err - } - l.geositeMap = m - return m, nil -} diff --git a/go-gost/gfw/ruleset/builtins/geo/geo_matcher.go b/go-gost/gfw/ruleset/builtins/geo/geo_matcher.go deleted file mode 100644 index 1bb0f30..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/geo_matcher.go +++ /dev/null @@ -1,113 +0,0 @@ -package geo - -import ( - "net" - "strings" - "sync" -) - -type GeoMatcher struct { - geoLoader GeoLoader - geoSiteMatcher map[string]hostMatcher - siteMatcherLock sync.Mutex - geoIpMatcher map[string]hostMatcher - ipMatcherLock sync.Mutex -} - -func NewGeoMatcher(geoSiteFilename, geoIpFilename string) *GeoMatcher { - return &GeoMatcher{ - geoLoader: NewDefaultGeoLoader(geoSiteFilename, geoIpFilename), - geoSiteMatcher: make(map[string]hostMatcher), - geoIpMatcher: make(map[string]hostMatcher), - } -} - -func (g *GeoMatcher) MatchGeoIp(ip, condition string) bool { - g.ipMatcherLock.Lock() - defer g.ipMatcherLock.Unlock() - - matcher, ok := g.geoIpMatcher[condition] - if !ok { - // GeoIP matcher - condition = strings.ToLower(condition) - country := condition - if len(country) == 0 { - return false - } - gMap, err := g.geoLoader.LoadGeoIP() - if err != nil { - return false - } - list, ok := gMap[country] - if !ok || list == nil { - return false - } - matcher, err = newGeoIPMatcher(list) - if err != nil { - return false - } - g.geoIpMatcher[condition] = matcher - } - parseIp := net.ParseIP(ip) - if parseIp == nil { - return false - } - ipv4 := parseIp.To4() - if ipv4 != nil { - return matcher.Match(HostInfo{IPv4: ipv4}) - } - ipv6 := parseIp.To16() - if ipv6 != nil { - return matcher.Match(HostInfo{IPv6: ipv6}) - } - return false -} - -func (g *GeoMatcher) MatchGeoSite(site, condition string) bool { - g.siteMatcherLock.Lock() - defer g.siteMatcherLock.Unlock() - - matcher, ok := g.geoSiteMatcher[condition] - if !ok { - // MatchGeoSite matcher - condition = strings.ToLower(condition) - name, attrs := parseGeoSiteName(condition) - if len(name) == 0 { - return false - } - gMap, err := g.geoLoader.LoadGeoSite() - if err != nil { - return false - } - list, ok := gMap[name] - if !ok || list == nil { - return false - } - matcher, err = newGeositeMatcher(list, attrs) - if err != nil { - return false - } - g.geoSiteMatcher[condition] = matcher - } - return matcher.Match(HostInfo{Name: site}) -} - -func (g *GeoMatcher) LoadGeoSite() error { - _, err := g.geoLoader.LoadGeoSite() - return err -} - -func (g *GeoMatcher) LoadGeoIP() error { - _, err := g.geoLoader.LoadGeoIP() - return err -} - -func parseGeoSiteName(s string) (string, []string) { - parts := strings.Split(s, "@") - base := strings.TrimSpace(parts[0]) - attrs := parts[1:] - for i := range attrs { - attrs[i] = strings.TrimSpace(attrs[i]) - } - return base, attrs -} diff --git a/go-gost/gfw/ruleset/builtins/geo/interface.go b/go-gost/gfw/ruleset/builtins/geo/interface.go deleted file mode 100644 index a655539..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/interface.go +++ /dev/null @@ -1,27 +0,0 @@ -package geo - -import ( - "fmt" - "net" - - "github.com/apernet/OpenGFW/ruleset/builtins/geo/v2geo" -) - -type HostInfo struct { - Name string - IPv4 net.IP - IPv6 net.IP -} - -func (h HostInfo) String() string { - return fmt.Sprintf("%s|%s|%s", h.Name, h.IPv4, h.IPv6) -} - -type GeoLoader interface { - LoadGeoIP() (map[string]*v2geo.GeoIP, error) - LoadGeoSite() (map[string]*v2geo.GeoSite, error) -} - -type hostMatcher interface { - Match(HostInfo) bool -} diff --git a/go-gost/gfw/ruleset/builtins/geo/matchers_v2geo.go b/go-gost/gfw/ruleset/builtins/geo/matchers_v2geo.go deleted file mode 100644 index 0271f33..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/matchers_v2geo.go +++ /dev/null @@ -1,213 +0,0 @@ -package geo - -import ( - "bytes" - "errors" - "net" - "regexp" - "sort" - "strings" - - "github.com/apernet/OpenGFW/ruleset/builtins/geo/v2geo" -) - -var _ hostMatcher = (*geoipMatcher)(nil) - -type geoipMatcher struct { - N4 []*net.IPNet // sorted - N6 []*net.IPNet // sorted - Inverse bool -} - -// matchIP tries to match the given IP address with the corresponding IPNets. -// Note that this function does NOT handle the Inverse flag. -func (m *geoipMatcher) matchIP(ip net.IP) bool { - var n []*net.IPNet - if ip4 := ip.To4(); ip4 != nil { - // N4 stores IPv4 addresses in 4-byte form. - // Make sure we use it here too, otherwise bytes.Compare will fail. - ip = ip4 - n = m.N4 - } else { - n = m.N6 - } - left, right := 0, len(n)-1 - for left <= right { - mid := (left + right) / 2 - if n[mid].Contains(ip) { - return true - } else if bytes.Compare(n[mid].IP, ip) < 0 { - left = mid + 1 - } else { - right = mid - 1 - } - } - return false -} - -func (m *geoipMatcher) Match(host HostInfo) bool { - if host.IPv4 != nil { - if m.matchIP(host.IPv4) { - return !m.Inverse - } - } - if host.IPv6 != nil { - if m.matchIP(host.IPv6) { - return !m.Inverse - } - } - return m.Inverse -} - -func newGeoIPMatcher(list *v2geo.GeoIP) (*geoipMatcher, error) { - n4 := make([]*net.IPNet, 0) - n6 := make([]*net.IPNet, 0) - for _, cidr := range list.Cidr { - if len(cidr.Ip) == 4 { - // IPv4 - n4 = append(n4, &net.IPNet{ - IP: cidr.Ip, - Mask: net.CIDRMask(int(cidr.Prefix), 32), - }) - } else if len(cidr.Ip) == 16 { - // IPv6 - n6 = append(n6, &net.IPNet{ - IP: cidr.Ip, - Mask: net.CIDRMask(int(cidr.Prefix), 128), - }) - } else { - return nil, errors.New("invalid IP length") - } - } - // Sort the IPNets, so we can do binary search later. - sort.Slice(n4, func(i, j int) bool { - return bytes.Compare(n4[i].IP, n4[j].IP) < 0 - }) - sort.Slice(n6, func(i, j int) bool { - return bytes.Compare(n6[i].IP, n6[j].IP) < 0 - }) - return &geoipMatcher{ - N4: n4, - N6: n6, - Inverse: list.InverseMatch, - }, nil -} - -var _ hostMatcher = (*geositeMatcher)(nil) - -type geositeDomainType int - -const ( - geositeDomainPlain geositeDomainType = iota - geositeDomainRegex - geositeDomainRoot - geositeDomainFull -) - -type geositeDomain struct { - Type geositeDomainType - Value string - Regex *regexp.Regexp - Attrs map[string]bool -} - -type geositeMatcher struct { - Domains []geositeDomain - // Attributes are matched using "and" logic - if you have multiple attributes here, - // a domain must have all of those attributes to be considered a match. - Attrs []string -} - -func (m *geositeMatcher) matchDomain(domain geositeDomain, host HostInfo) bool { - // Match attributes first - if len(m.Attrs) > 0 { - if len(domain.Attrs) == 0 { - return false - } - for _, attr := range m.Attrs { - if !domain.Attrs[attr] { - return false - } - } - } - - switch domain.Type { - case geositeDomainPlain: - return strings.Contains(host.Name, domain.Value) - case geositeDomainRegex: - if domain.Regex != nil { - return domain.Regex.MatchString(host.Name) - } - case geositeDomainFull: - return host.Name == domain.Value - case geositeDomainRoot: - if host.Name == domain.Value { - return true - } - return strings.HasSuffix(host.Name, "."+domain.Value) - default: - return false - } - return false -} - -func (m *geositeMatcher) Match(host HostInfo) bool { - for _, domain := range m.Domains { - if m.matchDomain(domain, host) { - return true - } - } - return false -} - -func newGeositeMatcher(list *v2geo.GeoSite, attrs []string) (*geositeMatcher, error) { - domains := make([]geositeDomain, len(list.Domain)) - for i, domain := range list.Domain { - switch domain.Type { - case v2geo.Domain_Plain: - domains[i] = geositeDomain{ - Type: geositeDomainPlain, - Value: domain.Value, - Attrs: domainAttributeToMap(domain.Attribute), - } - case v2geo.Domain_Regex: - regex, err := regexp.Compile(domain.Value) - if err != nil { - return nil, err - } - domains[i] = geositeDomain{ - Type: geositeDomainRegex, - Regex: regex, - Attrs: domainAttributeToMap(domain.Attribute), - } - case v2geo.Domain_Full: - domains[i] = geositeDomain{ - Type: geositeDomainFull, - Value: domain.Value, - Attrs: domainAttributeToMap(domain.Attribute), - } - case v2geo.Domain_RootDomain: - domains[i] = geositeDomain{ - Type: geositeDomainRoot, - Value: domain.Value, - Attrs: domainAttributeToMap(domain.Attribute), - } - default: - return nil, errors.New("unsupported domain type") - } - } - return &geositeMatcher{ - Domains: domains, - Attrs: attrs, - }, nil -} - -func domainAttributeToMap(attrs []*v2geo.Domain_Attribute) map[string]bool { - m := make(map[string]bool) - for _, attr := range attrs { - // Supposedly there are also int attributes, - // but nobody seems to use them, so we treat everything as boolean for now. - m[attr.Key] = true - } - return m -} diff --git a/go-gost/gfw/ruleset/builtins/geo/v2geo/load.go b/go-gost/gfw/ruleset/builtins/geo/v2geo/load.go deleted file mode 100644 index 2dd918c..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/v2geo/load.go +++ /dev/null @@ -1,44 +0,0 @@ -package v2geo - -import ( - "os" - "strings" - - "google.golang.org/protobuf/proto" -) - -// LoadGeoIP loads a GeoIP data file and converts it to a map. -// The keys of the map (country codes) are all normalized to lowercase. -func LoadGeoIP(filename string) (map[string]*GeoIP, error) { - bs, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - var list GeoIPList - if err := proto.Unmarshal(bs, &list); err != nil { - return nil, err - } - m := make(map[string]*GeoIP) - for _, entry := range list.Entry { - m[strings.ToLower(entry.CountryCode)] = entry - } - return m, nil -} - -// LoadGeoSite loads a GeoSite data file and converts it to a map. -// The keys of the map (site keys) are all normalized to lowercase. -func LoadGeoSite(filename string) (map[string]*GeoSite, error) { - bs, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - var list GeoSiteList - if err := proto.Unmarshal(bs, &list); err != nil { - return nil, err - } - m := make(map[string]*GeoSite) - for _, entry := range list.Entry { - m[strings.ToLower(entry.CountryCode)] = entry - } - return m, nil -} diff --git a/go-gost/gfw/ruleset/builtins/geo/v2geo/v2geo.pb.go b/go-gost/gfw/ruleset/builtins/geo/v2geo/v2geo.pb.go deleted file mode 100644 index 49f0963..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/v2geo/v2geo.pb.go +++ /dev/null @@ -1,745 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.31.0 -// protoc v4.24.4 -// source: v2geo.proto - -package v2geo - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Type of domain value. -type Domain_Type int32 - -const ( - // The value is used as is. - Domain_Plain Domain_Type = 0 - // The value is used as a regular expression. - Domain_Regex Domain_Type = 1 - // The value is a root domain. - Domain_RootDomain Domain_Type = 2 - // The value is a domain. - Domain_Full Domain_Type = 3 -) - -// Enum value maps for Domain_Type. -var ( - Domain_Type_name = map[int32]string{ - 0: "Plain", - 1: "Regex", - 2: "RootDomain", - 3: "Full", - } - Domain_Type_value = map[string]int32{ - "Plain": 0, - "Regex": 1, - "RootDomain": 2, - "Full": 3, - } -) - -func (x Domain_Type) Enum() *Domain_Type { - p := new(Domain_Type) - *p = x - return p -} - -func (x Domain_Type) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Domain_Type) Descriptor() protoreflect.EnumDescriptor { - return file_v2geo_proto_enumTypes[0].Descriptor() -} - -func (Domain_Type) Type() protoreflect.EnumType { - return &file_v2geo_proto_enumTypes[0] -} - -func (x Domain_Type) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Domain_Type.Descriptor instead. -func (Domain_Type) EnumDescriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{0, 0} -} - -// Domain for routing decision. -type Domain struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Domain matching type. - Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=Domain_Type" json:"type,omitempty"` - // Domain value. - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - // Attributes of this domain. May be used for filtering. - Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"` -} - -func (x *Domain) Reset() { - *x = Domain{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Domain) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Domain) ProtoMessage() {} - -func (x *Domain) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Domain.ProtoReflect.Descriptor instead. -func (*Domain) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{0} -} - -func (x *Domain) GetType() Domain_Type { - if x != nil { - return x.Type - } - return Domain_Plain -} - -func (x *Domain) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - -func (x *Domain) GetAttribute() []*Domain_Attribute { - if x != nil { - return x.Attribute - } - return nil -} - -// IP for routing decision, in CIDR form. -type CIDR struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // IP address, should be either 4 or 16 bytes. - Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - // Number of leading ones in the network mask. - Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"` -} - -func (x *CIDR) Reset() { - *x = CIDR{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CIDR) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CIDR) ProtoMessage() {} - -func (x *CIDR) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CIDR.ProtoReflect.Descriptor instead. -func (*CIDR) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{1} -} - -func (x *CIDR) GetIp() []byte { - if x != nil { - return x.Ip - } - return nil -} - -func (x *CIDR) GetPrefix() uint32 { - if x != nil { - return x.Prefix - } - return 0 -} - -type GeoIP struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` - Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"` - InverseMatch bool `protobuf:"varint,3,opt,name=inverse_match,json=inverseMatch,proto3" json:"inverse_match,omitempty"` - // resource_hash instruct simplified config converter to load domain from geo file. - ResourceHash []byte `protobuf:"bytes,4,opt,name=resource_hash,json=resourceHash,proto3" json:"resource_hash,omitempty"` - Code string `protobuf:"bytes,5,opt,name=code,proto3" json:"code,omitempty"` -} - -func (x *GeoIP) Reset() { - *x = GeoIP{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GeoIP) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GeoIP) ProtoMessage() {} - -func (x *GeoIP) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead. -func (*GeoIP) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{2} -} - -func (x *GeoIP) GetCountryCode() string { - if x != nil { - return x.CountryCode - } - return "" -} - -func (x *GeoIP) GetCidr() []*CIDR { - if x != nil { - return x.Cidr - } - return nil -} - -func (x *GeoIP) GetInverseMatch() bool { - if x != nil { - return x.InverseMatch - } - return false -} - -func (x *GeoIP) GetResourceHash() []byte { - if x != nil { - return x.ResourceHash - } - return nil -} - -func (x *GeoIP) GetCode() string { - if x != nil { - return x.Code - } - return "" -} - -type GeoIPList struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"` -} - -func (x *GeoIPList) Reset() { - *x = GeoIPList{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GeoIPList) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GeoIPList) ProtoMessage() {} - -func (x *GeoIPList) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead. -func (*GeoIPList) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{3} -} - -func (x *GeoIPList) GetEntry() []*GeoIP { - if x != nil { - return x.Entry - } - return nil -} - -type GeoSite struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` - Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"` - // resource_hash instruct simplified config converter to load domain from geo file. - ResourceHash []byte `protobuf:"bytes,3,opt,name=resource_hash,json=resourceHash,proto3" json:"resource_hash,omitempty"` - Code string `protobuf:"bytes,4,opt,name=code,proto3" json:"code,omitempty"` -} - -func (x *GeoSite) Reset() { - *x = GeoSite{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GeoSite) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GeoSite) ProtoMessage() {} - -func (x *GeoSite) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MatchGeoSite.ProtoReflect.Descriptor instead. -func (*GeoSite) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{4} -} - -func (x *GeoSite) GetCountryCode() string { - if x != nil { - return x.CountryCode - } - return "" -} - -func (x *GeoSite) GetDomain() []*Domain { - if x != nil { - return x.Domain - } - return nil -} - -func (x *GeoSite) GetResourceHash() []byte { - if x != nil { - return x.ResourceHash - } - return nil -} - -func (x *GeoSite) GetCode() string { - if x != nil { - return x.Code - } - return "" -} - -type GeoSiteList struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"` -} - -func (x *GeoSiteList) Reset() { - *x = GeoSiteList{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GeoSiteList) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GeoSiteList) ProtoMessage() {} - -func (x *GeoSiteList) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead. -func (*GeoSiteList) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{5} -} - -func (x *GeoSiteList) GetEntry() []*GeoSite { - if x != nil { - return x.Entry - } - return nil -} - -type Domain_Attribute struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - // Types that are assignable to TypedValue: - // - // *Domain_Attribute_BoolValue - // *Domain_Attribute_IntValue - TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"` -} - -func (x *Domain_Attribute) Reset() { - *x = Domain_Attribute{} - if protoimpl.UnsafeEnabled { - mi := &file_v2geo_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Domain_Attribute) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Domain_Attribute) ProtoMessage() {} - -func (x *Domain_Attribute) ProtoReflect() protoreflect.Message { - mi := &file_v2geo_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead. -func (*Domain_Attribute) Descriptor() ([]byte, []int) { - return file_v2geo_proto_rawDescGZIP(), []int{0, 0} -} - -func (x *Domain_Attribute) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (m *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue { - if m != nil { - return m.TypedValue - } - return nil -} - -func (x *Domain_Attribute) GetBoolValue() bool { - if x, ok := x.GetTypedValue().(*Domain_Attribute_BoolValue); ok { - return x.BoolValue - } - return false -} - -func (x *Domain_Attribute) GetIntValue() int64 { - if x, ok := x.GetTypedValue().(*Domain_Attribute_IntValue); ok { - return x.IntValue - } - return 0 -} - -type isDomain_Attribute_TypedValue interface { - isDomain_Attribute_TypedValue() -} - -type Domain_Attribute_BoolValue struct { - BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"` -} - -type Domain_Attribute_IntValue struct { - IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"` -} - -func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {} - -func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {} - -var File_v2geo_proto protoreflect.FileDescriptor - -var file_v2geo_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x76, 0x32, 0x67, 0x65, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x02, - 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x2f, 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x1a, 0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, - 0x36, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, - 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0e, 0x0a, - 0x0a, 0x52, 0x6f, 0x6f, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, - 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x03, 0x22, 0x2e, 0x0a, 0x04, 0x43, 0x49, 0x44, 0x52, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, - 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0xa3, 0x01, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, - 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, - 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d, - 0x61, 0x74, 0x63, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x0a, - 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x05, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x47, 0x65, 0x6f, 0x49, - 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x86, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x6f, - 0x53, 0x69, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, - 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, - 0x65, 0x22, 0x2d, 0x0a, 0x0b, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x1e, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x08, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x76, 0x32, 0x67, 0x65, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_v2geo_proto_rawDescOnce sync.Once - file_v2geo_proto_rawDescData = file_v2geo_proto_rawDesc -) - -func file_v2geo_proto_rawDescGZIP() []byte { - file_v2geo_proto_rawDescOnce.Do(func() { - file_v2geo_proto_rawDescData = protoimpl.X.CompressGZIP(file_v2geo_proto_rawDescData) - }) - return file_v2geo_proto_rawDescData -} - -var file_v2geo_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_v2geo_proto_msgTypes = make([]protoimpl.MessageInfo, 7) -var file_v2geo_proto_goTypes = []interface{}{ - (Domain_Type)(0), // 0: Domain.Type - (*Domain)(nil), // 1: Domain - (*CIDR)(nil), // 2: CIDR - (*GeoIP)(nil), // 3: GeoIP - (*GeoIPList)(nil), // 4: GeoIPList - (*GeoSite)(nil), // 5: MatchGeoSite - (*GeoSiteList)(nil), // 6: GeoSiteList - (*Domain_Attribute)(nil), // 7: Domain.Attribute -} -var file_v2geo_proto_depIdxs = []int32{ - 0, // 0: Domain.type:type_name -> Domain.Type - 7, // 1: Domain.attribute:type_name -> Domain.Attribute - 2, // 2: GeoIP.cidr:type_name -> CIDR - 3, // 3: GeoIPList.entry:type_name -> GeoIP - 1, // 4: MatchGeoSite.domain:type_name -> Domain - 5, // 5: GeoSiteList.entry:type_name -> MatchGeoSite - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name -} - -func init() { file_v2geo_proto_init() } -func file_v2geo_proto_init() { - if File_v2geo_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_v2geo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Domain); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v2geo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CIDR); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v2geo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GeoIP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v2geo_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GeoIPList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v2geo_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GeoSite); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v2geo_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GeoSiteList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v2geo_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Domain_Attribute); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_v2geo_proto_msgTypes[6].OneofWrappers = []interface{}{ - (*Domain_Attribute_BoolValue)(nil), - (*Domain_Attribute_IntValue)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_v2geo_proto_rawDesc, - NumEnums: 1, - NumMessages: 7, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_v2geo_proto_goTypes, - DependencyIndexes: file_v2geo_proto_depIdxs, - EnumInfos: file_v2geo_proto_enumTypes, - MessageInfos: file_v2geo_proto_msgTypes, - }.Build() - File_v2geo_proto = out.File - file_v2geo_proto_rawDesc = nil - file_v2geo_proto_goTypes = nil - file_v2geo_proto_depIdxs = nil -} diff --git a/go-gost/gfw/ruleset/builtins/geo/v2geo/v2geo.proto b/go-gost/gfw/ruleset/builtins/geo/v2geo/v2geo.proto deleted file mode 100644 index 48ff6f0..0000000 --- a/go-gost/gfw/ruleset/builtins/geo/v2geo/v2geo.proto +++ /dev/null @@ -1,76 +0,0 @@ -syntax = "proto3"; - -option go_package = "./v2geo"; - -// This file is copied from -// https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto -// with some modifications. - -// Domain for routing decision. -message Domain { - // Type of domain value. - enum Type { - // The value is used as is. - Plain = 0; - // The value is used as a regular expression. - Regex = 1; - // The value is a root domain. - RootDomain = 2; - // The value is a domain. - Full = 3; - } - - // Domain matching type. - Type type = 1; - - // Domain value. - string value = 2; - - message Attribute { - string key = 1; - - oneof typed_value { - bool bool_value = 2; - int64 int_value = 3; - } - } - - // Attributes of this domain. May be used for filtering. - repeated Attribute attribute = 3; -} - -// IP for routing decision, in CIDR form. -message CIDR { - // IP address, should be either 4 or 16 bytes. - bytes ip = 1; - - // Number of leading ones in the network mask. - uint32 prefix = 2; -} - -message GeoIP { - string country_code = 1; - repeated CIDR cidr = 2; - bool inverse_match = 3; - - // resource_hash instruct simplified config converter to load domain from geo file. - bytes resource_hash = 4; - string code = 5; -} - -message GeoIPList { - repeated GeoIP entry = 1; -} - -message GeoSite { - string country_code = 1; - repeated Domain domain = 2; - - // resource_hash instruct simplified config converter to load domain from geo file. - bytes resource_hash = 3; - string code = 4; -} - -message GeoSiteList { - repeated GeoSite entry = 1; -} diff --git a/go-gost/gfw/ruleset/expr.go b/go-gost/gfw/ruleset/expr.go deleted file mode 100644 index 373d0d3..0000000 --- a/go-gost/gfw/ruleset/expr.go +++ /dev/null @@ -1,377 +0,0 @@ -package ruleset - -import ( - "context" - "fmt" - "net" - "os" - "reflect" - "strings" - "time" - - "github.com/expr-lang/expr/builtin" - - "github.com/expr-lang/expr" - "github.com/expr-lang/expr/ast" - "github.com/expr-lang/expr/conf" - "github.com/expr-lang/expr/vm" - "gopkg.in/yaml.v3" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/modifier" - "github.com/apernet/OpenGFW/ruleset/builtins" -) - -// ExprRule is the external representation of an expression rule. -type ExprRule struct { - Name string `yaml:"name"` - Action string `yaml:"action"` - Log bool `yaml:"log"` - Modifier ModifierEntry `yaml:"modifier"` - Expr string `yaml:"expr"` -} - -type ModifierEntry struct { - Name string `yaml:"name"` - Args map[string]interface{} `yaml:"args"` -} - -func ExprRulesFromYAML(file string) ([]ExprRule, error) { - bs, err := os.ReadFile(file) - if err != nil { - return nil, err - } - var rules []ExprRule - err = yaml.Unmarshal(bs, &rules) - return rules, err -} - -// compiledExprRule is the internal, compiled representation of an expression rule. -type compiledExprRule struct { - Name string - Action *Action // fallthrough if nil - Log bool - ModInstance modifier.Instance - Program *vm.Program -} - -var _ Ruleset = (*exprRuleset)(nil) - -type exprRuleset struct { - Rules []compiledExprRule - Ans []analyzer.Analyzer - Logger Logger -} - -func (r *exprRuleset) Analyzers(info StreamInfo) []analyzer.Analyzer { - return r.Ans -} - -func (r *exprRuleset) Match(info StreamInfo) MatchResult { - env := streamInfoToExprEnv(info) - for _, rule := range r.Rules { - v, err := vm.Run(rule.Program, env) - if err != nil { - // Log the error and continue to the next rule. - r.Logger.MatchError(info, rule.Name, err) - continue - } - if vBool, ok := v.(bool); ok && vBool { - if rule.Log { - r.Logger.Log(info, rule.Name) - } - if rule.Action != nil { - return MatchResult{ - Action: *rule.Action, - ModInstance: rule.ModInstance, - } - } - } - } - // No match - return MatchResult{ - Action: ActionMaybe, - } -} - -// CompileExprRules compiles a list of expression rules into a ruleset. -// It returns an error if any of the rules are invalid, or if any of the analyzers -// used by the rules are unknown (not provided in the analyzer list). -func CompileExprRules(rules []ExprRule, ans []analyzer.Analyzer, mods []modifier.Modifier, config *BuiltinConfig) (Ruleset, error) { - var compiledRules []compiledExprRule - fullAnMap := analyzersToMap(ans) - fullModMap := modifiersToMap(mods) - depAnMap := make(map[string]analyzer.Analyzer) - funcMap := buildFunctionMap(config) - // Compile all rules and build a map of analyzers that are used by the rules. - for _, rule := range rules { - if rule.Action == "" && !rule.Log { - return nil, fmt.Errorf("rule %q must have at least one of action or log", rule.Name) - } - var action *Action - if rule.Action != "" { - a, ok := actionStringToAction(rule.Action) - if !ok { - return nil, fmt.Errorf("rule %q has invalid action %q", rule.Name, rule.Action) - } - action = &a - } - visitor := &idVisitor{Variables: make(map[string]bool), Identifiers: make(map[string]bool)} - patcher := &idPatcher{FuncMap: funcMap} - program, err := expr.Compile(rule.Expr, - func(c *conf.Config) { - c.Strict = false - c.Expect = reflect.Bool - c.Visitors = append(c.Visitors, visitor, patcher) - for name, f := range funcMap { - c.Functions[name] = &builtin.Function{ - Name: name, - Func: f.Func, - Types: f.Types, - } - } - }, - ) - if err != nil { - return nil, fmt.Errorf("rule %q has invalid expression: %w", rule.Name, err) - } - if patcher.Err != nil { - return nil, fmt.Errorf("rule %q failed to patch expression: %w", rule.Name, patcher.Err) - } - for name := range visitor.Identifiers { - // Skip built-in analyzers & user-defined variables - if isBuiltInAnalyzer(name) || visitor.Variables[name] { - continue - } - if f, ok := funcMap[name]; ok { - // Built-in function, initialize if necessary - if f.InitFunc != nil { - if err := f.InitFunc(); err != nil { - return nil, fmt.Errorf("rule %q failed to initialize function %q: %w", rule.Name, name, err) - } - } - } else if a, ok := fullAnMap[name]; ok { - // Analyzer, add to dependency map - depAnMap[name] = a - } - } - cr := compiledExprRule{ - Name: rule.Name, - Action: action, - Log: rule.Log, - Program: program, - } - if action != nil && *action == ActionModify { - mod, ok := fullModMap[rule.Modifier.Name] - if !ok { - return nil, fmt.Errorf("rule %q uses unknown modifier %q", rule.Name, rule.Modifier.Name) - } - modInst, err := mod.New(rule.Modifier.Args) - if err != nil { - return nil, fmt.Errorf("rule %q failed to create modifier instance: %w", rule.Name, err) - } - cr.ModInstance = modInst - } - compiledRules = append(compiledRules, cr) - } - // Convert the analyzer map to a list. - var depAns []analyzer.Analyzer - for _, a := range depAnMap { - depAns = append(depAns, a) - } - return &exprRuleset{ - Rules: compiledRules, - Ans: depAns, - Logger: config.Logger, - }, nil -} - -func streamInfoToExprEnv(info StreamInfo) map[string]interface{} { - m := map[string]interface{}{ - "id": info.ID, - "proto": info.Protocol.String(), - "ip": map[string]string{ - "src": info.SrcIP.String(), - "dst": info.DstIP.String(), - }, - "port": map[string]uint16{ - "src": info.SrcPort, - "dst": info.DstPort, - }, - } - for anName, anProps := range info.Props { - if len(anProps) != 0 { - // Ignore analyzers with empty properties - m[anName] = anProps - } - } - return m -} - -func isBuiltInAnalyzer(name string) bool { - switch name { - case "id", "proto", "ip", "port": - return true - default: - return false - } -} - -func actionStringToAction(action string) (Action, bool) { - switch strings.ToLower(action) { - case "allow": - return ActionAllow, true - case "block": - return ActionBlock, true - case "drop": - return ActionDrop, true - case "modify": - return ActionModify, true - default: - return ActionMaybe, false - } -} - -// analyzersToMap converts a list of analyzers to a map of name -> analyzer. -// This is for easier lookup when compiling rules. -func analyzersToMap(ans []analyzer.Analyzer) map[string]analyzer.Analyzer { - anMap := make(map[string]analyzer.Analyzer) - for _, a := range ans { - anMap[a.Name()] = a - } - return anMap -} - -// modifiersToMap converts a list of modifiers to a map of name -> modifier. -// This is for easier lookup when compiling rules. -func modifiersToMap(mods []modifier.Modifier) map[string]modifier.Modifier { - modMap := make(map[string]modifier.Modifier) - for _, m := range mods { - modMap[m.Name()] = m - } - return modMap -} - -// idVisitor is a visitor that collects all identifiers in an expression. -// This is for determining which analyzers are used by the expression. -type idVisitor struct { - Variables map[string]bool - Identifiers map[string]bool -} - -func (v *idVisitor) Visit(node *ast.Node) { - if varNode, ok := (*node).(*ast.VariableDeclaratorNode); ok { - v.Variables[varNode.Name] = true - } else if idNode, ok := (*node).(*ast.IdentifierNode); ok { - v.Identifiers[idNode.Value] = true - } -} - -// idPatcher patches the AST during expr compilation, replacing certain values with -// their internal representations for better runtime performance. -type idPatcher struct { - FuncMap map[string]*Function - Err error -} - -func (p *idPatcher) Visit(node *ast.Node) { - switch (*node).(type) { - case *ast.CallNode: - callNode := (*node).(*ast.CallNode) - if callNode.Callee == nil { - // Ignore invalid call nodes - return - } - if f, ok := p.FuncMap[callNode.Callee.String()]; ok { - if f.PatchFunc != nil { - if err := f.PatchFunc(&callNode.Arguments); err != nil { - p.Err = err - return - } - } - } - } -} - -type Function struct { - InitFunc func() error - PatchFunc func(args *[]ast.Node) error - Func func(params ...any) (any, error) - Types []reflect.Type -} - -func buildFunctionMap(config *BuiltinConfig) map[string]*Function { - return map[string]*Function{ - "geoip": { - InitFunc: config.GeoMatcher.LoadGeoIP, - PatchFunc: nil, - Func: func(params ...any) (any, error) { - return config.GeoMatcher.MatchGeoIp(params[0].(string), params[1].(string)), nil - }, - Types: []reflect.Type{reflect.TypeOf(config.GeoMatcher.MatchGeoIp)}, - }, - "geosite": { - InitFunc: config.GeoMatcher.LoadGeoSite, - PatchFunc: nil, - Func: func(params ...any) (any, error) { - return config.GeoMatcher.MatchGeoSite(params[0].(string), params[1].(string)), nil - }, - Types: []reflect.Type{reflect.TypeOf(config.GeoMatcher.MatchGeoSite)}, - }, - "cidr": { - InitFunc: nil, - PatchFunc: func(args *[]ast.Node) error { - cidrStringNode, ok := (*args)[1].(*ast.StringNode) - if !ok { - return fmt.Errorf("cidr: invalid argument type") - } - cidr, err := builtins.CompileCIDR(cidrStringNode.Value) - if err != nil { - return err - } - (*args)[1] = &ast.ConstantNode{Value: cidr} - return nil - }, - Func: func(params ...any) (any, error) { - return builtins.MatchCIDR(params[0].(string), params[1].(*net.IPNet)), nil - }, - Types: []reflect.Type{reflect.TypeOf(builtins.MatchCIDR)}, - }, - "lookup": { - InitFunc: nil, - PatchFunc: func(args *[]ast.Node) error { - var serverStr *ast.StringNode - if len(*args) > 1 { - // Has the optional server argument - var ok bool - serverStr, ok = (*args)[1].(*ast.StringNode) - if !ok { - return fmt.Errorf("lookup: invalid argument type") - } - } - r := &net.Resolver{ - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - if serverStr != nil { - address = serverStr.Value - } - return config.ProtectedDialContext(ctx, network, address) - }, - } - if len(*args) > 1 { - (*args)[1] = &ast.ConstantNode{Value: r} - } else { - *args = append(*args, &ast.ConstantNode{Value: r}) - } - return nil - }, - Func: func(params ...any) (any, error) { - ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) - defer cancel() - return params[1].(*net.Resolver).LookupHost(ctx, params[0].(string)) - }, - Types: []reflect.Type{ - reflect.TypeOf((func(string, *net.Resolver) []string)(nil)), - }, - }, - } -} diff --git a/go-gost/gfw/ruleset/interface.go b/go-gost/gfw/ruleset/interface.go deleted file mode 100644 index a6df363..0000000 --- a/go-gost/gfw/ruleset/interface.go +++ /dev/null @@ -1,108 +0,0 @@ -package ruleset - -import ( - "context" - "net" - "strconv" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/modifier" - "github.com/apernet/OpenGFW/ruleset/builtins/geo" -) - -type Action int - -const ( - // ActionMaybe indicates that the ruleset hasn't seen anything worth blocking based on - // current information, but that may change if volatile fields change in the future. - ActionMaybe Action = iota - // ActionAllow indicates that the stream should be allowed regardless of future changes. - ActionAllow - // ActionBlock indicates that the stream should be blocked. - ActionBlock - // ActionDrop indicates that the current packet should be dropped, - // but the stream should be allowed to continue. - // Only valid for UDP streams. Equivalent to ActionBlock for TCP streams. - ActionDrop - // ActionModify indicates that the current packet should be modified, - // and the stream should be allowed to continue. - // Only valid for UDP streams. Equivalent to ActionMaybe for TCP streams. - ActionModify -) - -func (a Action) String() string { - switch a { - case ActionMaybe: - return "maybe" - case ActionAllow: - return "allow" - case ActionBlock: - return "block" - case ActionDrop: - return "drop" - case ActionModify: - return "modify" - default: - return "unknown" - } -} - -type Protocol int - -func (p Protocol) String() string { - switch p { - case ProtocolTCP: - return "tcp" - case ProtocolUDP: - return "udp" - default: - return "unknown" - } -} - -const ( - ProtocolTCP Protocol = iota - ProtocolUDP -) - -type StreamInfo struct { - ID int64 - Protocol Protocol - SrcIP, DstIP net.IP - SrcPort, DstPort uint16 - Props analyzer.CombinedPropMap -} - -func (i StreamInfo) SrcString() string { - return net.JoinHostPort(i.SrcIP.String(), strconv.Itoa(int(i.SrcPort))) -} - -func (i StreamInfo) DstString() string { - return net.JoinHostPort(i.DstIP.String(), strconv.Itoa(int(i.DstPort))) -} - -type MatchResult struct { - Action Action - ModInstance modifier.Instance -} - -type Ruleset interface { - // Analyzers returns the list of analyzers to use for a stream. - // It must be safe for concurrent use by multiple workers. - Analyzers(StreamInfo) []analyzer.Analyzer - // Match matches a stream against the ruleset and returns the result. - // It must be safe for concurrent use by multiple workers. - Match(StreamInfo) MatchResult -} - -// Logger is the logging interface for the ruleset. -type Logger interface { - Log(info StreamInfo, name string) - MatchError(info StreamInfo, name string, err error) -} - -type BuiltinConfig struct { - Logger Logger - GeoMatcher *geo.GeoMatcher - ProtectedDialContext func(ctx context.Context, network, address string) (net.Conn, error) -} diff --git a/go-gost/gost b/go-gost/gost new file mode 100755 index 0000000..a20d804 Binary files /dev/null and b/go-gost/gost differ diff --git a/go-gost/main.go b/go-gost/main.go index 6e7cf2f..95d8626 100644 --- a/go-gost/main.go +++ b/go-gost/main.go @@ -115,22 +115,6 @@ func main() { os.Exit(1) } - // if runtime.GOOS == "linux" { - // go func() { - - // ruleFile := "./rules.yaml" - // if _, err := os.Stat(ruleFile); os.IsNotExist(err) { - // if err := os.WriteFile(ruleFile, []byte(""), 0644); err != nil { - // log.Printf("failed to create rules file: %v", err) - // } else { - // log.Printf("created empty rules file: %s", ruleFile) - // } - // } - - // cmd.Execute([]string{ruleFile}) - // }() - // } - fmt.Println("✅ 配置加载成功 - addr: %s", config.Addr) log := xlogger.NewLogger() @@ -147,5 +131,4 @@ func main() { } // GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o gost -// // upx --best --lzma gost diff --git a/go-gost/x/service/service.go b/go-gost/x/service/service.go index 0ae44d4..627917f 100644 --- a/go-gost/x/service/service.go +++ b/go-gost/x/service/service.go @@ -1,11 +1,15 @@ package service import ( + "bufio" "context" + "encoding/json" "errors" "fmt" "io" + "log" "net" + "os" "os/exec" "strings" "sync" @@ -40,8 +44,25 @@ type options struct { logger logger.Logger } +var isTls = 0 + +var isHttp = 0 + +var isSocks = 0 + +var needWrap = false + type Option func(opts *options) +func init() { + _, err := LoadConfig("config.json") + fmt.Println("config.json loaded") + if err != nil { + log.Fatal(err) + } + needWrap = isTls+isSocks+isHttp > 0 +} + func AdmissionOption(admission admission.Admission) Option { return func(opts *options) { opts.admission = admission @@ -138,6 +159,7 @@ func (s *defaultService) Addr() net.Addr { } func (s *defaultService) Serve() error { + s.execCmds("post-up", s.options.postUp) s.setState(StateReady) s.status.addEvent(Event{ @@ -166,6 +188,7 @@ func (s *defaultService) Serve() error { for { conn, e := s.listener.Accept() if e != nil { + // TODO: remove Temporary checking if ne, ok := e.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { @@ -256,6 +279,10 @@ func (s *defaultService) Serve() error { }() } + if needWrap { + conn = wrapConnPDetection(conn) + } + if err := s.handler.Handle(ctx, conn); err != nil { log.Error(err) if v := xmetrics.GetCounter(xmetrics.MetricServiceHandlerErrorsCounter, @@ -406,3 +433,150 @@ type ServiceEvent struct { func (ServiceEvent) Type() observer.EventType { return observer.EventStatus } + +func wrapConnPDetection(conn net.Conn) net.Conn { + return &detectConn{ + Conn: conn, + reader: bufio.NewReader(conn), + } +} + +type detectConn struct { + net.Conn + reader *bufio.Reader + detected bool +} + +func (c *detectConn) Read(b []byte) (int, error) { + n, err := c.reader.Read(b) + if n > 0 && !c.detected { + c.detected = true + if detectProtocol(b[:n], c.Conn) { + return 0, fmt.Errorf("connection blocked") + } + } + return n, err +} + +func detectProtocol(data []byte, conn net.Conn) (blocked bool) { + if isHttp == 1 && detectHTTP(data) { + conn.Close() + return false + } + + if isTls == 1 && detectTLS(data) { + conn.Close() + return true + } + + if isSocks == 1 && detectSOCKS(data) { + conn.Close() + return true + } + + return false +} + +func detectHTTP(data []byte) bool { + if len(data) < 3 { + return false + } + switch { + case len(data) >= 3 && data[0] == 'G' && data[1] == 'E' && data[2] == 'T': + return true + case len(data) >= 4 && data[0] == 'P' && data[1] == 'O' && data[2] == 'S' && data[3] == 'T': + return true + case len(data) >= 3 && data[0] == 'P' && data[1] == 'U' && data[2] == 'T': + return true + case len(data) >= 6 && data[0] == 'D' && data[1] == 'E' && data[2] == 'L' && + data[3] == 'E' && data[4] == 'T' && data[5] == 'E': + return true + case len(data) >= 4 && data[0] == 'H' && data[1] == 'E' && data[2] == 'A' && data[3] == 'D': + return true + case len(data) >= 7 && data[0] == 'O' && data[1] == 'P' && data[2] == 'T' && + data[3] == 'I' && data[4] == 'O' && data[5] == 'N' && data[6] == 'S': + return true + case len(data) >= 5 && data[0] == 'P' && data[1] == 'A' && data[2] == 'T' && + data[3] == 'C' && data[4] == 'H': + return true + case len(data) >= 7 && data[0] == 'C' && data[1] == 'O' && data[2] == 'N' && + data[3] == 'N' && data[4] == 'E' && data[5] == 'C' && data[6] == 'T': // HTTPS proxy + return true + default: + return false + } +} + +func detectTLS(data []byte) bool { + if len(data) < 5 { + return false + } + if data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 && data[2] <= 0x04 { + return true + } + return false +} + +func detectSOCKS(data []byte) bool { + if len(data) == 0 { + return false + } + + switch data[0] { + case 0x04: + if len(data) < 7 { + return false + } + cmd := data[1] + if cmd != 0x01 && cmd != 0x02 { + return false + } + return true + case 0x05: + if len(data) < 2 { + return false + } + nMethods := int(data[1]) + if len(data) < 2+nMethods { + return false + } + for _, method := range data[2 : 2+nMethods] { + if method == 0x00 || method == 0x02 { + return true + } + } + } + return false +} + +// Config 配置结构体 +type Config struct { + Addr string `json:"addr"` + Secret string `json:"secret"` + Http int `json:"http"` + Tls int `json:"tls"` + Socks int `json:"socks"` +} + +func LoadConfig(configPath string) (string, error) { + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return "", fmt.Errorf("配置文件不存在: %s", configPath) + } + + data, err := os.ReadFile(configPath) + if err != nil { + return "", fmt.Errorf("读取配置文件失败: %v", err) + } + + var config Config + if err := json.Unmarshal(data, &config); err != nil { + return "", fmt.Errorf("解析配置文件失败: %v", err) + } + + isTls = config.Tls + isSocks = config.Socks + isHttp = config.Http + + return "", nil + +} diff --git a/install.sh b/install.sh index c10a699..6d626f6 100644 --- a/install.sh +++ b/install.sh @@ -12,8 +12,7 @@ show_menu() { echo "1. 安装" echo "2. 更新" echo "3. 卸载" - # echo "4. 屏蔽协议" - echo "5. 退出" + echo "4. 退出" echo "===============================================" } @@ -94,232 +93,6 @@ check_and_install_tcpkill() { return 0 } -# 检查并安装 iptables -check_and_install_iptables() { - echo "🔍 检查 iptables..." - - # 检查 iptables 是否已安装 - if command -v iptables &> /dev/null; then - echo "✅ iptables 已安装" - return 0 - fi - - echo "📦 iptables 未安装,正在安装..." - - # 检查是否需要 sudo - if [[ $EUID -ne 0 ]]; then - SUDO_CMD="sudo" - else - SUDO_CMD="" - fi - - # 检测操作系统类型 - OS_TYPE=$(uname -s) - - if [[ "$OS_TYPE" == "Darwin" ]]; then - echo "⚠️ macOS 系统不支持 iptables,请手动安装或使用 pfctl" - return 1 - fi - - # 检测 Linux 发行版并安装对应的包 - if [ -f /etc/os-release ]; then - . /etc/os-release - DISTRO=$ID - elif [ -f /etc/redhat-release ]; then - DISTRO="rhel" - elif [ -f /etc/debian_version ]; then - DISTRO="debian" - else - echo "❌ 无法识别的操作系统" - return 1 - fi - - case $DISTRO in - ubuntu|debian) - $SUDO_CMD apt update &> /dev/null - $SUDO_CMD apt install -y iptables &> /dev/null - ;; - centos|rhel|fedora) - if command -v dnf &> /dev/null; then - $SUDO_CMD dnf install -y iptables &> /dev/null - elif command -v yum &> /dev/null; then - $SUDO_CMD yum install -y iptables &> /dev/null - fi - ;; - alpine) - $SUDO_CMD apk add --no-cache iptables &> /dev/null - ;; - arch|manjaro) - $SUDO_CMD pacman -S --noconfirm iptables &> /dev/null - ;; - opensuse*|sles) - $SUDO_CMD zypper install -y iptables &> /dev/null - ;; - gentoo) - $SUDO_CMD emerge --ask=n net-firewall/iptables &> /dev/null - ;; - void) - $SUDO_CMD xbps-install -Sy iptables &> /dev/null - ;; - esac - - # 验证安装 - if command -v iptables &> /dev/null; then - echo "✅ iptables 安装成功" - return 0 - else - echo "❌ iptables 安装失败" - return 1 - fi -} - - -# 屏蔽协议功能 -block_protocol() { - echo "🛡️ 屏蔽协议功能" - echo "===============================================" - - # 检查 GOST 是否已安装 - if [[ ! -d "$INSTALL_DIR" || ! -f "$INSTALL_DIR/gost" ]]; then - echo "❌ GOST 服务未安装,请先选择安装选项" - echo "💡 提示:请先运行选项 1 安装 GOST 服务" - return 1 - fi - - # 检查 GOST 服务是否正在运行 - if ! systemctl is-active --quiet gost; then - echo "⚠️ GOST 服务未运行,正在启动..." - systemctl start gost - sleep 2 - - if ! systemctl is-active --quiet gost; then - echo "❌ GOST 服务启动失败,请检查配置" - echo "💡 提示:请运行 'journalctl -u gost -f' 查看详细错误信息" - return 1 - fi - fi - - echo "✅ GOST 服务检测通过" - - 检查并安装 iptables - if ! check_and_install_iptables; then - echo "❌ iptables 检查失败,无法继续" - return 1 - fi - - # 验证 IPv4 - is_ipv4() { - [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 - } - - # 验证 IPv6(简单正则) - is_ipv6() { - [[ $1 =~ ^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$ ]] && return 0 || return 1 - } - - - ips=() # 用于存储所有输入的 IP - - while true; do - read -p "请输入 IP(多个用逗号分隔,输入 n 结束): " input - # 判断是否输入 n - if [[ "$input" == "n" ]]; then - break - fi - - # 使用逗号分割 - IFS=',' read -ra arr <<< "$input" - for ip in "${arr[@]}"; do - ip_trimmed=$(echo "$ip" | xargs) # 去掉首尾空格 - if [[ -z "$ip_trimmed" ]]; then - continue - fi - - if is_ipv4 "$ip_trimmed" || is_ipv6 "$ip_trimmed"; then - ips+=("$ip_trimmed") - else - echo "⚠️ 无效 IP: $ip_trimmed" - fi - done - done - - - # 打印记录的 IP - for ip in "${ips[@]}"; do - echo -e "\e[32m$ip\e[0m" - done - - echo "" - read -p "是否屏蔽 HTTP? (y/n) [n]: " block_http - block_http=${block_http:-n} - - read -p "是否屏蔽 TLS? (y/n) [n]: " block_tls - block_tls=${block_tls:-n} - - read -p "是否屏蔽 SOCKS5? (y/n) [n]: " block_socks5 - block_socks5=${block_socks5:-n} - - echo "" - echo "🛡️ 屏蔽设置结果:" - [[ "$block_http" == "y" ]] && echo " - HTTP 已屏蔽" || echo " - HTTP 未屏蔽" - [[ "$block_tls" == "y" ]] && echo " - TLS 已屏蔽" || echo " - TLS 未屏蔽" - [[ "$block_socks5" == "y" ]] && echo " - SOCKS5 已屏蔽" || echo " - SOCKS5 未屏蔽" - - # 生成 rules.yaml 文件 - local file="/etc/gost/rules.yaml" - > "$file" - - # 构造 IP 排除字符串 - ip_expr="" - for ip in "${ips[@]}"; do - ip_expr+="ip.src != \"$ip\" && " - done - ip_expr=${ip_expr% && } - - # 写入规则 - [[ "$block_http" == "y" ]] && cat >> "$file" <> "$file" <> "$file" < { - if (Objects.equals(service.getName(), "web_api")) { - return; // 排除API服务 - } - - String[] serviceIds = parseServiceName(service.getName()); - if (serviceIds.length == 4) { - String forwardId = serviceIds[0]; - String userId = serviceIds[1]; - String userTunnelId = serviceIds[2]; - String type = serviceIds[3]; - - if (Objects.equals(type, "tcp")) { // 只处理TCP,避免重复处理 - Forward forward = forwardService.getById(forwardId); - if (forward == null) { - log.info("删除孤立的服务: {} (节点: {})", service.getName(), node.getId()); - GostDto gostDto = GostUtil.DeleteService(node.getId(), forwardId + "_" + userId + "_" + userTunnelId); - System.out.println(gostDto); - } - } - - - if (Objects.equals(type, "tls")) { - Forward forward = forwardService.getById(forwardId); - if (forward == null) { - log.info("删除孤立的服务: {} (节点: {})", service.getName(), node.getId()); - GostUtil.DeleteRemoteService(node.getId(), forwardId+"_"+userId+"_"+userTunnelId); - } - } - - } - }, "清理服务 " + service.getName()); - } + if (gostConfig.getServices() == null) { + return; } - - - List tunnelList = tunnelService.list(new QueryWrapper().eq("in_node_id", node.getId())); - - try { - if (tunnelList != null && !tunnelList.isEmpty()) { - StringBuilder tunnelIds = new StringBuilder(); - for (Tunnel tunnel : tunnelList) { - tunnelIds.append(tunnel.getId()).append(","); + for (ConfigItem service : gostConfig.getServices()) { + safeExecute(() -> { + if (Objects.equals(service.getName(), "web_api")) { + return; // 排除API服务 } - String ids = tunnelIds.deleteCharAt(tunnelIds.length() - 1).toString(); - List speedLimits = speedLimitService.list(new QueryWrapper().in("tunnel_id", ids)); - if (speedLimits != null && !speedLimits.isEmpty()) { - List limiters = gostConfig.getLimiters(); - List limiters_ids = new ArrayList<>(); - List speedLimits_ids = new ArrayList<>(); - if (limiters != null){ - for (ConfigItem limiter : limiters) { - limiters_ids.add(Long.valueOf(limiter.getName())); + + String[] serviceIds = parseServiceName(service.getName()); + if (serviceIds.length == 4) { + String forwardId = serviceIds[0]; + String userId = serviceIds[1]; + String userTunnelId = serviceIds[2]; + String type = serviceIds[3]; + + if (Objects.equals(type, "tcp")) { // 只处理TCP,避免重复处理 + Forward forward = forwardService.getById(forwardId); + if (forward == null) { + log.info("删除孤立的服务: {} (节点: {})", service.getName(), node.getId()); + GostDto gostDto = GostUtil.DeleteService(node.getId(), forwardId + "_" + userId + "_" + userTunnelId); + System.out.println(gostDto); } } - for (SpeedLimit speedLimit : speedLimits) { - speedLimits_ids.add(speedLimit.getId()); - } - List diff = new ArrayList<>(speedLimits_ids); - diff.removeAll(limiters_ids); - System.out.println(diff); - if (!diff.isEmpty()) { - for (Long speed_id : diff) { - SpeedLimit speedLimit = speedLimitService.getById(speed_id); - if (speedLimit != null) { - SpeedLimitUpdateDto speedLimitUpdateDto = new SpeedLimitUpdateDto(); - speedLimitUpdateDto.setId(speed_id); - speedLimitUpdateDto.setName(speedLimit.getName()); - speedLimitUpdateDto.setSpeed(speedLimit.getSpeed()); - speedLimitUpdateDto.setTunnelId(speedLimit.getTunnelId()); - speedLimitUpdateDto.setTunnelName(speedLimit.getTunnelName()); - speedLimitService.updateSpeedLimit(speedLimitUpdateDto); - } + + if (Objects.equals(type, "tls")) { + Forward forward = forwardService.getById(forwardId); + if (forward == null) { + log.info("删除孤立的服务: {} (节点: {})", service.getName(), node.getId()); + GostUtil.DeleteRemoteService(node.getId(), forwardId+"_"+userId+"_"+userTunnelId); } } + } - } - }catch (Exception e){ - log.info("同步限速器" + e.getMessage()); + }, "清理服务 " + service.getName()); } - - - try { - if (tunnelList != null && !tunnelList.isEmpty()) { - StringBuilder tunnelIds = new StringBuilder(); - for (Tunnel tunnel : tunnelList) { - tunnelIds.append(tunnel.getId()).append(","); - } - String ids = tunnelIds.deleteCharAt(tunnelIds.length() - 1).toString(); - List forwardList = forwardService.list(new QueryWrapper().in("tunnel_id", ids)); - if (forwardList != null && !forwardList.isEmpty()) { - List services = gostConfig.getServices(); - List services_ids = new ArrayList<>(); - List forward_ids = new ArrayList<>(); - if (services != null){ - for (ConfigItem limiter : services) { - String[] strings = parseServiceName(limiter.getName()); - services_ids.add(Long.valueOf(strings[0])); - } - } - for (Forward forward : forwardList) { - forward_ids.add(forward.getId()); - } - List diff = new ArrayList<>(forward_ids); - diff.removeAll(services_ids); - System.out.println(diff); - if (!diff.isEmpty()) { - for (Long forward_id : diff) { - Forward forward = forwardService.getById(forward_id); - if (forward != null) { - forwardService.updateForwardA(forward); - } - - } - } - - } - - } - }catch (Exception e){ - log.info("同步转发" + e.getMessage()); - } - - } /** @@ -235,6 +149,96 @@ public class CheckGostConfigAsync { } } + /** + * 同步限流器 + */ + private void syncLimiters(GostConfigDto gostConfig, Node node) { + List tunnelList = tunnelService.list(new QueryWrapper().eq("in_node_id", node.getId())); + if (tunnelList == null || tunnelList.isEmpty()) return; + safeExecute(() -> { + StringBuilder tunnelIds = new StringBuilder(); + for (Tunnel tunnel : tunnelList) { + tunnelIds.append(tunnel.getId()).append(","); + } + String ids = tunnelIds.deleteCharAt(tunnelIds.length() - 1).toString(); + List speedLimits = speedLimitService.list(new QueryWrapper().in("tunnel_id", ids)); + if (speedLimits != null && !speedLimits.isEmpty()) { + List limiters = gostConfig.getLimiters(); + List limiters_ids = new ArrayList<>(); + List speedLimits_ids = new ArrayList<>(); + if (limiters != null){ + for (ConfigItem limiter : limiters) { + limiters_ids.add(Long.valueOf(limiter.getName())); + } + } + for (SpeedLimit speedLimit : speedLimits) { + speedLimits_ids.add(speedLimit.getId()); + } + List diff = new ArrayList<>(speedLimits_ids); + diff.removeAll(limiters_ids); + System.out.println(diff); + if (!diff.isEmpty()) { + + for (Long speed_id : diff) { + SpeedLimit speedLimit = speedLimitService.getById(speed_id); + if (speedLimit != null) { + SpeedLimitUpdateDto speedLimitUpdateDto = new SpeedLimitUpdateDto(); + speedLimitUpdateDto.setId(speed_id); + speedLimitUpdateDto.setName(speedLimit.getName()); + speedLimitUpdateDto.setSpeed(speedLimit.getSpeed()); + speedLimitUpdateDto.setTunnelId(speedLimit.getTunnelId()); + speedLimitUpdateDto.setTunnelName(speedLimit.getTunnelName()); + speedLimitService.updateSpeedLimit(speedLimitUpdateDto); + } + } + } + } + }, "同步限流器 "); + } + + /** + * 同步服务 + */ + private void syncServices(GostConfigDto gostConfig, Node node) { + List tunnelList = tunnelService.list(new QueryWrapper().eq("in_node_id", node.getId())); + if (tunnelList == null || tunnelList.isEmpty()) return; + safeExecute(() -> { + StringBuilder tunnelIds = new StringBuilder(); + for (Tunnel tunnel : tunnelList) { + tunnelIds.append(tunnel.getId()).append(","); + } + String ids = tunnelIds.deleteCharAt(tunnelIds.length() - 1).toString(); + List forwardList = forwardService.list(new QueryWrapper().in("tunnel_id", ids)); + if (forwardList != null && !forwardList.isEmpty()) { + List services = gostConfig.getServices(); + List services_ids = new ArrayList<>(); + List forward_ids = new ArrayList<>(); + if (services != null){ + for (ConfigItem limiter : services) { + String[] strings = parseServiceName(limiter.getName()); + services_ids.add(Long.valueOf(strings[0])); + } + } + for (Forward forward : forwardList) { + forward_ids.add(forward.getId()); + } + List diff = new ArrayList<>(forward_ids); + diff.removeAll(services_ids); + System.out.println(diff); + if (!diff.isEmpty()) { + for (Long forward_id : diff) { + Forward forward = forwardService.getById(forward_id); + if (forward != null) { + forwardService.updateForwardA(forward); + } + + } + } + + } + }, "同步限流器 "); + } + /** * 安全执行操作,捕获异常 */ @@ -246,6 +250,7 @@ public class CheckGostConfigAsync { } } + /** * 解析服务名称 */