From 6d704b9cd174d0a5827959c5f1897fb12f9ba4c4 Mon Sep 17 00:00:00 2001 From: MetaCubeX Date: Mon, 2 May 2022 05:10:18 +0800 Subject: [PATCH] feat: sniffer support http --- component/sniffer/http_sniffer.go | 109 ++++++++++++++++++++++++++++++ component/sniffer/quic_sniffer.go | 3 + constant/sniffer.go | 4 +- 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 component/sniffer/http_sniffer.go create mode 100644 component/sniffer/quic_sniffer.go diff --git a/component/sniffer/http_sniffer.go b/component/sniffer/http_sniffer.go new file mode 100644 index 000000000..84ab41032 --- /dev/null +++ b/component/sniffer/http_sniffer.go @@ -0,0 +1,109 @@ +package sniffer + +import ( + "bytes" + "errors" + C "github.com/Dreamacro/clash/constant" + "net" + "strconv" + "strings" +) + +var ( + // refer to https://pkg.go.dev/net/http@master#pkg-constants + methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect", "patch", "trace"} + errNotHTTPMethod = errors.New("not an HTTP method") +) + +type version byte + +const ( + HTTP1 version = iota + HTTP2 +) + +type HTTPSniffer struct { + version version + host string +} + +func (http *HTTPSniffer) Protocol() string { + switch http.version { + case HTTP1: + return "http1" + case HTTP2: + return "http2" + default: + return "unknown" + } +} + +func (http *HTTPSniffer) SupportNetwork() C.NetWork { + return C.TCP +} + +func (http *HTTPSniffer) SniffTCP(bytes []byte) (string, error) { + domain, err := SniffHTTP(bytes) + if err == nil { + return *domain, nil + } else { + return "", err + } +} + +func beginWithHTTPMethod(b []byte) error { + for _, m := range &methods { + if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) { + return nil + } + + if len(b) < len(m) { + return ErrNoClue + } + } + return errNotHTTPMethod +} + +func SniffHTTP(b []byte) (*string, error) { + if err := beginWithHTTPMethod(b); err != nil { + return nil, err + } + + _ = &HTTPSniffer{ + version: HTTP1, + } + + headers := bytes.Split(b, []byte{'\n'}) + for i := 1; i < len(headers); i++ { + header := headers[i] + if len(header) == 0 { + break + } + parts := bytes.SplitN(header, []byte{':'}, 2) + if len(parts) != 2 { + continue + } + key := strings.ToLower(string(parts[0])) + if key == "host" { + port := 80 + rawHost := strings.ToLower(string(bytes.TrimSpace(parts[1]))) + host, rawPort, err := net.SplitHostPort(rawHost) + if err != nil { + if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") { + host = rawHost + } else { + return nil, err + } + } else if len(rawPort) > 0 { + intPort, err := strconv.ParseUint(rawPort, 0, 16) + if err != nil { + return nil, err + } + port = int(intPort) + } + host = net.JoinHostPort(host, strconv.Itoa(port)) + return &host, nil + } + } + return nil, ErrNoClue +} diff --git a/component/sniffer/quic_sniffer.go b/component/sniffer/quic_sniffer.go new file mode 100644 index 000000000..de78cf821 --- /dev/null +++ b/component/sniffer/quic_sniffer.go @@ -0,0 +1,3 @@ +package sniffer + +//TODO diff --git a/constant/sniffer.go b/constant/sniffer.go index 5454279aa..c19b068cd 100644 --- a/constant/sniffer.go +++ b/constant/sniffer.go @@ -11,7 +11,7 @@ const ( ) var ( - SnifferList = []SnifferType{TLS} + SnifferList = []SnifferType{TLS, HTTP} ) type SnifferType int @@ -20,6 +20,8 @@ func (rt SnifferType) String() string { switch rt { case TLS: return "TLS" + case HTTP: + return "HTTP" default: return "Unknown" }