From ced9749104e47d717a849a4ae18fd9c6ed0a0658 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Thu, 30 Sep 2021 16:30:07 +0800 Subject: [PATCH 01/19] Fix: http proxy should response correct http version (#1651) --- listener/http/proxy.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/listener/http/proxy.go b/listener/http/proxy.go index 229106dfe..23a73739a 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -1,6 +1,7 @@ package http import ( + "fmt" "net" "net/http" "strings" @@ -43,11 +44,8 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) { if trusted { if request.Method == http.MethodConnect { - resp = responseWith(200) - resp.Status = "Connection established" - resp.ContentLength = -1 - - if resp.Write(conn) != nil { + // Manual writing to support CONNECT for http 1.0 (workaround for uplay client) + if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil { break // close connection } @@ -67,11 +65,11 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) { removeExtraHTTPHostPort(request) if request.URL.Scheme == "" || request.URL.Host == "" { - resp = responseWith(http.StatusBadRequest) + resp = responseWith(request, http.StatusBadRequest) } else { resp, err = client.Do(request) if err != nil { - resp = responseWith(http.StatusBadGateway) + resp = responseWith(request, http.StatusBadGateway) } } @@ -100,7 +98,7 @@ func authenticate(request *http.Request, cache *cache.Cache) *http.Response { if authenticator != nil { credential := parseBasicProxyAuthorization(request) if credential == "" { - resp := responseWith(http.StatusProxyAuthRequired) + resp := responseWith(request, http.StatusProxyAuthRequired) resp.Header.Set("Proxy-Authenticate", "Basic") return resp } @@ -114,20 +112,20 @@ func authenticate(request *http.Request, cache *cache.Cache) *http.Response { if !authed.(bool) { log.Infoln("Auth failed from %s", request.RemoteAddr) - return responseWith(http.StatusForbidden) + return responseWith(request, http.StatusForbidden) } } return nil } -func responseWith(statusCode int) *http.Response { +func responseWith(request *http.Request, statusCode int) *http.Response { return &http.Response{ StatusCode: statusCode, Status: http.StatusText(statusCode), - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, + Proto: request.Proto, + ProtoMajor: request.ProtoMajor, + ProtoMinor: request.ProtoMinor, Header: http.Header{}, } } From 537b672fcf9917c51003291bc73145d6713520f2 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:20:11 +0800 Subject: [PATCH 02/19] Change: use bbolt as cache db --- component/profile/cachefile/cache.go | 119 +++++++++++++++++---------- constant/path.go | 6 +- go.mod | 1 + go.sum | 3 + 4 files changed, 83 insertions(+), 46 deletions(-) diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index b23eeb97f..e78e0cca6 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -10,45 +10,40 @@ import ( "github.com/Dreamacro/clash/component/profile" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" + + bolt "go.etcd.io/bbolt" ) var ( initOnce sync.Once fileMode os.FileMode = 0666 defaultCache *CacheFile -) -type cache struct { - Selected map[string]string -} + bucketSelected = []byte("selected") +) // CacheFile store and update the cache file type CacheFile struct { - path string - model *cache - buf *bytes.Buffer - mux sync.Mutex + db *bolt.DB } func (c *CacheFile) SetSelected(group, selected string) { if !profile.StoreSelected.Load() { return - } - - c.mux.Lock() - defer c.mux.Unlock() - - model := c.element() - - model.Selected[group] = selected - c.buf.Reset() - if err := gob.NewEncoder(c.buf).Encode(model); err != nil { - log.Warnln("[CacheFile] encode gob failed: %s", err.Error()) + } else if c.db == nil { return } - if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil { - log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error()) + err := c.db.Batch(func(t *bolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketSelected) + if err != nil { + return err + } + return bucket.Put([]byte(group), []byte(selected)) + }) + + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.db.Path(), err.Error()) return } } @@ -56,46 +51,80 @@ func (c *CacheFile) SetSelected(group, selected string) { func (c *CacheFile) SelectedMap() map[string]string { if !profile.StoreSelected.Load() { return nil + } else if c.db == nil { + return nil } - c.mux.Lock() - defer c.mux.Unlock() - - model := c.element() - mapping := map[string]string{} - for k, v := range model.Selected { - mapping[k] = v - } + c.db.View(func(t *bolt.Tx) error { + bucket := t.Bucket(bucketSelected) + if bucket == nil { + return nil + } + + c := bucket.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + mapping[string(k)] = string(v) + } + return nil + }) return mapping } -func (c *CacheFile) element() *cache { - if c.model != nil { - return c.model - } +func (c *CacheFile) Close() error { + return c.db.Close() +} +func migrateCache() { + defer func() { + db, err := bolt.Open(C.Path.Cache(), fileMode, nil) + if err != nil { + log.Warnln("[CacheFile] can't open cache file: %s", err.Error()) + } + defaultCache = &CacheFile{ + db: db, + } + }() + + buf, err := ioutil.ReadFile(C.Path.OldCache()) + if err != nil { + return + } + defer os.Remove(C.Path.OldCache()) + + // read old cache file + type cache struct { + Selected map[string]string + } model := &cache{ Selected: map[string]string{}, } + bufReader := bytes.NewBuffer(buf) + gob.NewDecoder(bufReader).Decode(model) - if buf, err := ioutil.ReadFile(c.path); err == nil { - bufReader := bytes.NewBuffer(buf) - gob.NewDecoder(bufReader).Decode(model) + // write to new cache file + db, err := bolt.Open(C.Path.Cache(), fileMode, nil) + if err != nil { + return } - - c.model = model - return c.model + defer db.Close() + db.Batch(func(t *bolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketSelected) + if err != nil { + return err + } + for group, selected := range model.Selected { + if err := bucket.Put([]byte(group), []byte(selected)); err != nil { + return err + } + } + return nil + }) } // Cache return singleton of CacheFile func Cache() *CacheFile { - initOnce.Do(func() { - defaultCache = &CacheFile{ - path: C.Path.Cache(), - buf: &bytes.Buffer{}, - } - }) + initOnce.Do(migrateCache) return defaultCache } diff --git a/constant/path.go b/constant/path.go index ba0e8c23b..781cc5f7c 100644 --- a/constant/path.go +++ b/constant/path.go @@ -55,6 +55,10 @@ func (p *path) MMDB() string { return P.Join(p.homeDir, "Country.mmdb") } -func (p *path) Cache() string { +func (p *path) OldCache() string { return P.Join(p.homeDir, ".cache") } + +func (p *path) Cache() string { + return P.Join(p.homeDir, "cache.db") +} diff --git a/go.mod b/go.mod index e955449b8..dc79d77af 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/oschwald/geoip2-golang v1.5.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 + go.etcd.io/bbolt v1.3.6 go.uber.org/atomic v1.9.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f diff --git a/go.sum b/go.sum index d1d97cfc4..1fbae8ce4 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -87,6 +89,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 4f1fac02abe706926f2f8139ec1752e24649fc39 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:42:21 +0800 Subject: [PATCH 03/19] Chore: add remove TODO --- adapter/outbound/vmess.go | 36 +++++++++++++++------------- component/profile/cachefile/cache.go | 1 + test/go.mod | 1 + test/go.sum | 3 +++ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 6ca82c15e..0dbb0f1bf 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -31,23 +31,25 @@ type Vmess struct { } type VmessOption struct { - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - UUID string `proxy:"uuid"` - AlterID int `proxy:"alterId"` - Cipher string `proxy:"cipher"` - TLS bool `proxy:"tls,omitempty"` - UDP bool `proxy:"udp,omitempty"` - Network string `proxy:"network,omitempty"` - HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` - HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` - GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` - WSOpts WSOptions `proxy:"ws-opts,omitempty"` - WSPath string `proxy:"ws-path,omitempty"` - WSHeaders map[string]string `proxy:"ws-headers,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - ServerName string `proxy:"servername,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + AlterID int `proxy:"alterId"` + Cipher string `proxy:"cipher"` + UDP bool `proxy:"udp,omitempty"` + Network string `proxy:"network,omitempty"` + TLS bool `proxy:"tls,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + ServerName string `proxy:"servername,omitempty"` + HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` + GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSOpts WSOptions `proxy:"ws-opts,omitempty"` + + // TODO: remove these until 2022 + WSHeaders map[string]string `proxy:"ws-headers,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` } type HTTPOptions struct { diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index e78e0cca6..ffd588615 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -75,6 +75,7 @@ func (c *CacheFile) Close() error { return c.db.Close() } +// TODO: remove migrateCache until 2022 func migrateCache() { defer func() { db, err := bolt.Open(C.Path.Cache(), fileMode, nil) diff --git a/test/go.mod b/test/go.mod index 990edb6c5..3a8781517 100644 --- a/test/go.mod +++ b/test/go.mod @@ -36,6 +36,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect + go.etcd.io/bbolt v1.3.6 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect diff --git a/test/go.sum b/test/go.sum index 27d2c4de2..e018f54c7 100644 --- a/test/go.sum +++ b/test/go.sum @@ -629,6 +629,8 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -800,6 +802,7 @@ golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From b9d470cf79d145736fc87f73e3b7f58119e684a0 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 5 Oct 2021 13:31:19 +0800 Subject: [PATCH 04/19] Fix: dhcp client should request special interface --- dns/client.go | 13 +++++++++---- dns/dhcp.go | 7 +++++-- dns/resolver.go | 5 +++-- dns/util.go | 7 ++++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/dns/client.go b/dns/client.go index 6a54f9fad..5cb1fe029 100644 --- a/dns/client.go +++ b/dns/client.go @@ -15,9 +15,10 @@ import ( type client struct { *D.Client - r *Resolver - port string - host string + r *Resolver + port string + host string + iface string } func (c *client) Exchange(m *D.Msg) (*D.Msg, error) { @@ -45,7 +46,11 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) network = "tcp" } - conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port)) + options := []dialer.Option{} + if c.iface != "" { + options = append(options, dialer.WithInterface(c.iface)) + } + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...) if err != nil { return nil, err } diff --git a/dns/dhcp.go b/dns/dhcp.go index 94f8a36ce..f964cec8c 100644 --- a/dns/dhcp.go +++ b/dns/dhcp.go @@ -68,8 +68,11 @@ func (d *dhcpClient) resolve(ctx context.Context) (*Resolver, error) { dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName) if err == nil { nameserver := make([]NameServer, 0, len(dns)) - for _, d := range dns { - nameserver = append(nameserver, NameServer{Addr: net.JoinHostPort(d.String(), "53")}) + for _, item := range dns { + nameserver = append(nameserver, NameServer{ + Addr: net.JoinHostPort(item.String(), "53"), + Interface: d.ifaceName, + }) } res = NewResolver(Config{ diff --git a/dns/resolver.go b/dns/resolver.go index 914f1418e..8bbd0e8b2 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -302,8 +302,9 @@ func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D } type NameServer struct { - Net string - Addr string + Net string + Addr string + Interface string } type FallbackFilter struct { diff --git a/dns/util.go b/dns/util.go index 0c3f42e59..b167809a4 100644 --- a/dns/util.go +++ b/dns/util.go @@ -138,9 +138,10 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { UDPSize: 4096, Timeout: 5 * time.Second, }, - port: port, - host: host, - r: resolver, + port: port, + host: host, + iface: s.Interface, + r: resolver, }) } return ret From 66cb0b1218aecb2ea56852e6fba2933044f3dc10 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 5 Oct 2021 22:47:26 +0800 Subject: [PATCH 05/19] Fix: cache kv db should not block on init --- component/profile/cachefile/cache.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index ffd588615..28b05ee02 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "sync" + "time" "github.com/Dreamacro/clash/component/profile" C "github.com/Dreamacro/clash/constant" @@ -78,7 +79,7 @@ func (c *CacheFile) Close() error { // TODO: remove migrateCache until 2022 func migrateCache() { defer func() { - db, err := bolt.Open(C.Path.Cache(), fileMode, nil) + db, err := bolt.Open(C.Path.Cache(), fileMode, &bolt.Options{Timeout: time.Second}) if err != nil { log.Warnln("[CacheFile] can't open cache file: %s", err.Error()) } From 1996bef9e6cad6ee954facc186ea70e411eb108c Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 7 Oct 2021 22:57:55 +0800 Subject: [PATCH 06/19] Chore: doh request should with id 0 (#1660) --- dns/doh.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dns/doh.go b/dns/doh.go index 34375017d..53b0fb331 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -28,13 +28,19 @@ func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { } func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { - req, err := dc.newRequest(m) + // https://datatracker.ietf.org/doc/html/rfc8484#section-4.1 + // In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request. + newM := *m + newM.Id = 0 + req, err := dc.newRequest(&newM) if err != nil { return nil, err } req = req.WithContext(ctx) - return dc.doRequest(req) + msg, err = dc.doRequest(req) + msg.Id = m.Id + return } // newRequest returns a new DoH request given a dns.Msg. From 4ce35870fea0eeceb212f83110b9ab4be88e9281 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 9 Oct 2021 20:35:06 +0800 Subject: [PATCH 07/19] Chore: remove deprecated ioutil --- adapter/outbound/socks5.go | 3 +-- adapter/provider/fetcher.go | 5 ++--- adapter/provider/vehicle.go | 7 ++++--- component/process/process_linux.go | 14 +++++++++----- component/profile/cachefile/cache.go | 3 +-- dns/doh.go | 4 ++-- hub/executor/executor.go | 3 +-- listener/socks/tcp.go | 3 +-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 1402c6186..d97395575 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "strconv" @@ -116,7 +115,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { } go func() { - io.Copy(ioutil.Discard, c) + io.Copy(io.Discard, c) c.Close() // A UDP association terminates when the TCP connection that the UDP // ASSOCIATE request arrived on terminates. RFC1928 diff --git a/adapter/provider/fetcher.go b/adapter/provider/fetcher.go index 777e3aa54..d3c85ab56 100644 --- a/adapter/provider/fetcher.go +++ b/adapter/provider/fetcher.go @@ -3,7 +3,6 @@ package provider import ( "bytes" "crypto/md5" - "io/ioutil" "os" "path/filepath" "time" @@ -45,7 +44,7 @@ func (f *fetcher) Initial() (interface{}, error) { isLocal bool ) if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { - buf, err = ioutil.ReadFile(f.vehicle.Path()) + buf, err = os.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() f.updatedAt = &modTime isLocal = true @@ -165,7 +164,7 @@ func safeWrite(path string, buf []byte) error { } } - return ioutil.WriteFile(path, buf, fileMode) + return os.WriteFile(path, buf, fileMode) } func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(interface{})) *fetcher { diff --git a/adapter/provider/vehicle.go b/adapter/provider/vehicle.go index f556e69c4..4f08c317e 100644 --- a/adapter/provider/vehicle.go +++ b/adapter/provider/vehicle.go @@ -2,10 +2,11 @@ package provider import ( "context" - "io/ioutil" + "io" "net" "net/http" "net/url" + "os" "time" "github.com/Dreamacro/clash/component/dialer" @@ -25,7 +26,7 @@ func (f *FileVehicle) Path() string { } func (f *FileVehicle) Read() ([]byte, error) { - return ioutil.ReadFile(f.path) + return os.ReadFile(f.path) } func NewFileVehicle(path string) *FileVehicle { @@ -84,7 +85,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) { } defer resp.Body.Close() - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/component/process/process_linux.go b/component/process/process_linux.go index fabe969c8..f77c10597 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "fmt" "io" - "io/ioutil" "net" + "os" "path" "path/filepath" "syscall" @@ -167,7 +167,7 @@ func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { } func resolveProcessNameByProcSearch(inode, uid int) (string, error) { - files, err := ioutil.ReadDir(pathProc) + files, err := os.ReadDir(pathProc) if err != nil { return "", err } @@ -180,14 +180,18 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) { continue } - if f.Sys().(*syscall.Stat_t).Uid != uint32(uid) { + info, err := f.Info() + if err != nil { + return "", err + } + if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) { continue } processPath := path.Join(pathProc, f.Name()) fdPath := path.Join(processPath, "fd") - fds, err := ioutil.ReadDir(fdPath) + fds, err := os.ReadDir(fdPath) if err != nil { continue } @@ -199,7 +203,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) { } if bytes.Equal(buffer[:n], socket) { - cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline")) + cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) if err != nil { return "", err } diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index 28b05ee02..a9d1bc5b9 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -3,7 +3,6 @@ package cachefile import ( "bytes" "encoding/gob" - "io/ioutil" "os" "sync" "time" @@ -88,7 +87,7 @@ func migrateCache() { } }() - buf, err := ioutil.ReadFile(C.Path.OldCache()) + buf, err := os.ReadFile(C.Path.OldCache()) if err != nil { return } diff --git a/dns/doh.go b/dns/doh.go index 53b0fb331..3daa7f22b 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -3,7 +3,7 @@ package dns import ( "bytes" "context" - "io/ioutil" + "io" "net" "net/http" @@ -68,7 +68,7 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { } defer resp.Body.Close() - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 5c002b64f..2147906f1 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,7 +2,6 @@ package executor import ( "fmt" - "io/ioutil" "os" "sync" @@ -33,7 +32,7 @@ func readConfig(path string) ([]byte, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { return nil, err } diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index 49d0a1e31..29016f5b1 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -2,7 +2,6 @@ package socks import ( "io" - "io/ioutil" "net" "github.com/Dreamacro/clash/adapter/inbound" @@ -102,7 +101,7 @@ func HandleSocks5(conn net.Conn, in chan<- C.ConnContext) { } if command == socks5.CmdUDPAssociate { defer conn.Close() - io.Copy(ioutil.Discard, conn) + io.Copy(io.Discard, conn) return } in <- inbound.NewSocket(target, conn, C.SOCKS5) From f1cf7e926972e8d0a3e2042bec89f0175d987e39 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 10 Oct 2021 23:44:09 +0800 Subject: [PATCH 08/19] Style: use gofumpt for fmt --- .github/workflows/go.yml | 5 ++--- Makefile | 4 ++++ adapter/provider/fetcher.go | 4 ++-- adapter/provider/parser.go | 4 +--- common/cache/lrucache_test.go | 1 - common/murmur3/murmur32.go | 1 - common/observable/observable_test.go | 2 +- common/pool/alloc.go | 1 + common/singledo/singledo_test.go | 2 +- common/structure/structure_test.go | 6 ++++-- component/dialer/options.go | 4 +--- component/iface/iface.go | 6 ++++-- component/mmdb/mmdb.go | 6 ++++-- component/process/process_linux.go | 6 ++++-- component/profile/cachefile/cache.go | 3 +-- component/profile/profile.go | 6 ++---- component/trie/domain.go | 6 ++---- config/initial.go | 6 +++--- dns/filters.go | 1 + dns/middleware.go | 6 ++++-- dns/resolver.go | 1 - dns/util.go | 14 ++++++-------- hub/executor/executor.go | 4 +--- listener/auth/auth.go | 4 +--- log/level.go | 18 ++++++++---------- transport/gun/gun.go | 10 ++++------ transport/simple-obfs/tls.go | 1 + transport/snell/cipher.go | 1 + transport/snell/snell.go | 4 +--- transport/socks4/socks4.go | 2 +- transport/ssr/protocol/auth_aes128_sha1.go | 6 ++++-- transport/vmess/header.go | 2 +- transport/vmess/websocket.go | 1 + tunnel/mode.go | 14 ++++++-------- 34 files changed, 78 insertions(+), 84 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1058dd4f4..af6f9a91d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -30,9 +30,8 @@ jobs: - name: Get dependencies, run test and static check run: | go test ./... - go vet ./... - go install honnef.co/go/tools/cmd/staticcheck@latest - staticcheck -- $(go list ./...) + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... - name: Build if: startsWith(github.ref, 'refs/tags/') diff --git a/Makefile b/Makefile index 97328bc23..b6a5b22af 100644 --- a/Makefile +++ b/Makefile @@ -112,5 +112,9 @@ $(zip_releases): %.zip : % all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) releases: $(gz_releases) $(zip_releases) + +lint: + golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... + clean: rm $(BINDIR)/* diff --git a/adapter/provider/fetcher.go b/adapter/provider/fetcher.go index d3c85ab56..6c1e96b43 100644 --- a/adapter/provider/fetcher.go +++ b/adapter/provider/fetcher.go @@ -12,8 +12,8 @@ import ( ) var ( - fileMode os.FileMode = 0666 - dirMode os.FileMode = 0755 + fileMode os.FileMode = 0o666 + dirMode os.FileMode = 0o755 ) type parser = func([]byte) (interface{}, error) diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index f326d4ccb..8a1739669 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -10,9 +10,7 @@ import ( types "github.com/Dreamacro/clash/constant/provider" ) -var ( - errVehicleType = errors.New("unsupport vehicle type") -) +var errVehicleType = errors.New("unsupport vehicle type") type healthCheckSchema struct { Enable bool `provider:"enable"` diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go index 13675bff3..8a04f7464 100644 --- a/common/cache/lrucache_test.go +++ b/common/cache/lrucache_test.go @@ -149,7 +149,6 @@ func TestSetWithExpire(t *testing.T) { assert.Equal(t, nil, res) assert.Equal(t, time.Time{}, expires) assert.Equal(t, false, exist) - } func TestStale(t *testing.T) { diff --git a/common/murmur3/murmur32.go b/common/murmur3/murmur32.go index 9861eecd8..e52b7937d 100644 --- a/common/murmur3/murmur32.go +++ b/common/murmur3/murmur32.go @@ -67,7 +67,6 @@ func (d *digest32) bmix(p []byte) (tail []byte) { } func (d *digest32) Sum32() (h1 uint32) { - h1 = d.h1 var k1 uint32 diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index cb16ad399..b70feec87 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -38,7 +38,7 @@ func TestObservable_MultiSubscribe(t *testing.T) { src := NewObservable(iter) ch1, _ := src.Subscribe() ch2, _ := src.Subscribe() - var count = atomic.NewInt32(0) + count := atomic.NewInt32(0) var wg sync.WaitGroup wg.Add(2) diff --git a/common/pool/alloc.go b/common/pool/alloc.go index 6ae53c1db..710639af8 100644 --- a/common/pool/alloc.go +++ b/common/pool/alloc.go @@ -53,6 +53,7 @@ func (alloc *Allocator) Put(buf []byte) error { } //lint:ignore SA6002 ignore temporarily + //nolint alloc.buffers[bits].Put(buf) return nil } diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index 2b0d5988b..c85795c20 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -12,7 +12,7 @@ import ( func TestBasic(t *testing.T) { single := NewSingle(time.Millisecond * 30) foo := 0 - var shardCount = atomic.NewInt32(0) + shardCount := atomic.NewInt32(0) call := func() (interface{}, error) { foo++ time.Sleep(time.Millisecond * 5) diff --git a/common/structure/structure_test.go b/common/structure/structure_test.go index 6e3e6290d..0feef28e8 100644 --- a/common/structure/structure_test.go +++ b/common/structure/structure_test.go @@ -5,8 +5,10 @@ import ( "testing" ) -var decoder = NewDecoder(Option{TagName: "test"}) -var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) +var ( + decoder = NewDecoder(Option{TagName: "test"}) + weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) +) type Baz struct { Foo int `test:"foo"` diff --git a/component/dialer/options.go b/component/dialer/options.go index 4b5d64c0e..33083864a 100644 --- a/component/dialer/options.go +++ b/component/dialer/options.go @@ -1,8 +1,6 @@ package dialer -var ( - DefaultOptions []Option -) +var DefaultOptions []Option type config struct { skipDefault bool diff --git a/component/iface/iface.go b/component/iface/iface.go index 30c6a6bcb..4e9162601 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -15,8 +15,10 @@ type Interface struct { HardwareAddr net.HardwareAddr } -var ErrIfaceNotFound = errors.New("interface not found") -var ErrAddrNotFound = errors.New("addr not found") +var ( + ErrIfaceNotFound = errors.New("interface not found") + ErrAddrNotFound = errors.New("addr not found") +) var interfaces = singledo.NewSingle(time.Second * 20) diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 08743985a..e120055d8 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -9,8 +9,10 @@ import ( "github.com/oschwald/geoip2-golang" ) -var mmdb *geoip2.Reader -var once sync.Once +var ( + mmdb *geoip2.Reader + once sync.Once +) func LoadFromBytes(buffer []byte) { once.Do(func() { diff --git a/component/process/process_linux.go b/component/process/process_linux.go index f77c10597..be758a692 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -25,8 +25,10 @@ var nativeEndian = func() binary.ByteOrder { return binary.LittleEndian }() -type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) -type ProcessNameResolver func(inode, uid int) (name string, err error) +type ( + SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) + ProcessNameResolver func(inode, uid int) (name string, err error) +) // export for android var ( diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index a9d1bc5b9..03f389a3a 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -16,7 +16,7 @@ import ( var ( initOnce sync.Once - fileMode os.FileMode = 0666 + fileMode os.FileMode = 0o666 defaultCache *CacheFile bucketSelected = []byte("selected") @@ -41,7 +41,6 @@ func (c *CacheFile) SetSelected(group, selected string) { } return bucket.Put([]byte(group), []byte(selected)) }) - if err != nil { log.Warnln("[CacheFile] write cache to %s failed: %s", c.db.Path(), err.Error()) return diff --git a/component/profile/profile.go b/component/profile/profile.go index ca9b1b884..e3d9e78cf 100644 --- a/component/profile/profile.go +++ b/component/profile/profile.go @@ -4,7 +4,5 @@ import ( "go.uber.org/atomic" ) -var ( - // StoreSelected is a global switch for storing selected proxy to cache - StoreSelected = atomic.NewBool(true) -) +// StoreSelected is a global switch for storing selected proxy to cache +var StoreSelected = atomic.NewBool(true) diff --git a/component/trie/domain.go b/component/trie/domain.go index b4de4a707..ffd0b754a 100644 --- a/component/trie/domain.go +++ b/component/trie/domain.go @@ -12,10 +12,8 @@ const ( domainStep = "." ) -var ( - // ErrInvalidDomain means insert domain is invalid - ErrInvalidDomain = errors.New("invalid domain") -) +// ErrInvalidDomain means insert domain is invalid +var ErrInvalidDomain = errors.New("invalid domain") // DomainTrie contains the main logic for adding and searching nodes for domain segments. // support wildcard domain (e.g *.google.com) diff --git a/config/initial.go b/config/initial.go index df8452b93..9d1a2db10 100644 --- a/config/initial.go +++ b/config/initial.go @@ -18,7 +18,7 @@ func downloadMMDB(path string) (err error) { } defer resp.Body.Close() - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return err } @@ -54,7 +54,7 @@ func initMMDB() error { func Init(dir string) error { // initial homedir if _, err := os.Stat(dir); os.IsNotExist(err) { - if err := os.MkdirAll(dir, 0777); err != nil { + if err := os.MkdirAll(dir, 0o777); err != nil { return fmt.Errorf("can't create config directory %s: %s", dir, err.Error()) } } @@ -62,7 +62,7 @@ func Init(dir string) error { // initial config.yaml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { log.Infoln("Can't find config, create a initial config file") - f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) } diff --git a/dns/filters.go b/dns/filters.go index bacd7727f..30825a44a 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -32,6 +32,7 @@ func (inf *ipnetFilter) Match(ip net.IP) bool { type fallbackDomainFilter interface { Match(domain string) bool } + type domainFilter struct { tree *trie.DomainTrie } diff --git a/dns/middleware.go b/dns/middleware.go index 782c0ef07..da84788d7 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -14,8 +14,10 @@ import ( D "github.com/miekg/dns" ) -type handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) -type middleware func(next handler) handler +type ( + handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) + middleware func(next handler) handler +) func withHosts(hosts *trie.DomainTrie) middleware { return func(next handler) handler { diff --git a/dns/resolver.go b/dns/resolver.go index 8bbd0e8b2..bf544cb99 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -215,7 +215,6 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { } func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { - if matched := r.matchPolicy(m); len(matched) != 0 { res := <-r.asyncExchange(ctx, matched, m) return res.Msg, res.Error diff --git a/dns/util.go b/dns/util.go index b167809a4..0e1badabc 100644 --- a/dns/util.go +++ b/dns/util.go @@ -13,14 +13,12 @@ import ( D "github.com/miekg/dns" ) -var ( - // EnhancedModeMapping is a mapping for EnhancedMode enum - EnhancedModeMapping = map[string]EnhancedMode{ - NORMAL.String(): NORMAL, - FAKEIP.String(): FAKEIP, - MAPPING.String(): MAPPING, - } -) +// EnhancedModeMapping is a mapping for EnhancedMode enum +var EnhancedModeMapping = map[string]EnhancedMode{ + NORMAL.String(): NORMAL, + FAKEIP.String(): FAKEIP, + MAPPING.String(): MAPPING, +} const ( NORMAL EnhancedMode = iota diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 2147906f1..ca8ee95de 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -24,9 +24,7 @@ import ( "github.com/Dreamacro/clash/tunnel" ) -var ( - mux sync.Mutex -) +var mux sync.Mutex func readConfig(path string) ([]byte, error) { if _, err := os.Stat(path); os.IsNotExist(err) { diff --git a/listener/auth/auth.go b/listener/auth/auth.go index 2c29e186e..70473114d 100644 --- a/listener/auth/auth.go +++ b/listener/auth/auth.go @@ -4,9 +4,7 @@ import ( "github.com/Dreamacro/clash/component/auth" ) -var ( - authenticator auth.Authenticator -) +var authenticator auth.Authenticator func Authenticator() auth.Authenticator { return authenticator diff --git a/log/level.go b/log/level.go index ea223c127..f70116a32 100644 --- a/log/level.go +++ b/log/level.go @@ -5,16 +5,14 @@ import ( "errors" ) -var ( - // LogLevelMapping is a mapping for LogLevel enum - LogLevelMapping = map[string]LogLevel{ - ERROR.String(): ERROR, - WARNING.String(): WARNING, - INFO.String(): INFO, - DEBUG.String(): DEBUG, - SILENT.String(): SILENT, - } -) +// LogLevelMapping is a mapping for LogLevel enum +var LogLevelMapping = map[string]LogLevel{ + ERROR.String(): ERROR, + WARNING.String(): WARNING, + INFO.String(): INFO, + DEBUG.String(): DEBUG, + SILENT.String(): SILENT, +} const ( DEBUG LogLevel = iota diff --git a/transport/gun/gun.go b/transport/gun/gun.go index e60d13d75..f6f761166 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -27,12 +27,10 @@ var ( ErrSmallBuffer = errors.New("buffer too small") ) -var ( - defaultHeader = http.Header{ - "content-type": []string{"application/grpc"}, - "user-agent": []string{"grpc-go/1.36.0"}, - } -) +var defaultHeader = http.Header{ + "content-type": []string{"application/grpc"}, + "user-agent": []string{"grpc-go/1.36.0"}, +} type DialFn = func(network, addr string) (net.Conn, error) diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 914cfe4af..1c609c15a 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -78,6 +78,7 @@ func (to *TLSObfs) Read(b []byte) (int, error) { // type + ver = 3 return to.read(b, 3) } + func (to *TLSObfs) Write(b []byte) (int, error) { length := len(b) for i := 0; i < length; i += chunkSize { diff --git a/transport/snell/cipher.go b/transport/snell/cipher.go index f778e6471..0f31aea53 100644 --- a/transport/snell/cipher.go +++ b/transport/snell/cipher.go @@ -20,6 +20,7 @@ func (sc *snellCipher) SaltSize() int { return 16 } func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) } + func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) } diff --git a/transport/snell/snell.go b/transport/snell/snell.go index 8966b4bb7..64807b819 100644 --- a/transport/snell/snell.go +++ b/transport/snell/snell.go @@ -30,9 +30,7 @@ const ( Version byte = 1 ) -var ( - endSignal = []byte{} -) +var endSignal = []byte{} type Snell struct { net.Conn diff --git a/transport/socks4/socks4.go b/transport/socks4/socks4.go index c06bea20f..a29416241 100644 --- a/transport/socks4/socks4.go +++ b/transport/socks4/socks4.go @@ -184,7 +184,7 @@ func isReservedIP(ip net.IP) bool { } func readUntilNull(r io.Reader) ([]byte, error) { - var buf = &bytes.Buffer{} + buf := &bytes.Buffer{} var data [1]byte for { diff --git a/transport/ssr/protocol/auth_aes128_sha1.go b/transport/ssr/protocol/auth_aes128_sha1.go index fab9d0081..8ce57d288 100644 --- a/transport/ssr/protocol/auth_aes128_sha1.go +++ b/transport/ssr/protocol/auth_aes128_sha1.go @@ -14,8 +14,10 @@ import ( "github.com/Dreamacro/clash/transport/ssr/tools" ) -type hmacMethod func(key, data []byte) []byte -type hashDigestMethod func([]byte) []byte +type ( + hmacMethod func(key, data []byte) []byte + hashDigestMethod func([]byte) []byte +) func init() { register("auth_aes128_sha1", newAuthAES128SHA1, 9) diff --git a/transport/vmess/header.go b/transport/vmess/header.go index 27a734bed..67d407e6f 100644 --- a/transport/vmess/header.go +++ b/transport/vmess/header.go @@ -92,7 +92,7 @@ func sealVMessAEADHeader(key [16]byte, data []byte, t time.Time) []byte { payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:]) } - var outputBuffer = &bytes.Buffer{} + outputBuffer := &bytes.Buffer{} outputBuffer.Write(generatedAuthID[:]) outputBuffer.Write(payloadHeaderLengthAEADEncrypted) diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index f00e4c4bf..951fb5d0a 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -28,6 +28,7 @@ type websocketConn struct { rMux sync.Mutex wMux sync.Mutex } + type websocketWithEarlyDataConn struct { net.Conn underlay net.Conn diff --git a/tunnel/mode.go b/tunnel/mode.go index 6e07a060a..696607215 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -8,14 +8,12 @@ import ( type TunnelMode int -var ( - // ModeMapping is a mapping for Mode enum - ModeMapping = map[string]TunnelMode{ - Global.String(): Global, - Rule.String(): Rule, - Direct.String(): Direct, - } -) +// ModeMapping is a mapping for Mode enum +var ModeMapping = map[string]TunnelMode{ + Global.String(): Global, + Rule.String(): Rule, + Direct.String(): Direct, +} const ( Global TunnelMode = iota From a1c2478e74a86b7160c558aef1ae6980a7d36f88 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 11 Oct 2021 20:08:18 +0800 Subject: [PATCH 09/19] Chore: actions split lint and release --- .github/workflows/linter.yml | 12 ++++++++++++ .github/workflows/{go.yml => release.yml} | 10 +++------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/linter.yml rename .github/workflows/{go.yml => release.yml} (81%) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 000000000..26d318c30 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,12 @@ +name: Linter +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: latest + args: --disable-all -E govet -E gofumpt -E megacheck ./... diff --git a/.github/workflows/go.yml b/.github/workflows/release.yml similarity index 81% rename from .github/workflows/go.yml rename to .github/workflows/release.yml index af6f9a91d..d4bad8a37 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,7 @@ -name: Go -on: [push, pull_request] +name: Release +on: [push] jobs: - build: - name: Build runs-on: ubuntu-latest steps: - name: Get latest go version @@ -27,11 +25,9 @@ jobs: restore-keys: | ${{ runner.os }}-go- - - name: Get dependencies, run test and static check + - name: Get dependencies, run test run: | go test ./... - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... - name: Build if: startsWith(github.ref, 'refs/tags/') From 3d5681cffd371ecdb9b340fd963872e8416d32e9 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Mon, 11 Oct 2021 20:48:58 +0800 Subject: [PATCH 10/19] Feature: persistence fakeip (#1662) --- component/fakeip/cachefile.go | 49 ++++++++++ component/fakeip/memory.go | 60 ++++++++++++ component/fakeip/pool.go | 95 ++++++++++-------- component/fakeip/pool_test.go | 141 ++++++++++++++++++++++++--- component/profile/cachefile/cache.go | 75 +++++++++++--- config/config.go | 13 ++- dns/enhancer.go | 2 +- dns/middleware.go | 2 +- 8 files changed, 364 insertions(+), 73 deletions(-) create mode 100644 component/fakeip/cachefile.go create mode 100644 component/fakeip/memory.go diff --git a/component/fakeip/cachefile.go b/component/fakeip/cachefile.go new file mode 100644 index 000000000..7ee889812 --- /dev/null +++ b/component/fakeip/cachefile.go @@ -0,0 +1,49 @@ +package fakeip + +import ( + "net" + + "github.com/Dreamacro/clash/component/profile/cachefile" +) + +type cachefileStore struct { + cache *cachefile.CacheFile +} + +// GetByHost implements store.GetByHost +func (c *cachefileStore) GetByHost(host string) (net.IP, bool) { + elm := c.cache.GetFakeip([]byte(host)) + if elm == nil { + return nil, false + } + return net.IP(elm), true +} + +// PutByHost implements store.PutByHost +func (c *cachefileStore) PutByHost(host string, ip net.IP) { + c.cache.PutFakeip([]byte(host), ip) +} + +// GetByIP implements store.GetByIP +func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) { + elm := c.cache.GetFakeip(ip.To4()) + if elm == nil { + return "", false + } + return string(elm), true +} + +// PutByIP implements store.PutByIP +func (c *cachefileStore) PutByIP(ip net.IP, host string) { + c.cache.PutFakeip(ip.To4(), []byte(host)) +} + +// Exist implements store.Exist +func (c *cachefileStore) Exist(ip net.IP) bool { + _, exist := c.GetByIP(ip) + return exist +} + +// CloneTo implements store.CloneTo +// already persistence +func (c *cachefileStore) CloneTo(store store) {} diff --git a/component/fakeip/memory.go b/component/fakeip/memory.go new file mode 100644 index 000000000..75d4a3b24 --- /dev/null +++ b/component/fakeip/memory.go @@ -0,0 +1,60 @@ +package fakeip + +import ( + "net" + + "github.com/Dreamacro/clash/common/cache" +) + +type memoryStore struct { + cache *cache.LruCache +} + +// GetByHost implements store.GetByHost +func (m *memoryStore) GetByHost(host string) (net.IP, bool) { + if elm, exist := m.cache.Get(host); exist { + ip := elm.(net.IP) + + // ensure ip --> host on head of linked list + m.cache.Get(ipToUint(ip.To4())) + return ip, true + } + + return nil, false +} + +// PutByHost implements store.PutByHost +func (m *memoryStore) PutByHost(host string, ip net.IP) { + m.cache.Set(host, ip) +} + +// GetByIP implements store.GetByIP +func (m *memoryStore) GetByIP(ip net.IP) (string, bool) { + if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist { + host := elm.(string) + + // ensure host --> ip on head of linked list + m.cache.Get(host) + return host, true + } + + return "", false +} + +// PutByIP implements store.PutByIP +func (m *memoryStore) PutByIP(ip net.IP, host string) { + m.cache.Set(ipToUint(ip.To4()), host) +} + +// Exist implements store.Exist +func (m *memoryStore) Exist(ip net.IP) bool { + return m.cache.Exist(ipToUint(ip.To4())) +} + +// CloneTo implements store.CloneTo +// only for memoryStore to memoryStore +func (m *memoryStore) CloneTo(store store) { + if ms, ok := store.(*memoryStore); ok { + m.cache.CloneTo(ms.cache) + } +} diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 97d812ac3..180d5eb18 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -6,9 +6,19 @@ import ( "sync" "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/trie" ) +type store interface { + GetByHost(host string) (net.IP, bool) + PutByHost(host string, ip net.IP) + GetByIP(ip net.IP) (string, bool) + PutByIP(ip net.IP, host string) + Exist(ip net.IP) bool + CloneTo(store) +} + // Pool is a implementation about fake ip generator without storage type Pool struct { max uint32 @@ -18,25 +28,19 @@ type Pool struct { mux sync.Mutex host *trie.DomainTrie ipnet *net.IPNet - cache *cache.LruCache + store store } // Lookup return a fake ip with host func (p *Pool) Lookup(host string) net.IP { p.mux.Lock() defer p.mux.Unlock() - if elm, exist := p.cache.Get(host); exist { - ip := elm.(net.IP) - - // ensure ip --> host on head of linked list - n := ipToUint(ip.To4()) - offset := n - p.min + 1 - p.cache.Get(offset) + if ip, exist := p.store.GetByHost(host); exist { return ip } ip := p.get(host) - p.cache.Set(host, ip) + p.store.PutByHost(host, ip) return ip } @@ -49,22 +53,11 @@ func (p *Pool) LookBack(ip net.IP) (string, bool) { return "", false } - n := ipToUint(ip.To4()) - offset := n - p.min + 1 - - if elm, exist := p.cache.Get(offset); exist { - host := elm.(string) - - // ensure host --> ip on head of linked list - p.cache.Get(host) - return host, true - } - - return "", false + return p.store.GetByIP(ip) } -// LookupHost return if domain in host -func (p *Pool) LookupHost(domain string) bool { +// ShouldSkipped return if domain should be skipped +func (p *Pool) ShouldSkipped(domain string) bool { if p.host == nil { return false } @@ -80,9 +73,7 @@ func (p *Pool) Exist(ip net.IP) bool { return false } - n := ipToUint(ip.To4()) - offset := n - p.min + 1 - return p.cache.Exist(offset) + return p.store.Exist(ip) } // Gateway return gateway ip @@ -95,9 +86,9 @@ func (p *Pool) IPNet() *net.IPNet { return p.ipnet } -// PatchFrom clone cache from old pool -func (p *Pool) PatchFrom(o *Pool) { - o.cache.CloneTo(p.cache) +// CloneFrom clone cache from old pool +func (p *Pool) CloneFrom(o *Pool) { + o.store.CloneTo(p.store) } func (p *Pool) get(host string) net.IP { @@ -109,12 +100,13 @@ func (p *Pool) get(host string) net.IP { break } - if !p.cache.Exist(p.offset) { + ip := uintToIP(p.min + p.offset - 1) + if !p.store.Exist(ip) { break } } ip := uintToIP(p.min + p.offset - 1) - p.cache.Set(p.offset, host) + p.store.PutByIP(ip, host) return ip } @@ -130,11 +122,24 @@ func uintToIP(v uint32) net.IP { return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)} } -// New return Pool instance -func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { - min := ipToUint(ipnet.IP) + 2 +type Options struct { + IPNet *net.IPNet + Host *trie.DomainTrie - ones, bits := ipnet.Mask.Size() + // Size sets the maximum number of entries in memory + // and does not work if Persistence is true + Size int + + // Persistence will save the data to disk. + // Size will not work and record will be fully stored. + Persistence bool +} + +// New return Pool instance +func New(options Options) (*Pool, error) { + min := ipToUint(options.IPNet.IP) + 2 + + ones, bits := options.IPNet.Mask.Size() total := 1< Date: Mon, 11 Oct 2021 21:05:38 +0800 Subject: [PATCH 11/19] Fix: #1660 panic --- dns/doh.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dns/doh.go b/dns/doh.go index 3daa7f22b..943123551 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -39,7 +39,9 @@ func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, req = req.WithContext(ctx) msg, err = dc.doRequest(req) - msg.Id = m.Id + if err == nil { + msg.Id = m.Id + } return } From 583b2a5ace5be91abf64c2073275d2bf4ca61e46 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 14 Oct 2021 22:54:43 +0800 Subject: [PATCH 12/19] Change: use interface HardwareAddr for dhcp discovery --- component/dhcp/dhcp.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/component/dhcp/dhcp.go b/component/dhcp/dhcp.go index b2ca6928f..b7e9f5069 100644 --- a/component/dhcp/dhcp.go +++ b/component/dhcp/dhcp.go @@ -3,9 +3,10 @@ package dhcp import ( "context" "errors" - "math/rand" "net" + "github.com/Dreamacro/clash/component/iface" + "github.com/insomniacslk/dhcp/dhcpv4" ) @@ -23,7 +24,12 @@ func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, er result := make(chan []net.IP, 1) - discovery, err := dhcpv4.NewDiscovery(randomHardware(), dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) + ifaceObj, err := iface.ResolveInterface(ifaceName) + if err != nil { + return nil, err + } + + discovery, err := dhcpv4.NewDiscovery(ifaceObj.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) if err != nil { return nil, err } @@ -80,15 +86,3 @@ func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- [] return } } - -func randomHardware() net.HardwareAddr { - addr := make(net.HardwareAddr, 6) - - addr[0] = 0xff - - for i := 1; i < len(addr); i++ { - addr[i] = byte(rand.Intn(254) + 1) - } - - return addr -} From 68753b4ae11c7ca508bf422180e248b3ccd81068 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 15 Oct 2021 21:44:53 +0800 Subject: [PATCH 13/19] Chore: contexify ProxyAdapter ListenPacket --- .gitignore | 5 ++++- adapter/adapter.go | 18 +++++++++++++++--- adapter/outbound/base.go | 5 +++-- adapter/outbound/direct.go | 6 +++--- adapter/outbound/reject.go | 4 ++-- adapter/outbound/shadowsocks.go | 6 +++--- adapter/outbound/shadowsocksr.go | 6 +++--- adapter/outbound/socks5.go | 8 +++----- adapter/outbound/trojan.go | 6 ++---- adapter/outbound/vmess.go | 6 ++---- adapter/outboundgroup/fallback.go | 6 +++--- adapter/outboundgroup/loadbalance.go | 7 +++---- adapter/outboundgroup/selector.go | 6 +++--- adapter/outboundgroup/urltest.go | 6 +++--- constant/adapters.go | 19 +++++++++++++------ constant/provider/interface.go | 2 +- test/Makefile | 8 ++++++++ test/README.md | 4 ++-- test/clash_test.go | 7 ++++--- tunnel/tunnel.go | 27 ++++++++++++++++----------- 20 files changed, 96 insertions(+), 66 deletions(-) create mode 100644 test/Makefile diff --git a/.gitignore b/.gitignore index 0593cfd05..52efcc9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ bin/* # Output of the go coverage tool, specifically when used with LiteIDE *.out -# dep +# go mod vendor vendor # GoLand @@ -20,3 +20,6 @@ vendor # macOS file .DS_Store + +# test suite +test/config/cache* diff --git a/adapter/adapter.go b/adapter/adapter.go index 526866a5c..263301636 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -36,12 +36,24 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { conn, err := p.ProxyAdapter.DialContext(ctx, metadata) - if err != nil { - p.alive.Store(false) - } + p.alive.Store(err == nil) return conn, err } +// DialUDP implements C.ProxyAdapter +func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) + defer cancel() + return p.ListenPacketContext(ctx, metadata) +} + +// ListenPacketContext implements C.ProxyAdapter +func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata) + p.alive.Store(err == nil) + return pc, err +} + // DelayHistory implements C.Proxy func (p *Proxy) DelayHistory() []C.DelayHistory { queue := p.history.Copy() diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index a001d799e..22c0142b0 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -1,6 +1,7 @@ package outbound import ( + "context" "encoding/json" "errors" "net" @@ -30,8 +31,8 @@ func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return c, errors.New("no support") } -// DialUDP implements C.ProxyAdapter -func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { +// ListenPacketContext implements C.ProxyAdapter +func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { return nil, errors.New("no support") } diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 4b53e306c..42433c410 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -22,9 +22,9 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return NewConn(c, d), nil } -// DialUDP implements C.ProxyAdapter -func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket(context.Background(), "udp", "") +// ListenPacketContext implements C.ProxyAdapter +func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "") if err != nil { return nil, err } diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index 367504964..a97c6c711 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -19,8 +19,8 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return NewConn(&NopConn{}, r), nil } -// DialUDP implements C.ProxyAdapter -func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { +// ListenPacketContext implements C.ProxyAdapter +func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { return nil, errors.New("match reject rule") } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 64b431a9b..0194954e7 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -87,9 +87,9 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ return NewConn(c, ss), err } -// DialUDP implements C.ProxyAdapter -func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket(context.Background(), "udp", "") +// ListenPacketContext implements C.ProxyAdapter +func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "") if err != nil { return nil, err } diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index 9ba8bc229..fb0dd7a5f 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -72,9 +72,9 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) return NewConn(c, ssr), err } -// DialUDP implements C.ProxyAdapter -func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket(context.Background(), "udp", "") +// ListenPacketContext implements C.ProxyAdapter +func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "") if err != nil { return nil, err } diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index d97395575..7714c5c52 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -76,10 +76,8 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Co return NewConn(c, ss), nil } -// DialUDP implements C.ProxyAdapter -func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { - ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) - defer cancel() +// ListenPacketContext implements C.ProxyAdapter +func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { c, err := dialer.DialContext(ctx, "tcp", ss.addr) if err != nil { err = fmt.Errorf("%s connect error: %w", ss.addr, err) @@ -109,7 +107,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { return } - pc, err := dialer.ListenPacket(context.Background(), "udp", "") + pc, err := dialer.ListenPacket(ctx, "udp", "") if err != nil { return } diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index afed410fb..b8ea49e65 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -88,8 +88,8 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con return NewConn(c, t), err } -// DialUDP implements C.ProxyAdapter -func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { +// ListenPacketContext implements C.ProxyAdapter +func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { var c net.Conn // grpc transport @@ -100,8 +100,6 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { } defer safeConnClose(c, err) } else { - ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) - defer cancel() c, err = dialer.DialContext(ctx, "tcp", t.addr) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 0dbb0f1bf..209084ca8 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -215,8 +215,8 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn return NewConn(c, v), err } -// DialUDP implements C.ProxyAdapter -func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { +// ListenPacketContext implements C.ProxyAdapter +func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIP(metadata.Host) @@ -237,8 +237,6 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { c, err = v.client.StreamConn(c, parseVmessAddr(metadata)) } else { - ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) - defer cancel() c, err = dialer.DialContext(ctx, "tcp", v.addr) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index da01d4de1..3221b5527 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -32,10 +32,10 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con return c, err } -// DialUDP implements C.ProxyAdapter -func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { +// ListenPacketContext implements C.ProxyAdapter +func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { proxy := f.findAliveProxy(true) - pc, err := proxy.DialUDP(metadata) + pc, err := proxy.ListenPacketContext(ctx, metadata) if err == nil { pc.AppendToChains(f) } diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index e529b53be..fb2840102 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -82,8 +82,8 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c return } -// DialUDP implements C.ProxyAdapter -func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { +// ListenPacketContext implements C.ProxyAdapter +func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (pc C.PacketConn, err error) { defer func() { if err == nil { pc.AppendToChains(lb) @@ -91,8 +91,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error }() proxy := lb.Unwrap(metadata) - - return proxy.DialUDP(metadata) + return proxy.ListenPacketContext(ctx, metadata) } // SupportUDP implements C.ProxyAdapter diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 124539278..008e8af8d 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -28,9 +28,9 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con return c, err } -// DialUDP implements C.ProxyAdapter -func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := s.selectedProxy(true).DialUDP(metadata) +// ListenPacketContext implements C.ProxyAdapter +func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata) if err == nil { pc.AppendToChains(s) } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 2a11cd23b..b27f12a44 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -42,9 +42,9 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co return c, err } -// DialUDP implements C.ProxyAdapter -func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := u.fast(true).DialUDP(metadata) +// ListenPacketContext implements C.ProxyAdapter +func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := u.fast(true).ListenPacketContext(ctx, metadata) if err == nil { pc.AppendToChains(u) } diff --git a/constant/adapters.go b/constant/adapters.go index 733d86b91..2f7006e96 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -29,6 +29,7 @@ const ( const ( DefaultTCPTimeout = 5 * time.Second + DefaultUDPTimeout = DefaultTCPTimeout ) type Connection interface { @@ -73,11 +74,14 @@ type PacketConn interface { type ProxyAdapter interface { Name() string Type() AdapterType + Addr() string + SupportUDP() bool + MarshalJSON() ([]byte, error) // StreamConn wraps a protocol around net.Conn with Metadata. // // Examples: - // conn, _ := net.Dial("tcp", "host:port") + // conn, _ := net.DialContext(context.Background(), "tcp", "host:port") // conn, _ = adapter.StreamConn(conn, metadata) // // It returns a C.Conn with protocol which start with @@ -88,10 +92,8 @@ type ProxyAdapter interface { // contains multiplexing-related reuse logic (if any) DialContext(ctx context.Context, metadata *Metadata) (Conn, error) - DialUDP(metadata *Metadata) (PacketConn, error) - SupportUDP() bool - MarshalJSON() ([]byte, error) - Addr() string + ListenPacketContext(ctx context.Context, metadata *Metadata) (PacketConn, error) + // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. Unwrap(metadata *Metadata) Proxy } @@ -105,9 +107,14 @@ type Proxy interface { ProxyAdapter Alive() bool DelayHistory() []DelayHistory - Dial(metadata *Metadata) (Conn, error) LastDelay() uint16 URLTest(ctx context.Context, url string) (uint16, error) + + // Deprecated: use DialContext instead. + Dial(metadata *Metadata) (Conn, error) + + // Deprecated: use DialPacketConn instead. + DialUDP(metadata *Metadata) (PacketConn, error) } // AdapterType is enum of adapter type diff --git a/constant/provider/interface.go b/constant/provider/interface.go index d3483cdcd..53bda7eac 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -67,7 +67,7 @@ type ProxyProvider interface { Provider Proxies() []constant.Proxy // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. - // Commonly used in Dial and DialUDP + // Commonly used in DialContext and DialPacketConn ProxiesWithTouch() []constant.Proxy HealthCheck() } diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 000000000..012d88d52 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,8 @@ +lint: + golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... + +test: + go test -p 1 -v ./... + +benchmark: + go test -benchmem -run=^$ -bench . diff --git a/test/README.md b/test/README.md index 823fc5446..a95f3aea6 100644 --- a/test/README.md +++ b/test/README.md @@ -45,7 +45,7 @@ Prerequisite * docker (support Linux and macOS) ``` -$ go test -p 1 -v +$ make test ``` benchmark (Linux) @@ -55,5 +55,5 @@ benchmark (Linux) > (change chunkSize to measure the maximum throughput of clash on your machine) ``` -$ go test -benchmem -run=^$ -bench . +$ make benchmark ``` diff --git a/test/clash_test.go b/test/clash_test.go index 4011b04a4..a3303a239 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -96,6 +96,7 @@ func init() { images := []string{ ImageShadowsocks, + ImageShadowsocksRust, ImageVmess, ImageTrojan, ImageSnell, @@ -582,7 +583,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) { return } - pc, err := proxy.DialUDP(&C.Metadata{ + pc, err := proxy.ListenPacketContext(context.Background(), &C.Metadata{ NetWork: C.UDP, DstIP: localIP, DstPort: "10001", @@ -595,7 +596,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) { assert.NoError(t, testPingPongWithPacketConn(t, pc)) - pc, err = proxy.DialUDP(&C.Metadata{ + pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{ NetWork: C.UDP, DstIP: localIP, DstPort: "10001", @@ -608,7 +609,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) { assert.NoError(t, testLargeDataWithPacketConn(t, pc)) - pc, err = proxy.DialUDP(&C.Metadata{ + pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{ NetWork: C.UDP, DstIP: localIP, DstPort: "10001", diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 2a35f0960..ccd6a58bd 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -1,6 +1,7 @@ package tunnel import ( + "context" "fmt" "net" "runtime" @@ -12,7 +13,7 @@ import ( "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/context" + icontext "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel/statistic" ) @@ -209,14 +210,16 @@ func handleUDPConn(packet *inbound.PacketAdapter) { cond.Broadcast() }() - ctx := context.NewPacketConnContext(metadata) - proxy, rule, err := resolveMetadata(ctx, metadata) + pCtx := icontext.NewPacketConnContext(metadata) + proxy, rule, err := resolveMetadata(pCtx, metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) return } - rawPc, err := proxy.DialUDP(metadata) + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) + defer cancel() + rawPc, err := proxy.ListenPacketContext(ctx, metadata) if err != nil { if rule == nil { log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -225,7 +228,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { } return } - ctx.InjectPacketConn(rawPc) + pCtx.InjectPacketConn(rawPc) pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule) switch true { @@ -246,10 +249,10 @@ func handleUDPConn(packet *inbound.PacketAdapter) { }() } -func handleTCPConn(ctx C.ConnContext) { - defer ctx.Conn().Close() +func handleTCPConn(connCtx C.ConnContext) { + defer connCtx.Conn().Close() - metadata := ctx.Metadata() + metadata := connCtx.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) return @@ -260,13 +263,15 @@ func handleTCPConn(ctx C.ConnContext) { return } - proxy, rule, err := resolveMetadata(ctx, metadata) + proxy, rule, err := resolveMetadata(connCtx, metadata) if err != nil { log.Warnln("[Metadata] parse failed: %s", err.Error()) return } - remoteConn, err := proxy.Dial(metadata) + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) + defer cancel() + remoteConn, err := proxy.DialContext(ctx, metadata) if err != nil { if rule == nil { log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -289,7 +294,7 @@ func handleTCPConn(ctx C.ConnContext) { log.Infoln("[TCP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) } - handleSocket(ctx, remoteConn) + handleSocket(connCtx, remoteConn) } func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { From df3a491d40c28c309e2cad8ba6d23c811e0e35a3 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 16 Oct 2021 20:19:59 +0800 Subject: [PATCH 14/19] Feature: support trojan websocket --- adapter/outbound/trojan.go | 35 ++++++++++++++++++++++++-- adapter/outbound/vmess.go | 12 +++++++-- test/clash_test.go | 2 ++ test/config/trojan-ws.json | 20 +++++++++++++++ test/trojan_test.go | 38 +++++++++++++++++++++++++++++ transport/trojan/trojan.go | 32 ++++++++++++++++++++++++ transport/v2ray-plugin/websocket.go | 23 ++++++++++++----- transport/vmess/websocket.go | 15 ++---------- 8 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 test/config/trojan-ws.json diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index b8ea49e65..8bea0cc8f 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "fmt" "net" + "net/http" "strconv" "github.com/Dreamacro/clash/component/dialer" @@ -18,6 +19,7 @@ import ( type Trojan struct { *Base instance *trojan.Trojan + option *TrojanOption // for gun mux gunTLSConfig *tls.Config @@ -36,6 +38,34 @@ type TrojanOption struct { UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSOpts WSOptions `proxy:"ws-opts,omitempty"` +} + +func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { + if t.option.Network == "ws" { + host, port, _ := net.SplitHostPort(t.addr) + wsOpts := &trojan.WebsocketOption{ + Host: host, + Port: port, + Path: t.option.WSOpts.Path, + } + + if t.option.SNI != "" { + wsOpts.Host = t.option.SNI + } + + if len(t.option.WSOpts.Headers) != 0 { + header := http.Header{} + for key, value := range t.option.WSOpts.Headers { + header.Add(key, value) + } + wsOpts.Headers = header + } + + return t.instance.StreamWebsocketConn(c, wsOpts) + } + + return t.instance.StreamConn(c) } // StreamConn implements C.ProxyAdapter @@ -44,7 +74,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) if t.transport != nil { c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig) } else { - c, err = t.instance.StreamConn(c) + c, err = t.plainStream(c) } if err != nil { @@ -106,7 +136,7 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) } defer safeConnClose(c, err) tcpKeepAlive(c) - c, err = t.instance.StreamConn(c) + c, err = t.plainStream(c) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } @@ -143,6 +173,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { udp: option.UDP, }, instance: trojan.New(tOption), + option: &option, } if option.Network == "grpc" { diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 209084ca8..b4db00ad9 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -105,8 +105,16 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { if v.option.TLS { wsOpts.TLS = true - wsOpts.SkipCertVerify = v.option.SkipCertVerify - wsOpts.ServerName = v.option.ServerName + wsOpts.TLSConfig = &tls.Config{ + ServerName: host, + InsecureSkipVerify: v.option.SkipCertVerify, + NextProtos: []string{"http/1.1"}, + } + if v.option.ServerName != "" { + wsOpts.TLSConfig.ServerName = v.option.ServerName + } else if host := wsOpts.Headers.Get("Host"); host != "" { + wsOpts.TLSConfig.ServerName = host + } } c, err = vmess.StreamWebsocketConn(c, wsOpts) case "http": diff --git a/test/clash_test.go b/test/clash_test.go index a3303a239..5eb9d5bda 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -31,6 +31,7 @@ const ( ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest" ImageVmess = "v2fly/v2fly-core:latest" ImageTrojan = "trojangfw/trojan:latest" + ImageTrojanGo = "p4gefau1t/trojan-go:latest" ImageSnell = "icpz/snell-server:latest" ImageXray = "teddysun/xray:latest" ) @@ -99,6 +100,7 @@ func init() { ImageShadowsocksRust, ImageVmess, ImageTrojan, + ImageTrojanGo, ImageSnell, ImageXray, } diff --git a/test/config/trojan-ws.json b/test/config/trojan-ws.json new file mode 100644 index 000000000..efc0acbd0 --- /dev/null +++ b/test/config/trojan-ws.json @@ -0,0 +1,20 @@ +{ + "run_type": "server", + "local_addr": "0.0.0.0", + "local_port": 10002, + "disable_http_check": true, + "password": [ + "example" + ], + "websocket": { + "enabled": true, + "path": "/", + "host": "example.org" + }, + "ssl": { + "verify": true, + "cert": "/fullchain.pem", + "key": "/privkey.pem", + "sni": "example.org" + } +} \ No newline at end of file diff --git a/test/trojan_test.go b/test/trojan_test.go index a57dab99f..d1ab2a006 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -93,6 +93,44 @@ func TestClash_TrojanGrpc(t *testing.T) { testSuit(t, proxy) } +func TestClash_TrojanWebsocket(t *testing.T) { + cfg := &container.Config{ + Image: ImageTrojanGo, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/trojan-go/config.json", C.Path.Resolve("trojan-ws.json")), + fmt.Sprintf("%s:/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan-ws") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "example", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + Network: "ws", + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + func Benchmark_Trojan(b *testing.B) { cfg := &container.Config{ Image: ImageTrojan, diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index d39cbec1e..26e7adc7d 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -8,10 +8,12 @@ import ( "errors" "io" "net" + "net/http" "sync" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/transport/socks5" + "github.com/Dreamacro/clash/transport/vmess" ) const ( @@ -38,6 +40,13 @@ type Option struct { SkipCertVerify bool } +type WebsocketOption struct { + Host string + Port string + Path string + Headers http.Header +} + type Trojan struct { option *Option hexPassword []byte @@ -64,6 +73,29 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { return tlsConn, nil } +func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) { + alpn := defaultALPN + if len(t.option.ALPN) != 0 { + alpn = t.option.ALPN + } + + tlsConfig := &tls.Config{ + NextProtos: alpn, + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: t.option.SkipCertVerify, + ServerName: t.option.ServerName, + } + + return vmess.StreamWebsocketConn(conn, &vmess.WebsocketConfig{ + Host: wsOptions.Host, + Port: wsOptions.Port, + Path: wsOptions.Path, + Headers: wsOptions.Headers, + TLS: true, + TLSConfig: tlsConfig, + }) +} + func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error { buf := pool.GetBuffer() defer pool.PutBuffer(buf) diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 317c172fc..7591b4a86 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -1,6 +1,7 @@ package obfs import ( + "crypto/tls" "net" "net/http" @@ -26,12 +27,22 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { } config := &vmess.WebsocketConfig{ - Host: option.Host, - Port: option.Port, - Path: option.Path, - TLS: option.TLS, - Headers: header, - SkipCertVerify: option.SkipCertVerify, + Host: option.Host, + Port: option.Port, + Path: option.Path, + Headers: header, + } + + if option.TLS { + config.TLS = true + config.TLSConfig = &tls.Config{ + ServerName: option.Host, + InsecureSkipVerify: option.SkipCertVerify, + NextProtos: []string{"http/1.1"}, + } + if host := config.Headers.Get("Host"); host != "" { + config.TLSConfig.ServerName = host + } } var err error diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index 951fb5d0a..f769dcce8 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -45,8 +45,7 @@ type WebsocketConfig struct { Path string Headers http.Header TLS bool - SkipCertVerify bool - ServerName string + TLSConfig *tls.Config MaxEarlyData int EarlyDataHeaderName string } @@ -254,17 +253,7 @@ func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buf scheme := "ws" if c.TLS { scheme = "wss" - dialer.TLSClientConfig = &tls.Config{ - ServerName: c.Host, - InsecureSkipVerify: c.SkipCertVerify, - NextProtos: []string{"http/1.1"}, - } - - if c.ServerName != "" { - dialer.TLSClientConfig.ServerName = c.ServerName - } else if host := c.Headers.Get("Host"); host != "" { - dialer.TLSClientConfig.ServerName = host - } + dialer.TLSClientConfig = c.TLSConfig } uri := url.URL{ From fea9d1c5e234d852e9cae41a51373ca44b2c08dc Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 16 Oct 2021 20:35:06 +0800 Subject: [PATCH 15/19] Fix: replace vmess grpc test image --- test/vmess_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/vmess_test.go b/test/vmess_test.go index b696fcea7..1483cf0af 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -304,13 +304,13 @@ func TestClash_VmessWebsocketTLS(t *testing.T) { func TestClash_VmessGrpc(t *testing.T) { cfg := &container.Config{ - Image: ImageXray, + Image: ImageVmess, ExposedPorts: defaultExposedPorts, } hostCfg := &container.HostConfig{ PortBindings: defaultPortBindings, Binds: []string{ - fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("vmess-grpc.json")), + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-grpc.json")), fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), }, From 81d5da51a3b0d84ed0a6f752c3e7c02e1065bce1 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 18 Oct 2021 21:08:27 +0800 Subject: [PATCH 16/19] Fix: unexpected proxy dial behavior on mapping mode --- config/config.go | 6 ++-- constant/dns.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ constant/metadata.go | 18 ++++++++++++ dns/enhancer.go | 9 +++--- dns/middleware.go | 5 ++-- dns/resolver.go | 3 +- dns/util.go | 66 ----------------------------------------- tunnel/tunnel.go | 6 ++-- 8 files changed, 105 insertions(+), 78 deletions(-) create mode 100644 constant/dns.go diff --git a/config/config.go b/config/config.go index f532c61a1..603523ec6 100644 --- a/config/config.go +++ b/config/config.go @@ -62,7 +62,7 @@ type DNS struct { Fallback []dns.NameServer `yaml:"fallback"` FallbackFilter FallbackFilter `yaml:"fallback-filter"` Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + EnhancedMode C.DNSMode `yaml:"enhanced-mode"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` FakeIPRange *fakeip.Pool Hosts *trie.DomainTrie @@ -107,7 +107,7 @@ type RawDNS struct { Fallback []string `yaml:"fallback"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + EnhancedMode C.DNSMode `yaml:"enhanced-mode"` FakeIPRange string `yaml:"fake-ip-range"` FakeIPFilter []string `yaml:"fake-ip-filter"` DefaultNameserver []string `yaml:"default-nameserver"` @@ -584,7 +584,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) { } } - if cfg.EnhancedMode == dns.FAKEIP { + if cfg.EnhancedMode == C.DNSFakeIP { _, ipnet, err := net.ParseCIDR(cfg.FakeIPRange) if err != nil { return nil, err diff --git a/constant/dns.go b/constant/dns.go new file mode 100644 index 000000000..d1cd7a61c --- /dev/null +++ b/constant/dns.go @@ -0,0 +1,70 @@ +package constant + +import ( + "encoding/json" + "errors" +) + +// DNSModeMapping is a mapping for EnhancedMode enum +var DNSModeMapping = map[string]DNSMode{ + DNSNormal.String(): DNSNormal, + DNSFakeIP.String(): DNSFakeIP, + DNSMapping.String(): DNSMapping, +} + +const ( + DNSNormal DNSMode = iota + DNSFakeIP + DNSMapping +) + +type DNSMode int + +// UnmarshalYAML unserialize EnhancedMode with yaml +func (e *DNSMode) UnmarshalYAML(unmarshal func(interface{}) error) error { + var tp string + if err := unmarshal(&tp); err != nil { + return err + } + mode, exist := DNSModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *e = mode + return nil +} + +// MarshalYAML serialize EnhancedMode with yaml +func (e DNSMode) MarshalYAML() (interface{}, error) { + return e.String(), nil +} + +// UnmarshalJSON unserialize EnhancedMode with json +func (e *DNSMode) UnmarshalJSON(data []byte) error { + var tp string + json.Unmarshal(data, &tp) + mode, exist := DNSModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *e = mode + return nil +} + +// MarshalJSON serialize EnhancedMode with json +func (e DNSMode) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e DNSMode) String() string { + switch e { + case DNSNormal: + return "normal" + case DNSFakeIP: + return "fake-ip" + case DNSMapping: + return "redir-host" + default: + return "unknown" + } +} diff --git a/constant/metadata.go b/constant/metadata.go index 9fff773b3..822b4714d 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -71,6 +71,7 @@ type Metadata struct { DstPort string `json:"destinationPort"` AddrType int `json:"-"` Host string `json:"host"` + DNSMode DNSMode `json:"dnsMode"` } func (m *Metadata) RemoteAddress() string { @@ -85,6 +86,23 @@ func (m *Metadata) Resolved() bool { return m.DstIP != nil } +// Pure is used to solve unexpected behavior +// when dialing proxy connection in DNSMapping mode. +func (m *Metadata) Pure() *Metadata { + if m.DNSMode == DNSMapping && m.DstIP != nil { + copy := *m + copy.Host = "" + if copy.DstIP.To4() != nil { + copy.AddrType = AtypIPv4 + } else { + copy.AddrType = AtypIPv6 + } + return © + } + + return m +} + func (m *Metadata) UDPAddr() *net.UDPAddr { if m.NetWork != UDP || m.DstIP == nil { return nil diff --git a/dns/enhancer.go b/dns/enhancer.go index d2592e6e9..76f0f2628 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -5,20 +5,21 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" + C "github.com/Dreamacro/clash/constant" ) type ResolverEnhancer struct { - mode EnhancedMode + mode C.DNSMode fakePool *fakeip.Pool mapping *cache.LruCache } func (h *ResolverEnhancer) FakeIPEnabled() bool { - return h.mode == FAKEIP + return h.mode == C.DNSFakeIP } func (h *ResolverEnhancer) MappingEnabled() bool { - return h.mode == FAKEIP || h.mode == MAPPING + return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping } func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool { @@ -75,7 +76,7 @@ func NewEnhancer(cfg Config) *ResolverEnhancer { var fakePool *fakeip.Pool var mapping *cache.LruCache - if cfg.EnhancedMode != NORMAL { + if cfg.EnhancedMode != C.DNSNormal { fakePool = cfg.Pool mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)) } diff --git a/dns/middleware.go b/dns/middleware.go index de0b3aff4..9d0da5968 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -8,6 +8,7 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/trie" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" @@ -178,11 +179,11 @@ func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler { middlewares = append(middlewares, withHosts(resolver.hosts)) } - if mapper.mode == FAKEIP { + if mapper.mode == C.DNSFakeIP { middlewares = append(middlewares, withFakeIP(mapper.fakePool)) } - if mapper.mode != NORMAL { + if mapper.mode != C.DNSNormal { middlewares = append(middlewares, withMapping(mapper.mapping)) } diff --git a/dns/resolver.go b/dns/resolver.go index bf544cb99..91efbb8bc 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -14,6 +14,7 @@ import ( "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/trie" + C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" "golang.org/x/sync/singleflight" @@ -317,7 +318,7 @@ type Config struct { Main, Fallback []NameServer Default []NameServer IPv6 bool - EnhancedMode EnhancedMode + EnhancedMode C.DNSMode FallbackFilter FallbackFilter Pool *fakeip.Pool Hosts *trie.DomainTrie diff --git a/dns/util.go b/dns/util.go index 0e1badabc..d11870f81 100644 --- a/dns/util.go +++ b/dns/util.go @@ -2,8 +2,6 @@ package dns import ( "crypto/tls" - "encoding/json" - "errors" "net" "time" @@ -13,70 +11,6 @@ import ( D "github.com/miekg/dns" ) -// EnhancedModeMapping is a mapping for EnhancedMode enum -var EnhancedModeMapping = map[string]EnhancedMode{ - NORMAL.String(): NORMAL, - FAKEIP.String(): FAKEIP, - MAPPING.String(): MAPPING, -} - -const ( - NORMAL EnhancedMode = iota - FAKEIP - MAPPING -) - -type EnhancedMode int - -// UnmarshalYAML unserialize EnhancedMode with yaml -func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error { - var tp string - if err := unmarshal(&tp); err != nil { - return err - } - mode, exist := EnhancedModeMapping[tp] - if !exist { - return errors.New("invalid mode") - } - *e = mode - return nil -} - -// MarshalYAML serialize EnhancedMode with yaml -func (e EnhancedMode) MarshalYAML() (interface{}, error) { - return e.String(), nil -} - -// UnmarshalJSON unserialize EnhancedMode with json -func (e *EnhancedMode) UnmarshalJSON(data []byte) error { - var tp string - json.Unmarshal(data, &tp) - mode, exist := EnhancedModeMapping[tp] - if !exist { - return errors.New("invalid mode") - } - *e = mode - return nil -} - -// MarshalJSON serialize EnhancedMode with json -func (e EnhancedMode) MarshalJSON() ([]byte, error) { - return json.Marshal(e.String()) -} - -func (e EnhancedMode) String() string { - switch e { - case NORMAL: - return "normal" - case FAKEIP: - return "fake-ip" - case MAPPING: - return "redir-host" - default: - return "unknown" - } -} - func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) { var ttl uint32 switch { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index ccd6a58bd..dd64b0155 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -135,9 +135,11 @@ func preHandleMetadata(metadata *C.Metadata) error { metadata.AddrType = C.AtypDomainName if resolver.FakeIPEnabled() { metadata.DstIP = nil + metadata.DNSMode = C.DNSFakeIP } else if node := resolver.DefaultHosts.Search(host); node != nil { // redir-host should lookup the hosts metadata.DstIP = node.Data.(net.IP) + metadata.DNSMode = C.DNSMapping } } else if resolver.IsFakeIP(metadata.DstIP) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) @@ -219,7 +221,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) defer cancel() - rawPc, err := proxy.ListenPacketContext(ctx, metadata) + rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure()) if err != nil { if rule == nil { log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -271,7 +273,7 @@ func handleTCPConn(connCtx C.ConnContext) { ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) defer cancel() - remoteConn, err := proxy.DialContext(ctx, metadata) + remoteConn, err := proxy.DialContext(ctx, metadata.Pure()) if err != nil { if rule == nil { log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) From 967932d02c14d45914c9dc106c483c55e380712f Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 18 Oct 2021 22:58:16 +0800 Subject: [PATCH 17/19] Fix: set dnsmode behavior --- tunnel/tunnel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index dd64b0155..1c0075549 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -133,13 +133,13 @@ func preHandleMetadata(metadata *C.Metadata) error { if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName + metadata.DNSMode = C.DNSMapping if resolver.FakeIPEnabled() { metadata.DstIP = nil metadata.DNSMode = C.DNSFakeIP } else if node := resolver.DefaultHosts.Search(host); node != nil { // redir-host should lookup the hosts metadata.DstIP = node.Data.(net.IP) - metadata.DNSMode = C.DNSMapping } } else if resolver.IsFakeIP(metadata.DstIP) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) From c6cceeb0c589983ee0758f1bbbe6fd7ad1f3a1eb Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 19 Oct 2021 22:34:18 +0800 Subject: [PATCH 18/19] Chore: use alpn http 1.1 only on trojan websocket by default --- transport/trojan/trojan.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 26e7adc7d..9d9a33b99 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -22,8 +22,10 @@ const ( ) var ( - defaultALPN = []string{"h2", "http/1.1"} - crlf = []byte{'\r', '\n'} + defaultALPN = []string{"h2", "http/1.1"} + defaultWebsocketALPN = []string{"http/1.1"} + + crlf = []byte{'\r', '\n'} ) type Command = byte @@ -74,7 +76,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) { - alpn := defaultALPN + alpn := defaultWebsocketALPN if len(t.option.ALPN) != 0 { alpn = t.option.ALPN } From a7aea12aa69596edf9c3bad2352ebde303ea5d6e Mon Sep 17 00:00:00 2001 From: Blaise Wang Date: Wed, 20 Oct 2021 13:44:05 +0800 Subject: [PATCH 19/19] Fix: remove ResponseHeaderTimeout limitation (#1690) --- listener/http/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/listener/http/client.go b/listener/http/client.go index 15078b0ac..873a9a3c9 100644 --- a/listener/http/client.go +++ b/listener/http/client.go @@ -19,7 +19,6 @@ func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client { MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, - ResponseHeaderTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: func(context context.Context, network, address string) (net.Conn, error) { if network != "tcp" && network != "tcp4" && network != "tcp6" {