From 24ce6622a2960cdc6f84e4dde2ae0a74570415a4 Mon Sep 17 00:00:00 2001 From: fishg <1423545+fishg@users.noreply.github.com> Date: Wed, 30 Mar 2022 23:54:52 +0800 Subject: [PATCH 1/5] Feature: add tls SNI sniffing (#68) --- common/snifer/tls/sniff.go | 148 +++++++++++++++++++++++++++++ common/snifer/tls/sniff_test.go | 159 ++++++++++++++++++++++++++++++++ component/resolver/enhancer.go | 7 ++ dns/enhancer.go | 6 ++ tunnel/statistic/tracker.go | 17 ++++ 5 files changed, 337 insertions(+) create mode 100644 common/snifer/tls/sniff.go create mode 100644 common/snifer/tls/sniff_test.go diff --git a/common/snifer/tls/sniff.go b/common/snifer/tls/sniff.go new file mode 100644 index 000000000..1471fc68d --- /dev/null +++ b/common/snifer/tls/sniff.go @@ -0,0 +1,148 @@ +package tls + +import ( + "encoding/binary" + "errors" + "strings" +) + +var ErrNoClue = errors.New("not enough information for making a decision") + +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") + 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 ErrNoClue + } + sessionIDLen := int(data[38]) + if sessionIDLen > 32 || len(data) < 39+sessionIDLen { + return ErrNoClue + } + data = data[39+sessionIDLen:] + if len(data) < 2 { + return 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 ErrNoClue + } + compressionMethodsLen := int(data[0]) + if len(data) < 1+compressionMethodsLen { + return 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 + } + + if extension == 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, ErrNoClue + } + + if b[0] != 0x16 /* TLS Handshake */ { + return nil, errNotTLS + } + if !IsValidTLSVersion(b[1], b[2]) { + return nil, errNotTLS + } + headerLen := int(binary.BigEndian.Uint16(b[3:5])) + if 5+headerLen > len(b) { + return nil, ErrNoClue + } + + h := &SniffHeader{} + err := ReadClientHello(b[5:5+headerLen], h) + if err == nil { + return h, nil + } + return nil, err +} diff --git a/common/snifer/tls/sniff_test.go b/common/snifer/tls/sniff_test.go new file mode 100644 index 000000000..26f5f1eea --- /dev/null +++ b/common/snifer/tls/sniff_test.go @@ -0,0 +1,159 @@ +package tls + +import ( + "testing" +) + +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, + }, + { + input: []byte{ + 0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1, + 0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6, + 0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d, + 0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84, + 0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08, + 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04, + 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e, + 0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, + 0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, + 0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c, + 0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01, + 0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72, + }, + domain: "dogfish", + err: false, + }, + { + input: []byte{ + 0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00, + 0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee, + 0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14, + 0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62, + 0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45, + 0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c, + 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, + 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e, + 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, + 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14, + 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, + 0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03, + 0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, + 0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, + 0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30, + 0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04, + 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a, + 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, + 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, + 0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03, + 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, + 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02, + 0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f, + 0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, + 0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28, + 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, + 0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e, + 0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f, + 0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d, + 0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36, + }, + domain: "10.42.0.243", + 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 header.Domain() != test.domain { + t.Error("expect domain ", test.domain, " but got ", header.Domain()) + } + } + } +} diff --git a/component/resolver/enhancer.go b/component/resolver/enhancer.go index 9df3f54b3..77f183743 100644 --- a/component/resolver/enhancer.go +++ b/component/resolver/enhancer.go @@ -14,6 +14,7 @@ type Enhancer interface { IsExistFakeIP(net.IP) bool FindHostByIP(net.IP) (string, bool) FlushFakeIP() error + InsertHostByIP(net.IP, string) } func FakeIPEnabled() bool { @@ -56,6 +57,12 @@ func IsExistFakeIP(ip net.IP) bool { return false } +func InsertHostByIP(ip net.IP, host string) { + if mapper := DefaultHostMapper; mapper != nil { + mapper.InsertHostByIP(ip, host) + } +} + func FindHostByIP(ip net.IP) (string, bool) { if mapper := DefaultHostMapper; mapper != nil { return mapper.FindHostByIP(ip) diff --git a/dns/enhancer.go b/dns/enhancer.go index 9bf568c7f..016ff02ac 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -74,6 +74,12 @@ func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { return "", false } +func (h *ResolverEnhancer) InsertHostByIP(ip net.IP, host string) { + if mapping := h.mapping; mapping != nil { + h.mapping.Set(ip.String(), host) + } +} + func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { if h.mapping != nil && o.mapping != nil { o.mapping.CloneTo(h.mapping) diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index 1f5f1f9cc..f213ca61b 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -1,10 +1,14 @@ package statistic import ( + "errors" "net" "time" + "github.com/Dreamacro/clash/common/snifer/tls" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" "github.com/gofrs/uuid" "go.uber.org/atomic" @@ -48,7 +52,20 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) tt.manager.PushUploaded(upload) + if tt.UploadTotal.Load() < 128 && tt.Metadata.Host == "" && (tt.Metadata.DstPort == "443" || tt.Metadata.DstPort == "8443") { + header, err := tls.SniffTLS(b) + if err != nil { + // log.Errorln("Expect no error but actually %s %s:%s:%s", err.Error(), tt.Metadata.Host, tt.Metadata.DstIP.String(), tt.Metadata.DstPort) + } else { + resolver.InsertHostByIP(tt.Metadata.DstIP, header.Domain()) + log.Warnln("use sni update host: %s ip: %s", header.Domain(), tt.Metadata.DstIP.String()) + tt.manager.Leave(tt) + tt.Conn.Close() + return n, errors.New("sni update, break current link to avoid leaks") + } + } tt.UploadTotal.Add(upload) + return n, err } From e877b68179a735e5743a732b04e0de1dba238267 Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Thu, 31 Mar 2022 21:20:46 +0800 Subject: [PATCH 2/5] Chore: revert "Feature: add tls SNI sniffing (#68)" This reverts commit 24ce6622a2960cdc6f84e4dde2ae0a74570415a4. --- common/snifer/tls/sniff.go | 148 ----------------------------- common/snifer/tls/sniff_test.go | 159 -------------------------------- component/resolver/enhancer.go | 7 -- dns/enhancer.go | 6 -- tunnel/statistic/tracker.go | 17 ---- 5 files changed, 337 deletions(-) delete mode 100644 common/snifer/tls/sniff.go delete mode 100644 common/snifer/tls/sniff_test.go diff --git a/common/snifer/tls/sniff.go b/common/snifer/tls/sniff.go deleted file mode 100644 index 1471fc68d..000000000 --- a/common/snifer/tls/sniff.go +++ /dev/null @@ -1,148 +0,0 @@ -package tls - -import ( - "encoding/binary" - "errors" - "strings" -) - -var ErrNoClue = errors.New("not enough information for making a decision") - -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") - 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 ErrNoClue - } - sessionIDLen := int(data[38]) - if sessionIDLen > 32 || len(data) < 39+sessionIDLen { - return ErrNoClue - } - data = data[39+sessionIDLen:] - if len(data) < 2 { - return 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 ErrNoClue - } - compressionMethodsLen := int(data[0]) - if len(data) < 1+compressionMethodsLen { - return 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 - } - - if extension == 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, ErrNoClue - } - - if b[0] != 0x16 /* TLS Handshake */ { - return nil, errNotTLS - } - if !IsValidTLSVersion(b[1], b[2]) { - return nil, errNotTLS - } - headerLen := int(binary.BigEndian.Uint16(b[3:5])) - if 5+headerLen > len(b) { - return nil, ErrNoClue - } - - h := &SniffHeader{} - err := ReadClientHello(b[5:5+headerLen], h) - if err == nil { - return h, nil - } - return nil, err -} diff --git a/common/snifer/tls/sniff_test.go b/common/snifer/tls/sniff_test.go deleted file mode 100644 index 26f5f1eea..000000000 --- a/common/snifer/tls/sniff_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package tls - -import ( - "testing" -) - -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, - }, - { - input: []byte{ - 0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1, - 0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6, - 0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d, - 0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84, - 0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08, - 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c, - 0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04, - 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e, - 0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, - 0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, - 0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, - 0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00, - 0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c, - 0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01, - 0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72, - }, - domain: "dogfish", - err: false, - }, - { - input: []byte{ - 0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00, - 0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee, - 0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14, - 0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62, - 0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45, - 0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c, - 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, - 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e, - 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, - 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14, - 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, - 0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03, - 0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, - 0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98, - 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, - 0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30, - 0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04, - 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a, - 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, - 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, - 0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03, - 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, - 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, - 0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02, - 0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, - 0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f, - 0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, - 0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28, - 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, - 0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e, - 0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f, - 0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d, - 0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36, - }, - domain: "10.42.0.243", - 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 header.Domain() != test.domain { - t.Error("expect domain ", test.domain, " but got ", header.Domain()) - } - } - } -} diff --git a/component/resolver/enhancer.go b/component/resolver/enhancer.go index 77f183743..9df3f54b3 100644 --- a/component/resolver/enhancer.go +++ b/component/resolver/enhancer.go @@ -14,7 +14,6 @@ type Enhancer interface { IsExistFakeIP(net.IP) bool FindHostByIP(net.IP) (string, bool) FlushFakeIP() error - InsertHostByIP(net.IP, string) } func FakeIPEnabled() bool { @@ -57,12 +56,6 @@ func IsExistFakeIP(ip net.IP) bool { return false } -func InsertHostByIP(ip net.IP, host string) { - if mapper := DefaultHostMapper; mapper != nil { - mapper.InsertHostByIP(ip, host) - } -} - func FindHostByIP(ip net.IP) (string, bool) { if mapper := DefaultHostMapper; mapper != nil { return mapper.FindHostByIP(ip) diff --git a/dns/enhancer.go b/dns/enhancer.go index 016ff02ac..9bf568c7f 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -74,12 +74,6 @@ func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { return "", false } -func (h *ResolverEnhancer) InsertHostByIP(ip net.IP, host string) { - if mapping := h.mapping; mapping != nil { - h.mapping.Set(ip.String(), host) - } -} - func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { if h.mapping != nil && o.mapping != nil { o.mapping.CloneTo(h.mapping) diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index f213ca61b..1f5f1f9cc 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -1,14 +1,10 @@ package statistic import ( - "errors" "net" "time" - "github.com/Dreamacro/clash/common/snifer/tls" - "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" "github.com/gofrs/uuid" "go.uber.org/atomic" @@ -52,20 +48,7 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) tt.manager.PushUploaded(upload) - if tt.UploadTotal.Load() < 128 && tt.Metadata.Host == "" && (tt.Metadata.DstPort == "443" || tt.Metadata.DstPort == "8443") { - header, err := tls.SniffTLS(b) - if err != nil { - // log.Errorln("Expect no error but actually %s %s:%s:%s", err.Error(), tt.Metadata.Host, tt.Metadata.DstIP.String(), tt.Metadata.DstPort) - } else { - resolver.InsertHostByIP(tt.Metadata.DstIP, header.Domain()) - log.Warnln("use sni update host: %s ip: %s", header.Domain(), tt.Metadata.DstIP.String()) - tt.manager.Leave(tt) - tt.Conn.Close() - return n, errors.New("sni update, break current link to avoid leaks") - } - } tt.UploadTotal.Add(upload) - return n, err } From c495d314d4d94db15153cf1f558db35b0d5cc9a6 Mon Sep 17 00:00:00 2001 From: fishg <1423545+fishg@users.noreply.github.com> Date: Wed, 30 Mar 2022 23:54:52 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0tls=20sni=20?= =?UTF-8?q?=E5=97=85=E6=8E=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # tunnel/statistic/tracker.go # tunnel/tunnel.go --- common/snifer/tls/sniff.go | 148 +++++++++++++++++++++++++++++ common/snifer/tls/sniff_test.go | 159 ++++++++++++++++++++++++++++++++ component/resolver/enhancer.go | 7 ++ dns/enhancer.go | 6 ++ tunnel/statistic/tracker.go | 18 ++++ 5 files changed, 338 insertions(+) create mode 100644 common/snifer/tls/sniff.go create mode 100644 common/snifer/tls/sniff_test.go diff --git a/common/snifer/tls/sniff.go b/common/snifer/tls/sniff.go new file mode 100644 index 000000000..1471fc68d --- /dev/null +++ b/common/snifer/tls/sniff.go @@ -0,0 +1,148 @@ +package tls + +import ( + "encoding/binary" + "errors" + "strings" +) + +var ErrNoClue = errors.New("not enough information for making a decision") + +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") + 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 ErrNoClue + } + sessionIDLen := int(data[38]) + if sessionIDLen > 32 || len(data) < 39+sessionIDLen { + return ErrNoClue + } + data = data[39+sessionIDLen:] + if len(data) < 2 { + return 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 ErrNoClue + } + compressionMethodsLen := int(data[0]) + if len(data) < 1+compressionMethodsLen { + return 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 + } + + if extension == 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, ErrNoClue + } + + if b[0] != 0x16 /* TLS Handshake */ { + return nil, errNotTLS + } + if !IsValidTLSVersion(b[1], b[2]) { + return nil, errNotTLS + } + headerLen := int(binary.BigEndian.Uint16(b[3:5])) + if 5+headerLen > len(b) { + return nil, ErrNoClue + } + + h := &SniffHeader{} + err := ReadClientHello(b[5:5+headerLen], h) + if err == nil { + return h, nil + } + return nil, err +} diff --git a/common/snifer/tls/sniff_test.go b/common/snifer/tls/sniff_test.go new file mode 100644 index 000000000..26f5f1eea --- /dev/null +++ b/common/snifer/tls/sniff_test.go @@ -0,0 +1,159 @@ +package tls + +import ( + "testing" +) + +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, + }, + { + input: []byte{ + 0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1, + 0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6, + 0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d, + 0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84, + 0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08, + 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04, + 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e, + 0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, + 0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, + 0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c, + 0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01, + 0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72, + }, + domain: "dogfish", + err: false, + }, + { + input: []byte{ + 0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00, + 0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee, + 0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14, + 0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62, + 0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45, + 0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c, + 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, + 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e, + 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, + 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14, + 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, + 0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03, + 0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, + 0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, + 0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30, + 0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04, + 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a, + 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, + 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, + 0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03, + 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, + 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02, + 0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f, + 0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, + 0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28, + 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, + 0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e, + 0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f, + 0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d, + 0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36, + }, + domain: "10.42.0.243", + 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 header.Domain() != test.domain { + t.Error("expect domain ", test.domain, " but got ", header.Domain()) + } + } + } +} diff --git a/component/resolver/enhancer.go b/component/resolver/enhancer.go index 9df3f54b3..77f183743 100644 --- a/component/resolver/enhancer.go +++ b/component/resolver/enhancer.go @@ -14,6 +14,7 @@ type Enhancer interface { IsExistFakeIP(net.IP) bool FindHostByIP(net.IP) (string, bool) FlushFakeIP() error + InsertHostByIP(net.IP, string) } func FakeIPEnabled() bool { @@ -56,6 +57,12 @@ func IsExistFakeIP(ip net.IP) bool { return false } +func InsertHostByIP(ip net.IP, host string) { + if mapper := DefaultHostMapper; mapper != nil { + mapper.InsertHostByIP(ip, host) + } +} + func FindHostByIP(ip net.IP) (string, bool) { if mapper := DefaultHostMapper; mapper != nil { return mapper.FindHostByIP(ip) diff --git a/dns/enhancer.go b/dns/enhancer.go index 9bf568c7f..016ff02ac 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -74,6 +74,12 @@ func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { return "", false } +func (h *ResolverEnhancer) InsertHostByIP(ip net.IP, host string) { + if mapping := h.mapping; mapping != nil { + h.mapping.Set(ip.String(), host) + } +} + func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { if h.mapping != nil && o.mapping != nil { o.mapping.CloneTo(h.mapping) diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index 1f5f1f9cc..77713da21 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -1,11 +1,15 @@ package statistic import ( + "errors" "net" "time" + "github.com/Dreamacro/clash/common/snifer/tls" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" "github.com/gofrs/uuid" "go.uber.org/atomic" ) @@ -48,7 +52,21 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) tt.manager.PushUploaded(upload) + if tt.UploadTotal.Load() < 128 && tt.Metadata.Host == "" && (tt.Metadata.DstPort == "443" || tt.Metadata.DstPort == "8443") { + header, err := tls.SniffTLS(b) + if err != nil { + // log.Errorln("Expect no error but actually %s %s:%s:%s", err.Error(), tt.Metadata.Host, tt.Metadata.DstIP.String(), tt.Metadata.DstPort) + } else { + tt.Metadata.Host = header.Domain() + resolver.InsertHostByIP(tt.Metadata.DstIP, tt.Metadata.Host) + log.Errorln("sni %s %s", tt.Metadata.Host, tt.Metadata.DstIP.String()) + tt.manager.Leave(tt) + tt.Conn.Close() + return n, errors.New("sni update") + } + } tt.UploadTotal.Add(upload) + return n, err } From afdcb6cfc7b8be71f2ebb7ba38629becbaab1e86 Mon Sep 17 00:00:00 2001 From: fishg <1423545+fishg@users.noreply.github.com> Date: Thu, 31 Mar 2022 11:41:40 +0800 Subject: [PATCH 4/5] fix: log level ajust and lint fix --- tunnel/statistic/tracker.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index 77713da21..f213ca61b 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -8,8 +8,8 @@ import ( "github.com/Dreamacro/clash/common/snifer/tls" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/gofrs/uuid" "go.uber.org/atomic" ) @@ -57,12 +57,11 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { if err != nil { // log.Errorln("Expect no error but actually %s %s:%s:%s", err.Error(), tt.Metadata.Host, tt.Metadata.DstIP.String(), tt.Metadata.DstPort) } else { - tt.Metadata.Host = header.Domain() - resolver.InsertHostByIP(tt.Metadata.DstIP, tt.Metadata.Host) - log.Errorln("sni %s %s", tt.Metadata.Host, tt.Metadata.DstIP.String()) + resolver.InsertHostByIP(tt.Metadata.DstIP, header.Domain()) + log.Warnln("use sni update host: %s ip: %s", header.Domain(), tt.Metadata.DstIP.String()) tt.manager.Leave(tt) tt.Conn.Close() - return n, errors.New("sni update") + return n, errors.New("sni update, break current link to avoid leaks") } } tt.UploadTotal.Add(upload) From 13012a9f897adf8a7acc49c4b1f31e3fe629bc0b Mon Sep 17 00:00:00 2001 From: fishg <1423545+fishg@users.noreply.github.com> Date: Sat, 2 Apr 2022 16:03:53 +0800 Subject: [PATCH 5/5] fix: dns over proxy may due to cancel request, but proxy live status is fine --- adapter/adapter.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adapter/adapter.go b/adapter/adapter.go index 23dc304af..f40872413 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "net/url" + "strings" "time" "github.com/Dreamacro/clash/common/queue" @@ -37,7 +38,11 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) - p.alive.Store(err == nil) + wasCancel := false + if err != nil { + wasCancel = strings.Contains(err.Error(), "operation was canceled") + } + p.alive.Store(err == nil || wasCancel) return conn, err }