Browse Source

move sniffer to their own directory

Darien Raymond 7 years ago
parent
commit
8912e4eb90

+ 32 - 0
common/protocol/bittorrent/bittorrent.go

@@ -0,0 +1,32 @@
+package bittorrent
+
+import (
+	"errors"
+
+	"v2ray.com/core"
+)
+
+type SniffHeader struct {
+}
+
+func (h *SniffHeader) Protocol() string {
+	return "bittorrent"
+}
+
+func (h *SniffHeader) Domain() string {
+	return ""
+}
+
+var errNotBittorrent = errors.New("not bittorrent header")
+
+func SniffBittorrent(b []byte) (*SniffHeader, error) {
+	if len(b) < 20 {
+		return nil, core.ErrNoClue
+	}
+
+	if b[0] == 19 && string(b[1:20]) == "BitTorrent protocol" {
+		return &SniffHeader{}, nil
+	}
+
+	return nil, errNotBittorrent
+}

+ 90 - 0
common/protocol/http/sniff.go

@@ -0,0 +1,90 @@
+package http
+
+import (
+	"bytes"
+	"errors"
+	"strings"
+
+	"v2ray.com/core"
+)
+
+type version byte
+
+const (
+	HTTP1 version = iota
+	HTTP2
+)
+
+type SniffHeader struct {
+	version version
+	host    string
+}
+
+func (h *SniffHeader) Protocol() string {
+	switch h.version {
+	case HTTP1:
+		return "http1"
+	case HTTP2:
+		return "http2"
+	default:
+		return "unknown"
+	}
+}
+
+func (h *SniffHeader) Domain() string {
+	return h.host
+}
+
+var (
+	methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"}
+
+	errNotHTTPMethod = errors.New("not an HTTP method")
+)
+
+func beginWithHTTPMethod(b []byte) error {
+	for _, m := range methods {
+		if len(b) >= len(m) && strings.ToLower(string(b[:len(m)])) == m {
+			return nil
+		}
+
+		if len(b) < len(m) {
+			return core.ErrNoClue
+		}
+	}
+
+	return errNotHTTPMethod
+}
+
+func SniffHTTP(b []byte) (*SniffHeader, error) {
+	if err := beginWithHTTPMethod(b); err != nil {
+		return nil, err
+	}
+
+	sh := &SniffHeader{
+		version: HTTP1,
+	}
+
+	headers := bytes.Split(b, []byte{'\n'})
+	for i := 1; i < len(headers); i++ {
+		header := headers[i]
+		if len(header) == 0 {
+			return nil, core.ErrNoClue
+		}
+		parts := bytes.SplitN(header, []byte{':'}, 2)
+		if len(parts) != 2 {
+			continue
+		}
+		key := strings.ToLower(string(parts[0]))
+		value := strings.ToLower(string(bytes.Trim(parts[1], " ")))
+		if key == "host" {
+			domain := strings.Split(value, ":")
+			sh.host = strings.TrimSpace(domain[0])
+		}
+	}
+
+	if len(sh.host) > 0 {
+		return sh, nil
+	}
+
+	return nil, core.ErrNoClue
+}

+ 106 - 0
common/protocol/http/sniff_test.go

