3ef81afc76
Co-authored-by: Larvan2 <78135608+Larvan2@users.noreply.github.com> Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
136 lines
2.7 KiB
Go
136 lines
2.7 KiB
Go
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) 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" {
|
|
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
|
|
}
|