package sniffer import ( "bytes" "errors" "fmt" "net" "strings" "github.com/Dreamacro/clash/common/utils" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/sniffer" ) 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 { *BaseSniffer version version host string } var _ sniffer.Sniffer = (*HTTPSniffer)(nil) func NewHTTPSniffer(snifferConfig SnifferConfig) (*HTTPSniffer, error) { ports := snifferConfig.Ports if len(ports) == 0 { ports = utils.IntRanges[uint16]{utils.NewRange[uint16](80, 80)} } return &HTTPSniffer{ BaseSniffer: NewBaseSniffer(ports, C.TCP), }, nil } 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) SniffData(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" { rawHost := strings.ToLower(string(bytes.TrimSpace(parts[1]))) host, _, err := net.SplitHostPort(rawHost) if err != nil { if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") { return parseHost(rawHost) } else { return nil, err } } if net.ParseIP(host) != nil { return nil, fmt.Errorf("host is ip") } return &host, nil } } return nil, ErrNoClue } func parseHost(host string) (*string, error) { if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { if net.ParseIP(host[1:len(host)-1]) != nil { return nil, fmt.Errorf("host is ip") } } if net.ParseIP(host) != nil { return nil, fmt.Errorf("host is ip") } return &host, nil }