@@ -0,0 +1,106 @@
+package http_test
+
+import (
+	"testing"
+
+	"v2ray.com/core/common/compare"
+	. "v2ray.com/core/common/protocol/http"
+)
+
+func TestHTTPHeaders(t *testing.T) {
+	cases := []struct {
+		input  string
+		domain string
+		err    bool
+	}{
+		{
+			input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
+Host: net.tutsplus.com
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
+Pragma: no-cache
+Cache-Control: no-cache`,
+			domain: "net.tutsplus.com",
+		},
+		{
+			input: `POST /foo.php HTTP/1.1
+Host: localhost
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Referer: http://localhost/test.php
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 43
+ 
+first_name=John&last_name=Doe&action=Submit`,
+			domain: "localhost",
+		},
+		{
+			input: `X /foo.php HTTP/1.1
+Host: localhost
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Referer: http://localhost/test.php
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 43
+ 
+first_name=John&last_name=Doe&action=Submit`,
+			domain: "",
+			err:    true,
+		},
+		{
+			input: `GET /foo.php HTTP/1.1
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Referer: http://localhost/test.php
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 43
+
+Host: localhost
+first_name=John&last_name=Doe&action=Submit`,
+			domain: "",
+			err:    true,
+		},
+		{
+			input:  `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1`,
+			domain: "",
+			err:    true,
+		},
+	}
+
+	for _, test := range cases {
+		header, err := SniffHTTP([]byte(test.input))
+		if test.err {
+			if err == nil {
+				t.Errorf("Expect error but nil, in test: %v", test)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("Expect no error but actually %s in test %v", err.Error(), test)
+			}
+			if err := compare.StringEqualWithDetail(header.Domain(), test.domain); err != nil {
+				t.Error(err)
+			}
+		}
+	}
+}

+ 147 - 0
common/protocol/tls/sniff.go

@@ -0,0 +1,147 @@
+package tls
+
+import (
+	"errors"
+	"strings"
+
+	"v2ray.com/core"
+	"v2ray.com/core/common/serial"
+)
+
+type SniffHeader struct {
+	domain string
+}
+
+func (h *SniffHeader) Protocol() string {
+	return "tls"
+}
+
+func (h *SniffHeader) Domain() string {
+	return h.domain
+}
+
+var errNotTLS = errors.New("not TLS header")
+var errNotClientHello = errors.New("not client hello")
+
+func IsValidTLSVersion(major, minor byte) bool {
+	return major == 3
+}
+
+// ReadClientHello returns server name (if any) from TLS client hello message.
+// https://github.com/golang/go/blob/master/src/crypto/tls/handshake_messages.go#L300
+func ReadClientHello(data []byte, h *SniffHeader) error {
+	if len(data) < 42 {
+		return core.ErrNoClue
+	}
+	sessionIDLen := int(data[38])
+	if sessionIDLen > 32 || len(data) < 39+sessionIDLen {
+		return core.ErrNoClue
+	}
+	data = data[39+sessionIDLen:]
+	if len(data) < 2 {
+		return core.ErrNoClue
+	}
+	// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
+	// they are uint16s, the number must be even.
+	cipherSuiteLen := int(data[0])<<8 | int(data[1])
+	if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
+		return errNotClientHello
+	}
+	data = data[2+cipherSuiteLen:]
+	if len(data) < 1 {
+		return core.ErrNoClue
+	}
+	compressionMethodsLen := int(data[0])
+	if len(data) < 1+compressionMethodsLen {
+		return core.ErrNoClue
+	}
+	data = data[1+compressionMethodsLen:]
+
+	if len(data) == 0 {
+		return errNotClientHello
+	}
+	if len(data) < 2 {
+		return errNotClientHello
+	}
+
+	extensionsLength := int(data[0])<<8 | int(data[1])
+	data = data[2:]
+	if extensionsLength != len(data) {
+		return errNotClientHello
+	}
+
+	for len(data) != 0 {
+		if len(data) < 4 {
+			return errNotClientHello
+		}
+		extension := uint16(data[0])<<8 | uint16(data[1])
+		length := int(data[2])<<8 | int(data[3])
+		data = data[4:]
+		if len(data) < length {
+			return errNotClientHello
+		}
+
+		switch extension {
+		case 0x00: /* extensionServerName */
+			d := data[:length]
+			if len(d) < 2 {
+				return errNotClientHello
+			}
+			namesLen := int(d[0])<<8 | int(d[1])
+			d = d[2:]
+			if len(d) != namesLen {
+				return errNotClientHello
+			}
+			for len(d) > 0 {
+				if len(d) < 3 {
+					return errNotClientHello
+				}
+				nameType := d[0]
+				nameLen := int(d[1])<<8 | int(d[2])
+				d = d[3:]
+				if len(d) < nameLen {
+					return errNotClientHello
+				}
+				if nameType == 0 {
+					serverName := string(d[:nameLen])
+					// An SNI value may not include a
+					// trailing dot. See
+					// https://tools.ietf.org/html/rfc6066#section-3.
+					if strings.HasSuffix(serverName, ".") {
+						return errNotClientHello
+					}
+					h.domain = serverName
+					return nil
+				}
+				d = d[nameLen:]
+			}
+		}
+		data = data[length:]
+	}
+
+	return errNotTLS
+}
+
+func SniffTLS(b []byte) (*SniffHeader, error) {
+	if len(b) < 5 {
+		return nil, core.ErrNoClue
+	}
+
+	if b[0] != 0x16 /* TLS Handshake */ {
+		return nil, errNotTLS
+	}
+	if !IsValidTLSVersion(b[1], b[2]) {
+		return nil, errNotTLS
+	}
+	headerLen := int(serial.BytesToUint16(b[3:5]))
+	if 5+headerLen > len(b) {
+		return nil, core.ErrNoClue
+	}
+
+	h := &SniffHeader{}
+	err := ReadClientHello(b[5:5+headerLen], h)
+	if err == nil {
+		return h, nil
+	}
+	return nil, err
+}

+ 102 - 0
common/protocol/tls/sniff_test.go

@@ -0,0 +1,102 @@
+package tls_test
+
+import (
+	"testing"
+
+	"v2ray.com/core/common/compare"
+	. "v2ray.com/core/common/protocol/tls"
+)
+
+func TestTLSHeaders(t *testing.T) {
+	cases := []struct {
+		input  []byte
+		domain string
+		err    bool
+	}{
+		{
+			input: []byte{
+				0x16, 0x03, 0x01, 0x00, 0xc8, 0x01, 0x00, 0x00,
+				0xc4, 0x03, 0x03, 0x1a, 0xac, 0xb2, 0xa8, 0xfe,
+				0xb4, 0x96, 0x04, 0x5b, 0xca, 0xf7, 0xc1, 0xf4,
+				0x2e, 0x53, 0x24, 0x6e, 0x34, 0x0c, 0x58, 0x36,
+				0x71, 0x97, 0x59, 0xe9, 0x41, 0x66, 0xe2, 0x43,
+				0xa0, 0x13, 0xb6, 0x00, 0x00, 0x20, 0x1a, 0x1a,
+				0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+				0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
+				0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
+				0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
+				0x00, 0x7b, 0xba, 0xba, 0x00, 0x00, 0xff, 0x01,
+				0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
+				0x14, 0x00, 0x00, 0x11, 0x63, 0x2e, 0x73, 0x2d,
+				0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,
+				0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00,
+				0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
+				0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04,
+				0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08,
+				0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00,
+				0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
+				0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c,
+				0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70,
+				0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02,
+				0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,
+				0xaa, 0xaa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
+				0xaa, 0xaa, 0x00, 0x01, 0x00,
+			},
+			domain: "c.s-microsoft.com",
+			err:    false,
+		},
+		{
+			input: []byte{
+				0x16, 0x03, 0x01, 0x00, 0xee, 0x01, 0x00, 0x00,
+				0xea, 0x03, 0x03, 0xe7, 0x91, 0x9e, 0x93, 0xca,
+				0x78, 0x1b, 0x3c, 0xe0, 0x65, 0x25, 0x58, 0xb5,
+				0x93, 0xe1, 0x0f, 0x85, 0xec, 0x9a, 0x66, 0x8e,
+				0x61, 0x82, 0x88, 0xc8, 0xfc, 0xae, 0x1e, 0xca,
+				0xd7, 0xa5, 0x63, 0x20, 0xbd, 0x1c, 0x00, 0x00,
+				0x8b, 0xee, 0x09, 0xe3, 0x47, 0x6a, 0x0e, 0x74,
+				0xb0, 0xbc, 0xa3, 0x02, 0xa7, 0x35, 0xe8, 0x85,
+				0x70, 0x7c, 0x7a, 0xf0, 0x00, 0xdf, 0x4a, 0xea,
+				0x87, 0x01, 0x14, 0x91, 0x00, 0x20, 0xea, 0xea,
+				0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+				0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
+				0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
+				0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
+				0x00, 0x81, 0x9a, 0x9a, 0x00, 0x00, 0xff, 0x01,
+				0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
+				0x16, 0x00, 0x00, 0x13, 0x77, 0x77, 0x77, 0x30,
+				0x37, 0x2e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x74,
+				0x61, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x00,
+				0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+				0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
+				0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,
+				0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00,
+				0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e,
+				0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74,
+				0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x75, 0x50,
+				0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
+				0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x9a, 0x9a,
+				0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x8a, 0x8a,
+				0x00, 0x01, 0x00,
+			},
+			domain: "www07.clicktale.net",
+			err:    false,
+		},
+	}
+
+	for _, test := range cases {
+		header, err := SniffTLS(test.input)
+		if test.err {
+			if err == nil {
+				t.Errorf("Exepct error but nil in test %v", test)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("Expect no error but actually %s in test %v", err.Error(), test)
+			}
+			if err := compare.StringEqualWithDetail(header.Domain(), test.domain); err != nil {
+				t.Error(err)
+			}
+		}
+	}
+}