From d237b041b3b9f702897ca3aedd0401ab309a60c3 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Wed, 5 Jan 2022 11:41:31 +0800 Subject: [PATCH 01/27] Fix: ignore empty dns server error --- dns/server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dns/server.go b/dns/server.go index 49d4541a..db903349 100644 --- a/dns/server.go +++ b/dns/server.go @@ -60,6 +60,10 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) { address = "" } + if addr == "" { + return + } + var err error defer func() { if err != nil { From a832cfdb657137ac65c329b6e8a5197b131bed70 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Wed, 5 Jan 2022 19:28:54 +0800 Subject: [PATCH 02/27] [Fixed] compatible cfw --- hub/route/script.go | 16 ++++++++++++++++ hub/route/server.go | 1 + 2 files changed, 17 insertions(+) create mode 100644 hub/route/script.go diff --git a/hub/route/script.go b/hub/route/script.go new file mode 100644 index 00000000..fc1f791d --- /dev/null +++ b/hub/route/script.go @@ -0,0 +1,16 @@ +package route + +import ( + "github.com/go-chi/chi/v5" + "net/http" +) + +func scriptRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getScript) + return r +} + +func getScript(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(http.StatusMethodNotAllowed) +} diff --git a/hub/route/server.go b/hub/route/server.go index 402ba666..b657128e 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -72,6 +72,7 @@ func Start(addr string, secret string) { r.Mount("/connections", connectionRouter()) r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) + r.Mount("/script", scriptRouter()) }) if uiPath != "" { From d35d6c9ac949c139af602027654ad0c4ec29380f Mon Sep 17 00:00:00 2001 From: Skyxim Date: Thu, 6 Jan 2022 10:49:26 +0800 Subject: [PATCH 03/27] [Fixed] Stupid mistakes --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 185a50f1..c718dde4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@

Meta Kennel -
Meta Kennel
+
Meta Kernel

-

Another Clash Kennel.

+

Another Clash Kernel.

@@ -212,13 +212,13 @@ tun: ``` Create user given name `Clash.Meta`. -Run Meta Kennel by user `Clash.Meta` as a daemon. +Run Meta Kernel by user `Clash.Meta` as a daemon. Create the systemd configuration file at /etc/systemd/system/clash.service: ``` [Unit] -Description=Clash.Meta Daemon, Another Clash Kennel. +Description=Clash.Meta Daemon, Another Clash Kernel. After=network.target [Service] From 75b5f633cd2c877687cb9ec8b091f74d26bf27bf Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 7 Jan 2022 12:58:40 +0800 Subject: [PATCH 04/27] [Fixed] Positive health check multithreading is not safe --- adapter/outboundgroup/fallback.go | 5 ++--- adapter/outboundgroup/urltest.go | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 4ecc8d39..5f365ff8 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -70,11 +70,10 @@ func (f *Fallback) onDialFailed() { f.failedTimes.Store(-1) f.failedTime.Store(-1) } else { - f.failedTimes.Inc() - failedCount := f.failedTimes.Load() + failedCount := f.failedTimes.Inc() log.Warnln("%s failed count: %d", f.Name(), failedCount) if failedCount > 5 { - log.Debugln("%s failed multiple times.", f.Name()) + log.Warnln("because %s failed multiple times, active health check", f.Name()) for _, proxyProvider := range f.providers { go proxyProvider.HealthCheck() } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index ee6fe13a..c0ac5d3c 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -147,11 +147,10 @@ func (u *URLTest) onDialFailed() { u.failedTimes.Store(-1) u.failedTime.Store(-1) } else { - u.failedTimes.Inc() - failedCount := u.failedTimes.Load() + failedCount := u.failedTimes.Inc() log.Warnln("%s failed count: %d", u.Name(), failedCount) if failedCount > 5 { - log.Debugln("%s failed multiple times.", u.Name()) + log.Warnln("because %s failed multiple times, active health check", u.Name()) for _, proxyProvider := range u.providers { go proxyProvider.HealthCheck() } From 16abba385acbf7f5e5deb3cc711067685d3019d2 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 7 Jan 2022 22:32:02 +0800 Subject: [PATCH 05/27] [Style] Adjust the routing table of tun on mac --- listener/tun/dev/dev_darwin.go | 48 ++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/listener/tun/dev/dev_darwin.go b/listener/tun/dev/dev_darwin.go index 9424c16b..cde63c96 100644 --- a/listener/tun/dev/dev_darwin.go +++ b/listener/tun/dev/dev_darwin.go @@ -5,11 +5,13 @@ package dev import ( "bytes" - "errors" "fmt" + "github.com/Dreamacro/clash/log" "net" "os" "os/exec" + "regexp" + "strings" "sync" "syscall" "unsafe" @@ -176,7 +178,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { } if autoRoute { - SetLinuxAutoRoute() + setAutoRoute(tunAddress) } return tun, nil @@ -480,15 +482,39 @@ func (t *tunDarwin) attachLinkLocal() error { // GetAutoDetectInterface get ethernet interface func GetAutoDetectInterface() (string, error) { - cmd := exec.Command("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n") - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { + cmd := exec.Command("route", "-n", "get", "default") + if result, err := cmd.Output(); err != nil { return "", err + } else { + resultString := string(result) + reg, err := regexp.Compile("(interface:)(.*)") + if err != nil { + return "", err + } + matchResult := reg.FindStringSubmatch(resultString) + interfaceName := strings.TrimSpace(matchResult[len(matchResult)-1]) + return interfaceName, nil + } + +} + +func setAutoRoute(tunGateway string) { + addRoute("1", tunGateway) + addRoute("2/7", tunGateway) + addRoute("4/6", tunGateway) + addRoute("8/5", tunGateway) + addRoute("16/4", tunGateway) + addRoute("32/3", tunGateway) + addRoute("64/2", tunGateway) + addRoute("128.0/1", tunGateway) + addRoute("198.18.0/16", tunGateway) +} + +func addRoute(net, name string) { + cmd := exec.Command("route", "add", "-net", net, name) + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String()) } - if out.Len() == 0 { - return "", errors.New("interface not found by default route") - } - return out.String(), nil } From 4f1b227ca2416e20f0067c898e58d9d1a6a34d43 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 8 Jan 2022 09:23:49 +0800 Subject: [PATCH 06/27] [Style] Positive health check --- adapter/outboundgroup/fallback.go | 7 +++++-- adapter/outboundgroup/urltest.go | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 5f365ff8..734bd6b7 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -66,17 +66,20 @@ func (f *Fallback) onDialFailed() { f.failedTime.Store(now) f.failedTimes.Store(1) } else { - if f.failedTime.Load()-time.Now().UnixMilli() > 5*1000 { + if f.failedTime.Load()-time.Now().UnixMilli() > 5*time.Second.Milliseconds() { f.failedTimes.Store(-1) f.failedTime.Store(-1) } else { failedCount := f.failedTimes.Inc() log.Warnln("%s failed count: %d", f.Name(), failedCount) - if failedCount > 5 { + if failedCount >= 5 && failedCount < 6 { log.Warnln("because %s failed multiple times, active health check", f.Name()) for _, proxyProvider := range f.providers { go proxyProvider.HealthCheck() } + + f.failedTimes.Store(-1) + f.failedTime.Store(-1) } } } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index c0ac5d3c..836dcaac 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -149,11 +149,14 @@ func (u *URLTest) onDialFailed() { } else { failedCount := u.failedTimes.Inc() log.Warnln("%s failed count: %d", u.Name(), failedCount) - if failedCount > 5 { + if failedCount >= 5 && failedCount < 6 { log.Warnln("because %s failed multiple times, active health check", u.Name()) for _, proxyProvider := range u.providers { go proxyProvider.HealthCheck() } + + u.failedTimes.Store(-1) + u.failedTime.Store(-1) } } } From 7f0368da669d34ea6dd567f06f7f2acca52e263a Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 8 Jan 2022 16:55:02 +0800 Subject: [PATCH 07/27] [Style] Adjust delete routes on macos --- listener/tun/dev/dev_darwin.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/listener/tun/dev/dev_darwin.go b/listener/tun/dev/dev_darwin.go index cde63c96..61f08731 100644 --- a/listener/tun/dev/dev_darwin.go +++ b/listener/tun/dev/dev_darwin.go @@ -282,7 +282,7 @@ func (t *tunDarwin) IsClose() bool { func (t *tunDarwin) Close() error { t.stopOnce.Do(func() { if t.autoRoute { - RemoveLinuxAutoRoute() + resetAutoRoute(t.tunAddress) } t.closed = true t.tunFile.Close() @@ -510,6 +510,18 @@ func setAutoRoute(tunGateway string) { addRoute("198.18.0/16", tunGateway) } +func resetAutoRoute(tunGateway string) { + delRoute("1", tunGateway) + delRoute("2/7", tunGateway) + delRoute("4/6", tunGateway) + delRoute("8/5", tunGateway) + delRoute("16/4", tunGateway) + delRoute("32/3", tunGateway) + delRoute("64/2", tunGateway) + delRoute("128.0/1", tunGateway) + delRoute("198.18.0/16", tunGateway) +} + func addRoute(net, name string) { cmd := exec.Command("route", "add", "-net", net, name) var stderr bytes.Buffer @@ -518,3 +530,12 @@ func addRoute(net, name string) { log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String()) } } + +func delRoute(net, name string) { + cmd := exec.Command("route", "delete", "-net", net, name) + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String()) + } +} From 64869d0f17b1309833af497d129e29025d1f837d Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 8 Jan 2022 16:57:59 +0800 Subject: [PATCH 08/27] [Fixed] Remove the Linux automatic routing configuration Change the name of the Linux network card to utun --- listener/tun/dev/dev.go | 59 ----------------------------------- listener/tun/dev/dev_linux.go | 10 +++--- 2 files changed, 4 insertions(+), 65 deletions(-) diff --git a/listener/tun/dev/dev.go b/listener/tun/dev/dev.go index 004b51da..54ff591e 100644 --- a/listener/tun/dev/dev.go +++ b/listener/tun/dev/dev.go @@ -1,13 +1,5 @@ package dev -import ( - "bytes" - "os/exec" - "runtime" - - "github.com/Dreamacro/clash/log" -) - // TunDevice is cross-platform tun interface type TunDevice interface { Name() string @@ -18,54 +10,3 @@ type TunDevice interface { Read(buff []byte) (int, error) Write(buff []byte) (int, error) } - -func SetLinuxAutoRoute() { - log.Infoln("Tun adapter auto setting global route") - addLinuxSystemRoute("0") - //addLinuxSystemRoute("1") - //addLinuxSystemRoute("2/7") - //addLinuxSystemRoute("4/6") - //addLinuxSystemRoute("8/5") - //addLinuxSystemRoute("16/4") - //addLinuxSystemRoute("32/3") - //addLinuxSystemRoute("64/2") - //addLinuxSystemRoute("128.0/1") - //addLinuxSystemRoute("198.18.0/16") -} - -func RemoveLinuxAutoRoute() { - log.Infoln("Tun adapter removing global route") - delLinuxSystemRoute("0") - //delLinuxSystemRoute("1") - //delLinuxSystemRoute("2/7") - //delLinuxSystemRoute("4/6") - //delLinuxSystemRoute("8/5") - //delLinuxSystemRoute("16/4") - //delLinuxSystemRoute("32/3") - //delLinuxSystemRoute("64/2") - //delLinuxSystemRoute("128.0/1") - //delLinuxSystemRoute("198.18.0/16") -} - -func addLinuxSystemRoute(net string) { - if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { - return - } - cmd := exec.Command("route", "add", "-net", net, "meta") - var stderr bytes.Buffer - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String()) - } -} - -func delLinuxSystemRoute(net string) { - if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { - return - } - cmd := exec.Command("route", "delete", "-net", net, "meta") - _ = cmd.Run() - //if err := cmd.Run(); err != nil { - // log.Errorln("[auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String()) - //} -} diff --git a/listener/tun/dev/dev_linux.go b/listener/tun/dev/dev_linux.go index 4d63a87d..aeb90d4e 100644 --- a/listener/tun/dev/dev_linux.go +++ b/listener/tun/dev/dev_linux.go @@ -7,6 +7,7 @@ import ( "bytes" "errors" "fmt" + "github.com/Dreamacro/clash/log" "net/url" "os" "os/exec" @@ -38,7 +39,7 @@ type tunLinux struct { // OpenTunDevice return a TunDevice according a URL func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { - deviceURL, _ := url.Parse("dev://meta") + deviceURL, _ := url.Parse("dev://utun") mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32) t := &tunLinux{ @@ -62,7 +63,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { } if autoRoute { - SetLinuxAutoRoute() + log.Warnln("linux unsupported automatic route") } return dev, nil case "fd": @@ -76,7 +77,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { return nil, err } if autoRoute { - SetLinuxAutoRoute() + log.Warnln("linux unsupported automatic route") } return dev, nil } @@ -105,9 +106,6 @@ func (t *tunLinux) IsClose() bool { func (t *tunLinux) Close() error { t.stopOnce.Do(func() { - if t.autoRoute { - RemoveLinuxAutoRoute() - } t.closed = true t.tunFile.Close() }) From 4ab986cccb739f48085acebcad50c1855f2009ff Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sun, 9 Jan 2022 00:35:45 +0800 Subject: [PATCH 09/27] [Refactor] gvisor support hijack dns list dns-hijack: - 1.1.1.1 - 8.8.8.8:53 - tcp://1.1.1.1:53 - udp://223.5.5.5 - 10.0.0.1:5353 --- common/net/tcpip.go | 46 ++++ listener/tun/ipstack/gvisor/tun.go | 12 +- listener/tun/ipstack/gvisor/tundns.go | 383 +++++++++++++++++--------- 3 files changed, 308 insertions(+), 133 deletions(-) create mode 100644 common/net/tcpip.go diff --git a/common/net/tcpip.go b/common/net/tcpip.go new file mode 100644 index 00000000..a84e7e4c --- /dev/null +++ b/common/net/tcpip.go @@ -0,0 +1,46 @@ +package net + +import ( + "fmt" + "net" + "strings" +) + +func SplitNetworkType(s string) (string, string, error) { + var ( + shecme string + hostPort string + ) + result := strings.Split(s, "://") + if len(result) == 2 { + shecme = result[0] + hostPort = result[1] + } else if len(result) == 1 { + hostPort = result[0] + } else { + return "", "", fmt.Errorf("tcp/udp style error") + } + + if len(shecme) == 0 { + shecme = "udp" + } + + if shecme != "tcp" && shecme != "udp" { + return "", "", fmt.Errorf("scheme should be tcp:// or udp://") + } else { + return shecme, hostPort, nil + } +} + +func SplitHostPort(s string) (host, port string, hasPort bool, err error) { + temp := s + hasPort = true + + if !strings.Contains(s, ":") && !strings.Contains(s, "]:") { + temp += ":0" + hasPort = false + } + + host, port, err = net.SplitHostPort(temp) + return +} diff --git a/listener/tun/ipstack/gvisor/tun.go b/listener/tun/ipstack/gvisor/tun.go index e550739d..b19e6fa7 100644 --- a/listener/tun/ipstack/gvisor/tun.go +++ b/listener/tun/ipstack/gvisor/tun.go @@ -34,10 +34,10 @@ import ( const nicID tcpip.NICID = 1 type gvisorAdapter struct { - device dev.TunDevice - ipstack *stack.Stack - dnsServers []*DNSServer - udpIn chan<- *inbound.PacketAdapter + device dev.TunDevice + ipstack *stack.Stack + dnsServer *DNSServer + udpIn chan<- *inbound.PacketAdapter stackName string autoRoute bool @@ -47,7 +47,7 @@ type gvisorAdapter struct { writeHandle *channel.NotificationHandle } -// GvisorAdapter create GvisorAdapter +// NewAdapter GvisorAdapter create GvisorAdapter func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) { ipstack := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, @@ -132,7 +132,7 @@ func (t *gvisorAdapter) AutoRoute() bool { // Close close the TunAdapter func (t *gvisorAdapter) Close() { - t.StopAllDNSServer() + t.StopDNSServer() if t.ipstack != nil { t.ipstack.Close() } diff --git a/listener/tun/ipstack/gvisor/tundns.go b/listener/tun/ipstack/gvisor/tundns.go index 5724aa70..7fba6bbe 100644 --- a/listener/tun/ipstack/gvisor/tundns.go +++ b/listener/tun/ipstack/gvisor/tundns.go @@ -2,13 +2,14 @@ package gvisor import ( "fmt" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "net" + Common "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" @@ -23,15 +24,33 @@ var ( ipv6Zero = tcpip.Address(net.IPv6zero.To16()) ) +type ListenerWrap struct { + net.Listener + listener net.Listener +} + +func (l *ListenerWrap) Accept() (conn net.Conn, err error) { + conn, err = l.listener.Accept() + log.Debugln("[DNS] hijack tcp:%s", l.Addr()) + return +} + +func (l *ListenerWrap) Close() error { + return l.listener.Close() +} + +func (l *ListenerWrap) Addr() net.Addr { + return l.listener.Addr() +} + // DNSServer is DNS Server listening on tun devcice type DNSServer struct { - *dns.Server - resolver *dns.Resolver - - stack *stack.Stack - tcpListener net.Listener - udpEndpoint *dnsEndpoint - udpEndpointID *stack.TransportEndpointID + dnsServers []*dns.Server + tcpListeners []net.Listener + resolver *dns.Resolver + stack *stack.Stack + udpEndpoints []*dnsEndpoint + udpEndpointIDs []*stack.TransportEndpointID tcpip.NICID } @@ -66,6 +85,7 @@ func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pack var msg D.Msg msg.Unpack(pkt.Data().AsRange().ToOwnedView()) writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id} + log.Debugln("[DNS] hijack udp:%s:%d", id.LocalAddress.String(), id.LocalPort) go e.server.ServeDNS(&writer, &msg) } @@ -129,167 +149,276 @@ func (w *dnsResponseWriter) Close() error { } // CreateDNSServer create a dns server on given netstack -func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, ip net.IP, port int, nicID tcpip.NICID) (*DNSServer, error) { - var v4 bool +func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijack []net.Addr, nicID tcpip.NICID) (*DNSServer, error) { var err error - - address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)} - var protocol tcpip.NetworkProtocolNumber - if ip.To4() != nil { - v4 = true - address.Addr = tcpip.Address(ip.To4()) - protocol = ipv4.ProtocolNumber - - } else { - v4 = false - address.Addr = tcpip.Address(ip.To16()) - protocol = ipv6.ProtocolNumber - } - protocolAddr := tcpip.ProtocolAddress{ - Protocol: protocol, - AddressWithPrefix: address.Addr.WithPrefix(), - } - // netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints - if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { - log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err) - } - - if address.Addr == ipv4Zero || address.Addr == ipv6Zero { - address.Addr = "" - } - handler := dns.NewHandler(resolver, mapper) serverIn := &dns.Server{} serverIn.SetHandler(handler) - - // UDP DNS - id := &stack.TransportEndpointID{ - LocalAddress: address.Addr, - LocalPort: uint16(port), - RemotePort: 0, - RemoteAddress: "", - } - - // TransportEndpoint for DNS - endpoint := &dnsEndpoint{ - stack: s, - uniqueID: s.UniqueID(), - server: serverIn, - } - - if tcpiperr := s.RegisterTransportEndpoint( - []tcpip.NetworkProtocolNumber{ - ipv4.ProtocolNumber, - ipv6.ProtocolNumber, - }, - udp.ProtocolNumber, - *id, - endpoint, - ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect. - nicID); tcpiperr != nil { - log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String()) - } - - // TCP DNS - var tcpListener net.Listener - if v4 { - tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber) - } else { - tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber) - } - if err != nil { - return nil, fmt.Errorf("can not listen on tun: %v", err) + tcpDnsArr := make([]net.TCPAddr, 0, len(dnsHijack)) + udpDnsArr := make([]net.UDPAddr, 0, len(dnsHijack)) + for _, d := range dnsHijack { + switch d.(type) { + case *net.TCPAddr: + { + tcpDnsArr = append(tcpDnsArr, *d.(*net.TCPAddr)) + break + } + case *net.UDPAddr: + { + udpDnsArr = append(udpDnsArr, *d.(*net.UDPAddr)) + break + } + } } + endpoints, ids := hijackUdpDns(udpDnsArr, s, serverIn) + tcpListeners, dnsServers := hijackTcpDns(tcpDnsArr, s, serverIn) server := &DNSServer{ - Server: serverIn, - resolver: resolver, - stack: s, - tcpListener: tcpListener, - udpEndpoint: endpoint, - udpEndpointID: id, - NICID: nicID, + resolver: resolver, + stack: s, + udpEndpoints: endpoints, + udpEndpointIDs: ids, + NICID: nicID, + tcpListeners: tcpListeners, } - server.SetHandler(handler) - server.Server.Server = &D.Server{Listener: tcpListener, Handler: server} - go func() { - server.ActivateAndServe() - }() + server.dnsServers = dnsServers return server, err } +func hijackUdpDns(dnsArr []net.UDPAddr, s *stack.Stack, serverIn *dns.Server) ([]*dnsEndpoint, []*stack.TransportEndpointID) { + endpoints := make([]*dnsEndpoint, len(dnsArr)) + ids := make([]*stack.TransportEndpointID, len(dnsArr)) + for i, dns := range dnsArr { + port := dns.Port + ip := dns.IP + address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)} + var protocol tcpip.NetworkProtocolNumber + if ip.To4() != nil { + address.Addr = tcpip.Address(ip.To4()) + protocol = ipv4.ProtocolNumber + + } else { + address.Addr = tcpip.Address(ip.To16()) + protocol = ipv6.ProtocolNumber + } + + protocolAddr := tcpip.ProtocolAddress{ + Protocol: protocol, + AddressWithPrefix: address.Addr.WithPrefix(), + } + + // netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints + if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err) + } + + if address.Addr == ipv4Zero || address.Addr == ipv6Zero { + address.Addr = "" + } + + // UDP DNS + id := &stack.TransportEndpointID{ + LocalAddress: address.Addr, + LocalPort: uint16(port), + RemotePort: 0, + RemoteAddress: "", + } + + // TransportEndpoint for DNS + endpoint := &dnsEndpoint{ + stack: s, + uniqueID: s.UniqueID(), + server: serverIn, + } + + if tcpiperr := s.RegisterTransportEndpoint( + []tcpip.NetworkProtocolNumber{ + ipv4.ProtocolNumber, + ipv6.ProtocolNumber, + }, + udp.ProtocolNumber, + *id, + endpoint, + ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect. + nicID); tcpiperr != nil { + log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String()) + } + + ids[i] = id + endpoints[i] = endpoint + } + + return endpoints, ids +} + +func hijackTcpDns(dnsArr []net.TCPAddr, s *stack.Stack, serverIn *dns.Server) ([]net.Listener, []*dns.Server) { + tcpListeners := make([]net.Listener, len(dnsArr)) + dnsServers := make([]*dns.Server, len(dnsArr)) + + for i, dnsAddr := range dnsArr { + var tcpListener net.Listener + var v4 bool + var err error + port := dnsAddr.Port + ip := dnsAddr.IP + address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)} + if ip.To4() != nil { + address.Addr = tcpip.Address(ip.To4()) + v4 = true + } else { + address.Addr = tcpip.Address(ip.To16()) + v4 = false + } + + if v4 { + tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber) + } else { + tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber) + } + + if err != nil { + log.Errorln("can not listen on tun: %v, hijack tcp[%s] failed", err, dnsAddr) + } else { + tcpListeners[i] = tcpListener + server := &D.Server{Listener: &ListenerWrap{ + listener: tcpListener, + }, Handler: serverIn} + dnsServer := dns.Server{} + dnsServer.Server = server + go dnsServer.ActivateAndServe() + dnsServers[i] = &dnsServer + } + + } + // + //for _, listener := range tcpListeners { + // server := &D.Server{Listener: listener, Handler: serverIn} + // + // dnsServers = append(dnsServers, &dnsServer) + // go dnsServer.ActivateAndServe() + //} + + return tcpListeners, dnsServers +} + // Stop stop the DNS Server on tun func (s *DNSServer) Stop() { - // shutdown TCP DNS Server - s.Server.Shutdown() - // remove TCP endpoint from stack - if s.Listener != nil { - s.Listener.Close() + if s == nil { + return + } + + for i := 0; i < len(s.udpEndpointIDs); i++ { + ep := s.udpEndpoints[i] + id := s.udpEndpointIDs[i] + // remove udp endpoint from stack + s.stack.UnregisterTransportEndpoint( + []tcpip.NetworkProtocolNumber{ + ipv4.ProtocolNumber, + ipv6.ProtocolNumber, + }, + udp.ProtocolNumber, + *id, + ep, + ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint + s.NICID) + } + + for _, server := range s.dnsServers { + server.Shutdown() + } + + for _, listener := range s.tcpListeners { + listener.Close() } - // remove udp endpoint from stack - s.stack.UnregisterTransportEndpoint( - []tcpip.NetworkProtocolNumber{ - ipv4.ProtocolNumber, - ipv6.ProtocolNumber, - }, - udp.ProtocolNumber, - *s.udpEndpointID, - s.udpEndpoint, - ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint - s.NICID) } // DnsHijack return the listening address of DNS Server func (t *gvisorAdapter) DnsHijack() []string { - results := make([]string, len(t.dnsServers)) - for i, dnsServer := range t.dnsServers { - id := dnsServer.udpEndpointID - results[i] = fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort) + dnsHijackArr := make([]string, len(t.dnsServer.udpEndpoints)) + for _, id := range t.dnsServer.udpEndpointIDs { + dnsHijackArr = append(dnsHijackArr, fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort)) } - return results + return dnsHijackArr } -func (t *gvisorAdapter) StopAllDNSServer() { - for _, dnsServer := range t.dnsServers { - dnsServer.Stop() - } +func (t *gvisorAdapter) StopDNSServer() { + t.dnsServer.Stop() log.Debugln("tun DNS server stoped") - t.dnsServers = nil + t.dnsServer = nil } // ReCreateDNSServer recreate the DNS Server on tun -func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addrs []string) error { - t.StopAllDNSServer() +func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijackArr []string) error { + t.StopDNSServer() if resolver == nil { return fmt.Errorf("failed to create DNS server on tun: resolver not provided") } - if len(addrs) == 0 { + if len(dnsHijackArr) == 0 { return fmt.Errorf("failed to create DNS server on tun: len(addrs) == 0") } - for _, addr := range addrs { - var err error - _, port, err := net.SplitHostPort(addr) - if port == "0" || port == "" || err != nil { - return nil - } + var err error + var addrs []net.Addr + for _, addr := range dnsHijackArr { + var ( + addrType string + hostPort string + ) - udpAddr, err := net.ResolveUDPAddr("udp", addr) + addrType, hostPort, err = Common.SplitNetworkType(addr) if err != nil { return err } - server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID) - if err != nil { - return err + var ( + host, port string + hasPort bool + ) + + host, port, hasPort, err = Common.SplitHostPort(hostPort) + if !hasPort { + port = "53" } - t.dnsServers = append(t.dnsServers, server) - log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled()) + + switch addrType { + case "udp", "": + { + var udpDNS *net.UDPAddr + udpDNS, err = net.ResolveUDPAddr("udp", net.JoinHostPort(host, port)) + if err != nil { + return err + } + + addrs = append(addrs, udpDNS) + break + } + case "tcp": + { + var tcpDNS *net.TCPAddr + tcpDNS, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port)) + if err != nil { + return err + } + + addrs = append(addrs, tcpDNS) + break + } + default: + err = fmt.Errorf("unspported dns scheme:%s", addrType) + } + } + server, err := CreateDNSServer(t.ipstack, resolver, mapper, addrs, nicID) + if err != nil { + return err + } + + t.dnsServer = server + return nil } From 00e44cd141020e28fce8416bf83a41562989cbf5 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sun, 9 Jan 2022 00:36:05 +0800 Subject: [PATCH 10/27] [Style] Modify the default configuration, tun config delete default hijack dns and modify auto-route to false. modify NameServer to 223.5.5.5 and 119.29.29.29 by Skyxim --- config/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 54db63d6..73389540 100644 --- a/config/config.go +++ b/config/config.go @@ -208,8 +208,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Tun: Tun{ Enable: false, Stack: "gvisor", - DnsHijack: []string{"192.18.0.2:53"}, - AutoRoute: true, + DnsHijack: []string{}, + AutoRoute: false, }, DNS: RawDNS{ Enable: false, @@ -228,8 +228,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { "1.0.0.1", }, NameServer: []string{ - "https://8.8.8.8/dns-query", - "https://1.0.0.1/dns-query", + "223.5.5.5", + "119.29.29", }, FakeIPFilter: []string{ "dns.msftnsci.com", From 8f3385bbb607afbe7f0d1df58444c385b4d63a4f Mon Sep 17 00:00:00 2001 From: Digital Pencil Date: Mon, 10 Jan 2022 20:24:20 +0800 Subject: [PATCH 11/27] Feature: support snell v3 (#1884) --- adapter/outbound/snell.go | 28 ++++++- test/clash_test.go | 2 +- test/snell_test.go | 36 +++++++++ transport/snell/snell.go | 163 +++++++++++++++++++++++++++++++++++++- 4 files changed, 223 insertions(+), 6 deletions(-) diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index 3b0dd4bc..16791836 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -27,6 +27,7 @@ type SnellOption struct { Server string `proxy:"server"` Port int `proxy:"port"` Psk string `proxy:"psk"` + UDP bool `proxy:"udp,omitempty"` Version int `proxy:"version,omitempty"` ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` } @@ -85,6 +86,24 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d return NewConn(c, s), err } +// ListenPacketContext implements C.ProxyAdapter +func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...) + if err != nil { + return nil, err + } + tcpKeepAlive(c) + c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) + + err = snell.WriteUDPHeader(c, s.version) + if err != nil { + return nil, err + } + + pc := snell.PacketConn(c) + return newPacketConn(pc, s), nil +} + func NewSnell(option SnellOption) (*Snell, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) psk := []byte(option.Psk) @@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) { if option.Version == 0 { option.Version = snell.DefaultSnellVersion } - if option.Version != snell.Version1 && option.Version != snell.Version2 { + switch option.Version { + case snell.Version1, snell.Version2: + if option.UDP { + return nil, fmt.Errorf("snell version %d not support UDP", option.Version) + } + case snell.Version3: + default: return nil, fmt.Errorf("snell version error: %d", option.Version) } @@ -115,6 +140,7 @@ func NewSnell(option SnellOption) (*Snell, error) { name: option.Name, addr: addr, tp: C.Snell, + udp: option.UDP, iface: option.Interface, }, psk: psk, diff --git a/test/clash_test.go b/test/clash_test.go index 5eb9d5bd..ade2ed76 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -32,7 +32,7 @@ const ( ImageVmess = "v2fly/v2fly-core:latest" ImageTrojan = "trojangfw/trojan:latest" ImageTrojanGo = "p4gefau1t/trojan-go:latest" - ImageSnell = "icpz/snell-server:latest" + ImageSnell = "ghcr.io/icpz/snell-server:latest" ImageXray = "teddysun/xray:latest" ) diff --git a/test/snell_test.go b/test/snell_test.go index f9cd610c..e03e6dd5 100644 --- a/test/snell_test.go +++ b/test/snell_test.go @@ -120,6 +120,42 @@ func TestClash_Snell(t *testing.T) { testSuit(t, proxy) } +func TestClash_Snellv3(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + UDP: true, + Version: 3, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + func Benchmark_Snell(b *testing.B) { cfg := &container.Config{ Image: ImageSnell, diff --git a/transport/snell/snell.go b/transport/snell/snell.go index 64807b81..4cd5fba8 100644 --- a/transport/snell/snell.go +++ b/transport/snell/snell.go @@ -6,8 +6,10 @@ import ( "fmt" "io" "net" + "sync" "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/go-shadowsocks2/shadowaead" ) @@ -15,13 +17,19 @@ import ( const ( Version1 = 1 Version2 = 2 + Version3 = 3 DefaultSnellVersion = Version1 + + // max packet length + maxLength = 0x3FFF ) const ( - CommandPing byte = 0 - CommandConnect byte = 1 - CommandConnectV2 byte = 5 + CommandPing byte = 0 + CommandConnect byte = 1 + CommandConnectV2 byte = 5 + CommandUDP byte = 6 + CommondUDPForward byte = 1 CommandTunnel byte = 0 CommandPong byte = 1 @@ -100,6 +108,16 @@ func WriteHeader(conn net.Conn, host string, port uint, version int) error { return nil } +func WriteUDPHeader(conn net.Conn, version int) error { + if version < Version3 { + return errors.New("unsupport UDP version") + } + + // version, command, clientID length + _, err := conn.Write([]byte{Version, CommandUDP, 0x00}) + return err +} + // HalfClose works only on version2 func HalfClose(conn net.Conn) error { if _, err := conn.Write(endSignal); err != nil { @@ -114,10 +132,147 @@ func HalfClose(conn net.Conn) error { func StreamConn(conn net.Conn, psk []byte, version int) *Snell { var cipher shadowaead.Cipher - if version == Version2 { + if version != Version1 { cipher = NewAES128GCM(psk) } else { cipher = NewChacha20Poly1305(psk) } return &Snell{Conn: shadowaead.NewConn(conn, cipher)} } + +func PacketConn(conn net.Conn) net.PacketConn { + return &packetConn{ + Conn: conn, + } +} + +func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) + + // compose snell UDP address format (refer: icpz/snell-server-reversed) + // a brand new wheel to replace socks5 address format, well done Yachen + buf.WriteByte(CommondUDPForward) + switch socks5Addr[0] { + case socks5.AtypDomainName: + hostLen := socks5Addr[1] + buf.Write(socks5Addr[1 : 1+1+hostLen+2]) + case socks5.AtypIPv4: + buf.Write([]byte{0x00, 0x04}) + buf.Write(socks5Addr[1 : 1+net.IPv4len+2]) + case socks5.AtypIPv6: + buf.Write([]byte{0x00, 0x06}) + buf.Write(socks5Addr[1 : 1+net.IPv6len+2]) + } + + buf.Write(payload) + _, err := w.Write(buf.Bytes()) + if err != nil { + return 0, err + } + return len(payload), nil +} + +func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { + if len(payload) <= maxLength { + return writePacket(w, socks5Addr, payload) + } + + offset := 0 + total := len(payload) + for { + cursor := offset + maxLength + if cursor > total { + cursor = total + } + + n, err := writePacket(w, socks5Addr, payload[offset:cursor]) + if err != nil { + return offset + n, err + } + + offset = cursor + if offset == total { + break + } + } + + return total, nil +} + +func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, error) { + buf := pool.Get(pool.UDPBufferSize) + defer pool.Put(buf) + + n, err := r.Read(buf) + headLen := 1 + if err != nil { + return nil, 0, err + } + if n < headLen { + return nil, 0, errors.New("insufficient UDP length") + } + + // parse snell UDP response address format + switch buf[0] { + case 0x04: + headLen += net.IPv4len + 2 + if n < headLen { + err = errors.New("insufficient UDP length") + break + } + buf[0] = socks5.AtypIPv4 + case 0x06: + headLen += net.IPv6len + 2 + if n < headLen { + err = errors.New("insufficient UDP length") + break + } + buf[0] = socks5.AtypIPv6 + default: + err = errors.New("ip version invalid") + } + + if err != nil { + return nil, 0, err + } + + addr := socks5.SplitAddr(buf[0:]) + if addr == nil { + return nil, 0, errors.New("remote address invalid") + } + uAddr := addr.UDPAddr() + + length := len(payload) + if n-headLen < length { + length = n - headLen + } + copy(payload[:], buf[headLen:headLen+length]) + + return uAddr, length, nil +} + +type packetConn struct { + net.Conn + rMux sync.Mutex + wMux sync.Mutex +} + +func (pc *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) { + pc.wMux.Lock() + defer pc.wMux.Unlock() + + return WritePacket(pc, socks5.ParseAddr(addr.String()), b) +} + +func (pc *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { + pc.rMux.Lock() + defer pc.rMux.Unlock() + + addr, n, err := ReadPacket(pc.Conn, b) + if err != nil { + return 0, nil, err + } + + return n, addr, nil +} From ee6c1871a9568dde9aa0a03c87fc7112a528c6da Mon Sep 17 00:00:00 2001 From: Skyxim Date: Tue, 11 Jan 2022 22:17:24 +0800 Subject: [PATCH 12/27] [Refactor] lazy loading geosite.bat --- config/config.go | 11 +++++++++++ config/initial.go | 36 +----------------------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/config/config.go b/config/config.go index 73389540..740d0a4c 100644 --- a/config/config.go +++ b/config/config.go @@ -565,6 +565,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin parsed, parseErr := R.ParseRule(ruleName, payload, target, params) if parseErr != nil { return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) + } else { + if parsed.RuleType() == C.GEOSITE { + if err := initGeoSite(); err != nil { + return nil, nil, fmt.Errorf("can't initial GeoSite: %w", err) + } + } } if mode != T.Script { @@ -699,6 +705,11 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) { var sites []*router.DomainMatcher + if len(countries) > 0 { + if err := initGeoSite(); err != nil { + return nil, fmt.Errorf("can't initial GeoSite: %w", err) + } + } for _, country := range countries { found := false diff --git a/config/initial.go b/config/initial.go index 9d8288ba..e9869e7e 100644 --- a/config/initial.go +++ b/config/initial.go @@ -50,23 +50,6 @@ func initMMDB() error { return nil } -//func downloadGeoIP(path string) (err error) { -// resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat") -// if err != nil { -// return -// } -// defer resp.Body.Close() -// -// f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) -// if err != nil { -// return err -// } -// defer f.Close() -// _, err = io.Copy(f, resp.Body) -// -// return err -//} - func downloadGeoSite(path string) (err error) { resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat") if err != nil { @@ -84,22 +67,9 @@ func downloadGeoSite(path string) (err error) { return err } -// -//func initGeoIP() error { -// if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { -// log.Infoln("Can't find GeoIP.dat, start download") -// if err := downloadGeoIP(C.Path.GeoIP()); err != nil { -// return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) -// } -// log.Infoln("Download GeoIP.dat finish") -// } -// -// return nil -//} - func initGeoSite() error { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { - log.Infoln("Can't find GeoSite.dat, start download") + log.Infoln("Need GeoSite but can't find GeoSite.dat, start download") if err := downloadGeoSite(C.Path.GeoSite()); err != nil { return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) } @@ -139,9 +109,5 @@ func Init(dir string) error { return fmt.Errorf("can't initial MMDB: %w", err) } - // initial GeoSite - if err := initGeoSite(); err != nil { - return fmt.Errorf("can't initial GeoSite: %w", err) - } return nil } From 9732efe938306afb363c4dd0be5473eebe041181 Mon Sep 17 00:00:00 2001 From: thank243 Date: Sat, 15 Jan 2022 19:33:21 +0800 Subject: [PATCH 13/27] Fix: tls handshake requires a timeout (#1893) --- constant/adapters.go | 1 + transport/gun/gun.go | 8 +++++++- transport/trojan/trojan.go | 8 +++++++- transport/vmess/tls.go | 9 ++++++++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/constant/adapters.go b/constant/adapters.go index 136e48eb..dd754427 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -32,6 +32,7 @@ const ( const ( DefaultTCPTimeout = 5 * time.Second DefaultUDPTimeout = DefaultTCPTimeout + DefaultTLSTimeout = DefaultTCPTimeout ) type Connection interface { diff --git a/transport/gun/gun.go b/transport/gun/gun.go index f6f76116..43988004 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -5,6 +5,7 @@ package gun import ( "bufio" + "context" "crypto/tls" "encoding/binary" "errors" @@ -17,6 +18,7 @@ import ( "time" "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" "go.uber.org/atomic" "golang.org/x/net/http2" @@ -173,7 +175,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport { } cn := tls.Client(pconn, cfg) - if err := cn.Handshake(); err != nil { + + // fix tls handshake not timeout + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + if err := cn.HandshakeContext(ctx); err != nil { pconn.Close() return nil, err } diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 9d9a33b9..ac9f17dd 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -1,6 +1,7 @@ package trojan import ( + "context" "crypto/sha256" "crypto/tls" "encoding/binary" @@ -12,6 +13,7 @@ import ( "sync" "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/vmess" ) @@ -68,7 +70,11 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } tlsConn := tls.Client(conn, tlsConfig) - if err := tlsConn.Handshake(); err != nil { + + // fix tls handshake not timeout + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + if err := tlsConn.HandshakeContext(ctx); err != nil { return nil, err } diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index 234c3147..e4f29a2f 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -1,8 +1,11 @@ package vmess import ( + "context" "crypto/tls" "net" + + C "github.com/Dreamacro/clash/constant" ) type TLSConfig struct { @@ -19,6 +22,10 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { } tlsConn := tls.Client(conn, tlsConfig) - err := tlsConn.Handshake() + + // fix tls handshake not timeout + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + err := tlsConn.HandshakeContext(ctx) return tlsConn, err } From cfe7354c07e8c837691b03fb183127f431e5d218 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Tue, 18 Jan 2022 13:32:47 +0800 Subject: [PATCH 14/27] Improve: change provider file modify time when updated (#1918) --- adapter/provider/fetcher.go | 1 + 1 file changed, 1 insertion(+) diff --git a/adapter/provider/fetcher.go b/adapter/provider/fetcher.go index 6c1e96b4..81f9ec96 100644 --- a/adapter/provider/fetcher.go +++ b/adapter/provider/fetcher.go @@ -102,6 +102,7 @@ func (f *fetcher) Update() (interface{}, bool, error) { hash := md5.Sum(buf) if bytes.Equal(f.hash[:], hash[:]) { f.updatedAt = &now + os.Chtimes(f.vehicle.Path(), now, now) return nil, true, nil } From b15344ec78367058df471e2fd0a6f051d63ae6a0 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Tue, 18 Jan 2022 21:09:36 +0800 Subject: [PATCH 15/27] [Refactor] 1.allow maybe empty group 2.use COMPATIBLE(DIRECT alias) when proxy group is empty 3.http provider pass through tunnel --- adapter/inbound/socket.go | 13 ++++++++++ adapter/outbound/direct.go | 10 ++++++++ adapter/outboundgroup/common.go | 14 ++++++++-- adapter/outboundgroup/fallback.go | 5 ++-- adapter/outboundgroup/loadbalance.go | 4 ++- adapter/outboundgroup/relay.go | 6 +++-- adapter/outboundgroup/selector.go | 3 ++- adapter/outboundgroup/urltest.go | 6 +++-- adapter/provider/provider.go | 16 ++++++------ adapter/provider/vehicle.go | 5 ++-- config/config.go | 27 +------------------- constant/adapters.go | 5 ++-- constant/metadata.go | 7 +++++ hub/executor/executor.go | 38 ++++++++++++++++++++++++++-- listener/inner/tcp.go | 20 +++++++++++++++ listener/listener.go | 2 ++ rule/provider.go | 4 +++ 17 files changed, 135 insertions(+), 50 deletions(-) create mode 100644 listener/inner/tcp.go diff --git a/adapter/inbound/socket.go b/adapter/inbound/socket.go index be717017..f761bd9b 100644 --- a/adapter/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -20,3 +20,16 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo return context.NewConnContext(conn, metadata) } + +func NewInner(conn net.Conn, dst string, host string) *context.ConnContext { + metadata := &C.Metadata{} + metadata.NetWork = C.TCP + metadata.Type = C.INNER + metadata.DNSMode = C.DNSMapping + metadata.AddrType = C.AtypDomainName + metadata.Host = host + if _, port, err := parseAddr(dst); err == nil { + metadata.DstPort = port + } + return context.NewConnContext(conn, metadata) +} diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 4c4305f5..4cf16e38 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -44,3 +44,13 @@ func NewDirect() *Direct { }, } } + +func NewCompatible() *Direct { + return &Direct{ + Base: &Base{ + name: "COMPATIBLE", + tp: C.Compatible, + udp: true, + }, + } +} diff --git a/adapter/outboundgroup/common.go b/adapter/outboundgroup/common.go index 1c9333b9..ab988f55 100644 --- a/adapter/outboundgroup/common.go +++ b/adapter/outboundgroup/common.go @@ -1,6 +1,7 @@ package outboundgroup import ( + "github.com/Dreamacro/clash/tunnel" "regexp" "time" @@ -21,6 +22,7 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter proxies = append(proxies, provider.Proxies()...) } } + var filterReg *regexp.Regexp = nil matchedProxies := []C.Proxy{} if len(filter) > 0 { @@ -32,8 +34,16 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter } //if no proxy matched, means bad filter, return all proxies if len(matchedProxies) > 0 { - proxies = matchedProxies + return matchedProxies + } else { + return append([]C.Proxy{}, tunnel.Proxies()["COMPATIBLE"]) + } + } else { + if len(proxies) == 0 { + return append(proxies, tunnel.Proxies()["COMPATIBLE"]) + } else { + return proxies } } - return proxies + } diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 734bd6b7..b795229b 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -72,7 +72,7 @@ func (f *Fallback) onDialFailed() { } else { failedCount := f.failedTimes.Inc() log.Warnln("%s failed count: %d", f.Name(), failedCount) - if failedCount >= 5 && failedCount < 6 { + if failedCount >= 5 { log.Warnln("because %s failed multiple times, active health check", f.Name()) for _, proxyProvider := range f.providers { go proxyProvider.HealthCheck() @@ -97,10 +97,11 @@ func (f *Fallback) SupportUDP() bool { // MarshalJSON implements C.ProxyAdapter func (f *Fallback) MarshalJSON() ([]byte, error) { - var all []string + all := make([]string, 0) for _, proxy := range f.proxies(false) { all = append(all, proxy.Name()) } + return json.Marshal(map[string]interface{}{ "type": f.Type().String(), "now": f.Now(), diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 2a6e3b30..402062c8 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -150,10 +150,12 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy { // MarshalJSON implements C.ProxyAdapter func (lb *LoadBalance) MarshalJSON() ([]byte, error) { - var all []string + all := make([]string, 0) + for _, proxy := range lb.proxies(false) { all = append(all, proxy.Name()) } + return json.Marshal(map[string]interface{}{ "type": lb.Type().String(), "all": all, diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index fab24ed4..f794afe3 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -23,7 +23,7 @@ type Relay struct { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { var proxies []C.Proxy for _, proxy := range r.proxies(metadata, true) { - if proxy.Type() != C.Direct { + if proxy.Type() != C.Direct && proxy.Type() != C.Compatible { proxies = append(proxies, proxy) } } @@ -69,10 +69,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d // MarshalJSON implements C.ProxyAdapter func (r *Relay) MarshalJSON() ([]byte, error) { - var all []string + all := make([]string, 0) + for _, proxy := range r.rawProxies(false) { all = append(all, proxy.Name()) } + return json.Marshal(map[string]interface{}{ "type": r.Type().String(), "all": all, diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index d79e4a92..47b8d34f 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -50,7 +50,8 @@ func (s *Selector) SupportUDP() bool { // MarshalJSON implements C.ProxyAdapter func (s *Selector) MarshalJSON() ([]byte, error) { - var all []string + all := make([]string, 0) + for _, proxy := range getProvidersProxies(s.providers, false, s.filter) { all = append(all, proxy.Name()) } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 836dcaac..28a9fe40 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -125,10 +125,12 @@ func (u *URLTest) SupportUDP() bool { // MarshalJSON implements C.ProxyAdapter func (u *URLTest) MarshalJSON() ([]byte, error) { - var all []string + all := make([]string, 0) + for _, proxy := range u.proxies(false) { all = append(all, proxy.Name()) } + return json.Marshal(map[string]interface{}{ "type": u.Type().String(), "now": u.Now(), @@ -149,7 +151,7 @@ func (u *URLTest) onDialFailed() { } else { failedCount := u.failedTimes.Inc() log.Warnln("%s failed count: %d", u.Name(), failedCount) - if failedCount >= 5 && failedCount < 6 { + if failedCount >= 5 { log.Warnln("because %s failed multiple times, active health check", u.Name()) for _, proxyProvider := range u.providers { go proxyProvider.HealthCheck() diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 6e27d2cc..49aceda0 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -67,6 +67,10 @@ func (pp *proxySetProvider) Initial() error { } pp.onUpdate(elm) + if pp.healthCheck.auto() { + go pp.healthCheck.process() + } + return nil } @@ -102,10 +106,6 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh return nil, fmt.Errorf("invalid filter regex: %w", err) } - if hc.auto() { - go hc.process() - } - pd := &proxySetProvider{ proxies: []C.Proxy{}, healthCheck: hc, @@ -190,6 +190,10 @@ func (cp *compatibleProvider) Update() error { } func (cp *compatibleProvider) Initial() error { + if cp.healthCheck.auto() { + go cp.healthCheck.process() + } + return nil } @@ -219,10 +223,6 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co return nil, errors.New("provider need one proxy at least") } - if hc.auto() { - go hc.process() - } - pd := &compatibleProvider{ name: name, proxies: proxies, diff --git a/adapter/provider/vehicle.go b/adapter/provider/vehicle.go index a0237a92..e2efa46e 100644 --- a/adapter/provider/vehicle.go +++ b/adapter/provider/vehicle.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/Dreamacro/clash/listener/inner" "io" "net" "net/http" @@ -10,7 +11,6 @@ import ( "time" netHttp "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/dialer" types "github.com/Dreamacro/clash/constant/provider" ) @@ -77,7 +77,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) { TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - return dialer.DialContext(ctx, network, address) + conn := inner.HandleTcp(address, uri.Hostname()) + return conn, nil }, } diff --git a/config/config.go b/config/config.go index 740d0a4c..1a2d218f 100644 --- a/config/config.go +++ b/config/config.go @@ -350,6 +350,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) + proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible()) proxyList = append(proxyList, "DIRECT", "REJECT") // parse proxy @@ -396,13 +397,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ providersMap[name] = pd } - for _, rp := range providersMap { - log.Infoln("Start initial provider %s", rp.Name()) - if err := rp.Initial(); err != nil { - return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", rp.Name(), err) - } - } - // parse proxy group for idx, mapping := range groupsConfig { group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) @@ -418,18 +412,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ proxies[groupName] = adapter.NewProxy(group) } - // initial compatible provider - for _, pd := range providersMap { - if pd.VehicleType() != providerTypes.Compatible { - continue - } - - log.Infoln("Start initial compatible provider %s", pd.Name()) - if err := pd.Initial(); err != nil { - return nil, nil, err - } - } - var ps []C.Proxy for _, v := range proxyList { ps = append(ps, proxies[v]) @@ -511,13 +493,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin R.SetRuleProvider(rp) } - for _, ruleProvider := range ruleProviders { - log.Infoln("Start initial provider %s", (*ruleProvider).Name()) - if err := (*ruleProvider).Initial(); err != nil { - return nil, nil, fmt.Errorf("initial rule provider %s error: %w", (*ruleProvider).Name(), err) - } - } - var rules []C.Rule rulesConfig := cfg.Rule mode := cfg.Mode diff --git a/constant/adapters.go b/constant/adapters.go index c152bc7b..e460e41f 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -13,7 +13,7 @@ import ( const ( Direct AdapterType = iota Reject - + Compatible Shadowsocks ShadowsocksR Snell @@ -128,7 +128,8 @@ func (at AdapterType) String() string { return "Direct" case Reject: return "Reject" - + case Compatible: + return "Compatible" case Shadowsocks: return "Shadowsocks" case ShadowsocksR: diff --git a/constant/metadata.go b/constant/metadata.go index f14b93f3..73a49973 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -24,6 +24,7 @@ const ( REDIR TPROXY TUN + INNER ) type NetWork int @@ -59,6 +60,8 @@ func (t Type) String() string { return "TProxy" case TUN: return "Tun" + case INNER: + return "Inner" default: return "Unknown" } @@ -94,6 +97,10 @@ func (m *Metadata) SourceDetail() string { if m.Process != "" { return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process) } else { + if m.Type == INNER { + return fmt.Sprintf("[Clash]") + } + return fmt.Sprintf("%s", m.SourceAddress()) } } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 5e2fa1fd..6d06470f 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -74,15 +74,17 @@ func ApplyConfig(cfg *config.Config, force bool) { defer mux.Unlock() updateUsers(cfg.Users) - updateProxies(cfg.Proxies, cfg.Providers) - updateRules(cfg.Rules, cfg.RuleProviders) updateHosts(cfg.Hosts) updateProfile(cfg) + updateProxies(cfg.Proxies, cfg.Providers) + updateRules(cfg.Rules, cfg.RuleProviders) updateIPTables(cfg.DNS, cfg.General, cfg.Tun) updateDNS(cfg.DNS, cfg.Tun) updateGeneral(cfg.General, cfg.Tun, force) updateTun(cfg.Tun) updateExperimental(cfg) + + loadProvider(cfg.RuleProviders, cfg.Providers) } func GetGeneral() *config.General { @@ -175,6 +177,38 @@ func updateRules(rules []C.Rule, ruleProviders map[string]*provider.RuleProvider tunnel.UpdateRules(rules, ruleProviders) } +func loadProvider(ruleProviders map[string]*provider.RuleProvider, proxyProviders map[string]provider.ProxyProvider) { + load := func(pv provider.Provider) { + if pv.VehicleType() == provider.Compatible { + log.Infoln("Start initial compatible provider %s", pv.Name()) + } else { + log.Infoln("Start initial provider %s", (pv).Name()) + } + + if err := (pv).Initial(); err != nil { + switch pv.Type() { + case provider.Proxy: + { + log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err) + } + case provider.Rule: + { + log.Warnln("initial rule provider %s error: %v", (pv).Name(), err) + } + + } + } + } + + for _, proxyProvider := range proxyProviders { + load(proxyProvider) + } + + for _, ruleProvider := range ruleProviders { + load(*ruleProvider) + } +} + func updateGeneral(general *config.General, Tun *config.Tun, force bool) { tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 diff --git a/listener/inner/tcp.go b/listener/inner/tcp.go new file mode 100644 index 00000000..a7e38588 --- /dev/null +++ b/listener/inner/tcp.go @@ -0,0 +1,20 @@ +package inner + +import ( + "github.com/Dreamacro/clash/adapter/inbound" + C "github.com/Dreamacro/clash/constant" + "net" +) + +var tcpIn chan<- C.ConnContext + +func New(in chan<- C.ConnContext) { + tcpIn = in +} + +func HandleTcp(dst string, host string) net.Conn { + conn1, conn2 := net.Pipe() + context := inbound.NewInner(conn2, dst, host) + tcpIn <- context + return conn1 +} diff --git a/listener/listener.go b/listener/listener.go index 11e02e27..675abb5d 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -2,6 +2,7 @@ package proxy import ( "fmt" + "github.com/Dreamacro/clash/listener/inner" "net" "runtime" "strconv" @@ -124,6 +125,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P log.Errorln("Start SOCKS server error: %s", err.Error()) } }() + inner.New(tcpIn) addr := genAddr(bindAddress, port, allowLan) diff --git a/rule/provider.go b/rule/provider.go index ea409f55..561dd12a 100644 --- a/rule/provider.go +++ b/rule/provider.go @@ -75,6 +75,10 @@ func (rp *ruleSetProvider) Behavior() P.RuleType { } func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool { + if rp.count == 0 { + return false + } + switch rp.behavior { case P.Domain: return rp.DomainRules.Search(metadata.Host) != nil From daf83eb6f79508876636f8800ebf4842fa24b3c9 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 21 Jan 2022 22:38:02 +0800 Subject: [PATCH 16/27] [Fixed] select group crash --- adapter/outboundgroup/selector.go | 3 +-- hub/executor/executor.go | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 47b8d34f..909050bf 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -100,7 +100,6 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy { } func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { - selected := providers[0].Proxies()[0].Name() return &Selector{ Base: outbound.NewBase(outbound.BaseOption{ Name: option.Name, @@ -110,7 +109,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, - selected: selected, + selected: "COMPATIBLE", disableUDP: option.DisableUDP, filter: option.Filter, } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 6d06470f..f8e34ea8 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,7 +2,6 @@ package executor import ( "fmt" - "github.com/Dreamacro/clash/listener/tproxy" "net" "os" "runtime" @@ -10,6 +9,8 @@ import ( "strings" "sync" + "github.com/Dreamacro/clash/listener/tproxy" + "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/component/auth" @@ -75,7 +76,6 @@ func ApplyConfig(cfg *config.Config, force bool) { updateUsers(cfg.Users) updateHosts(cfg.Hosts) - updateProfile(cfg) updateProxies(cfg.Proxies, cfg.Providers) updateRules(cfg.Rules, cfg.RuleProviders) updateIPTables(cfg.DNS, cfg.General, cfg.Tun) @@ -83,8 +83,9 @@ func ApplyConfig(cfg *config.Config, force bool) { updateGeneral(cfg.General, cfg.Tun, force) updateTun(cfg.Tun) updateExperimental(cfg) - loadProvider(cfg.RuleProviders, cfg.Providers) + updateProfile(cfg) + } func GetGeneral() *config.General { From 58a47e1835ff0ad70835f364da3113ee45a9a0e4 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 21 Jan 2022 22:38:28 +0800 Subject: [PATCH 17/27] [Style] clear unless notes --- adapter/outboundgroup/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapter/outboundgroup/common.go b/adapter/outboundgroup/common.go index ab988f55..3f84ba42 100644 --- a/adapter/outboundgroup/common.go +++ b/adapter/outboundgroup/common.go @@ -32,7 +32,7 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter matchedProxies = append(matchedProxies, p) } } - //if no proxy matched, means bad filter, return all proxies + if len(matchedProxies) > 0 { return matchedProxies } else { From e5c99cbee73d9016c36b933751a49535cc89154e Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 21 Jan 2022 22:39:00 +0800 Subject: [PATCH 18/27] modify gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 52efcc9b..e7ee7ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ vendor # test suite test/config/cache* +/output +/.vscode \ No newline at end of file From 03b956b7a31311bece31d02098ed51b77e3e6dee Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 22 Jan 2022 13:24:31 +0800 Subject: [PATCH 19/27] [Fixed] auto-route support use ip route --- config/config.go | 2 +- listener/tun/dev/dev_linux.go | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 1a2d218f..d4437865 100644 --- a/config/config.go +++ b/config/config.go @@ -208,7 +208,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Tun: Tun{ Enable: false, Stack: "gvisor", - DnsHijack: []string{}, + DnsHijack: []string{"198.18.0.2:53"}, AutoRoute: false, }, DNS: RawDNS{ diff --git a/listener/tun/dev/dev_linux.go b/listener/tun/dev/dev_linux.go index aeb90d4e..d43433ad 100644 --- a/listener/tun/dev/dev_linux.go +++ b/listener/tun/dev/dev_linux.go @@ -63,8 +63,9 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { } if autoRoute { - log.Warnln("linux unsupported automatic route") + addRoute(tunAddress) } + return dev, nil case "fd": fd, err := strconv.ParseInt(deviceURL.Host, 10, 32) @@ -328,3 +329,21 @@ func GetAutoDetectInterface() (string, error) { } return out.String(), nil } + +func addRoute(gateway string) { + cmd := exec.Command("route", "add", "default", "gw", gateway) + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String()) + } +} + +func delRoute(gateway string) { + cmd := exec.Command("ip", "route", "delete", "gw") + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String()) + } +} From 8595d6c2e9e714bd78bceeddcf8d2d034cb42a86 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 22 Jan 2022 22:10:45 +0800 Subject: [PATCH 20/27] [Feature] 1.Add Network rule, match network type(TCP/UDP) 2.Add logic rules(NOT,OR,AND) -AND,((DOMAIN,baidu.com),(NETWORK,UDP)),REJECT (cherry picked from commit d7092e2e37f2c48282c878edea1b2ebc2912b09a) --- common/collections/stack.go | 56 +++++++++ config/config.go | 44 ++++--- constant/rule.go | 13 ++ rule/{ => common}/base.go | 6 +- rule/{ => common}/domain.go | 2 +- rule/{ => common}/domain_keyword.go | 2 +- rule/{ => common}/domain_suffix.go | 2 +- rule/{ => common}/final.go | 2 +- rule/{ => common}/geoip.go | 2 +- rule/{ => common}/geosite.go | 2 +- rule/{ => common}/ipcidr.go | 2 +- rule/common/network_type.go | 53 ++++++++ rule/{ => common}/port.go | 2 +- rule/{ => common}/process.go | 2 +- rule/logic/and.go | 58 +++++++++ rule/logic/common.go | 185 ++++++++++++++++++++++++++++ rule/logic/not.go | 51 ++++++++ rule/logic/or.go | 58 +++++++++ rule/parser.go | 90 +++++--------- rule/{ => provider}/fetcher.go | 2 +- rule/provider/parse.go | 93 ++++++++++++++ rule/{ => provider}/provider.go | 4 +- rule/{ => provider}/rule_set.go | 2 +- tunnel/tunnel.go | 2 +- 24 files changed, 637 insertions(+), 98 deletions(-) create mode 100644 common/collections/stack.go rename rule/{ => common}/base.go (85%) rename rule/{ => common}/domain.go (98%) rename rule/{ => common}/domain_keyword.go (98%) rename rule/{ => common}/domain_suffix.go (98%) rename rule/{ => common}/final.go (97%) rename rule/{ => common}/geoip.go (98%) rename rule/{ => common}/geosite.go (98%) rename rule/{ => common}/ipcidr.go (98%) create mode 100644 rule/common/network_type.go rename rule/{ => common}/port.go (99%) rename rule/{ => common}/process.go (99%) create mode 100644 rule/logic/and.go create mode 100644 rule/logic/common.go create mode 100644 rule/logic/not.go create mode 100644 rule/logic/or.go rename rule/{ => provider}/fetcher.go (99%) create mode 100644 rule/provider/parse.go rename rule/{ => provider}/provider.go (98%) rename rule/{ => provider}/rule_set.go (98%) diff --git a/common/collections/stack.go b/common/collections/stack.go new file mode 100644 index 00000000..74673f9a --- /dev/null +++ b/common/collections/stack.go @@ -0,0 +1,56 @@ +package collections + +import "sync" + +type ( + stack struct { + top *node + length int + lock *sync.RWMutex + } + + node struct { + value interface{} + prev *node + } +) + +// NewStack Create a new stack +func NewStack() *stack { + return &stack{nil, 0, &sync.RWMutex{}} +} + +// Len Return the number of items in the stack +func (this *stack) Len() int { + return this.length +} + +// Peek View the top item on the stack +func (this *stack) Peek() interface{} { + if this.length == 0 { + return nil + } + return this.top.value +} + +// Pop the top item of the stack and return it +func (this *stack) Pop() interface{} { + this.lock.Lock() + defer this.lock.Unlock() + if this.length == 0 { + return nil + } + n := this.top + this.top = n.prev + this.length-- + return n.value +} + +// Push a value onto the top of the stack +func (this *stack) Push(value interface{}) { + this.lock.Lock() + defer this.lock.Unlock() + n := &node{value, this.top} + this.top = n + this.length++ +} diff --git a/config/config.go b/config/config.go index d4437865..a302a63a 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,8 @@ import ( "container/list" "errors" "fmt" + R "github.com/Dreamacro/clash/rule" + RP "github.com/Dreamacro/clash/rule/provider" "net" "net/url" "os" @@ -24,7 +26,6 @@ import ( providerTypes "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" - R "github.com/Dreamacro/clash/rule" T "github.com/Dreamacro/clash/tunnel" "gopkg.in/yaml.v2" @@ -484,13 +485,13 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin // parse rule provider for name, mapping := range cfg.RuleProvider { - rp, err := R.ParseRuleProvider(name, mapping) + rp, err := RP.ParseRuleProvider(name, mapping) if err != nil { return nil, nil, err } ruleProviders[name] = &rp - R.SetRuleProvider(rp) + RP.SetRuleProvider(rp) } var rules []C.Rule @@ -511,24 +512,29 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin continue } - switch l := len(rule); { - case l == 2: - target = rule[1] - case l == 3: - if ruleName == "MATCH" { - payload = "" + if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" { + payload = strings.Join(rule[1:len(rule)-1], ",") + target = rule[len(rule)-1] + } else { + switch l := len(rule); { + case l == 2: target = rule[1] - params = rule[2:] - break + case l == 3: + if ruleName == "MATCH" { + payload = "" + target = rule[1] + params = rule[2:] + break + } + payload = rule[1] + target = rule[2] + case l >= 4: + payload = rule[1] + target = rule[2] + params = rule[3:] + default: + return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) } - payload = rule[1] - target = rule[2] - case l >= 4: - payload = rule[1] - target = rule[2] - params = rule[3:] - default: - return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) } if _, ok := proxies[target]; mode != T.Script && !ok { diff --git a/constant/rule.go b/constant/rule.go index df7a5d19..2efc2010 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -14,7 +14,12 @@ const ( Process Script RuleSet + Network + Combination MATCH + AND + OR + NOT ) type RuleType int @@ -47,6 +52,14 @@ func (rt RuleType) String() string { return "Match" case RuleSet: return "RuleSet" + case Network: + return "Network" + case AND: + return "AND" + case OR: + return "OR" + case NOT: + return "NOT" default: return "Unknown" } diff --git a/rule/base.go b/rule/common/base.go similarity index 85% rename from rule/base.go rename to rule/common/base.go index 96d8bfe5..3e27ac24 100644 --- a/rule/base.go +++ b/rule/common/base.go @@ -1,4 +1,4 @@ -package rules +package common import ( "errors" @@ -22,7 +22,7 @@ func HasNoResolve(params []string) bool { return false } -func findNetwork(params []string) C.NetWork { +func FindNetwork(params []string) C.NetWork { for _, p := range params { if p == "tcp" { return C.TCP @@ -33,7 +33,7 @@ func findNetwork(params []string) C.NetWork { return C.ALLNet } -func findSourceIPs(params []string) []*net.IPNet { +func FindSourceIPs(params []string) []*net.IPNet { var ips []*net.IPNet for _, p := range params { if p == noResolve || len(p) < 7 { diff --git a/rule/domain.go b/rule/common/domain.go similarity index 98% rename from rule/domain.go rename to rule/common/domain.go index 42281b5d..31aa0f5b 100644 --- a/rule/domain.go +++ b/rule/common/domain.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/domain_keyword.go b/rule/common/domain_keyword.go similarity index 98% rename from rule/domain_keyword.go rename to rule/common/domain_keyword.go index 933e2524..90819b9d 100644 --- a/rule/domain_keyword.go +++ b/rule/common/domain_keyword.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/domain_suffix.go b/rule/common/domain_suffix.go similarity index 98% rename from rule/domain_suffix.go rename to rule/common/domain_suffix.go index 0ea4cea6..d577e721 100644 --- a/rule/domain_suffix.go +++ b/rule/common/domain_suffix.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/final.go b/rule/common/final.go similarity index 97% rename from rule/final.go rename to rule/common/final.go index 532824b9..1cdd20f1 100644 --- a/rule/final.go +++ b/rule/common/final.go @@ -1,4 +1,4 @@ -package rules +package common import ( C "github.com/Dreamacro/clash/constant" diff --git a/rule/geoip.go b/rule/common/geoip.go similarity index 98% rename from rule/geoip.go rename to rule/common/geoip.go index 1783b7b7..1485a335 100644 --- a/rule/geoip.go +++ b/rule/common/geoip.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/geosite.go b/rule/common/geosite.go similarity index 98% rename from rule/geosite.go rename to rule/common/geosite.go index 875320bd..fcc04c17 100644 --- a/rule/geosite.go +++ b/rule/common/geosite.go @@ -1,4 +1,4 @@ -package rules +package common import ( "fmt" diff --git a/rule/ipcidr.go b/rule/common/ipcidr.go similarity index 98% rename from rule/ipcidr.go rename to rule/common/ipcidr.go index 8910110f..7f7f437d 100644 --- a/rule/ipcidr.go +++ b/rule/common/ipcidr.go @@ -1,4 +1,4 @@ -package rules +package common import ( "net" diff --git a/rule/common/network_type.go b/rule/common/network_type.go new file mode 100644 index 00000000..965f9575 --- /dev/null +++ b/rule/common/network_type.go @@ -0,0 +1,53 @@ +package common + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" + "strings" +) + +type NetworkType struct { + network C.NetWork + adapter string +} + +func NewNetworkType(network, adapter string) (*NetworkType, error) { + ntType := new(NetworkType) + ntType.adapter = adapter + switch strings.ToUpper(network) { + case "TCP": + ntType.network = C.TCP + break + case "UDP": + ntType.network = C.UDP + break + default: + return nil, fmt.Errorf("unsupported network type, only TCP/UDP") + } + + return ntType, nil +} + +func (n *NetworkType) RuleType() C.RuleType { + return C.Network +} + +func (n *NetworkType) Match(metadata *C.Metadata) bool { + return n.network == metadata.NetWork +} + +func (n *NetworkType) Adapter() string { + return n.adapter +} + +func (n *NetworkType) Payload() string { + return n.network.String() +} + +func (n *NetworkType) ShouldResolveIP() bool { + return false +} + +func (n *NetworkType) RuleExtra() *C.RuleExtra { + return nil +} diff --git a/rule/port.go b/rule/common/port.go similarity index 99% rename from rule/port.go rename to rule/common/port.go index e978e28d..6990af50 100644 --- a/rule/port.go +++ b/rule/common/port.go @@ -1,4 +1,4 @@ -package rules +package common import ( "fmt" diff --git a/rule/process.go b/rule/common/process.go similarity index 99% rename from rule/process.go rename to rule/common/process.go index aa2a9129..47731443 100644 --- a/rule/process.go +++ b/rule/common/process.go @@ -1,4 +1,4 @@ -package rules +package common import ( "fmt" diff --git a/rule/logic/and.go b/rule/logic/and.go new file mode 100644 index 00000000..f75de048 --- /dev/null +++ b/rule/logic/and.go @@ -0,0 +1,58 @@ +package logic + +import C "github.com/Dreamacro/clash/constant" + +type AND struct { + rules []C.Rule + payload string + adapter string + needIP bool +} + +func NewAND(payload string, adapter string) (*AND, error) { + and := &AND{payload: payload, adapter: adapter} + rules, err := parseRule(payload) + if err != nil { + return nil, err + } + + and.rules = rules + for _, rule := range rules { + if rule.ShouldResolveIP() { + and.needIP = true + break + } + } + + return and, nil +} + +func (A *AND) RuleType() C.RuleType { + return C.AND +} + +func (A *AND) Match(metadata *C.Metadata) bool { + for _, rule := range A.rules { + if !rule.Match(metadata) { + return false + } + } + + return true +} + +func (A *AND) Adapter() string { + return A.adapter +} + +func (A *AND) Payload() string { + return A.payload +} + +func (A *AND) ShouldResolveIP() bool { + return A.needIP +} + +func (A *AND) RuleExtra() *C.RuleExtra { + return nil +} diff --git a/rule/logic/common.go b/rule/logic/common.go new file mode 100644 index 00000000..4c1aa44c --- /dev/null +++ b/rule/logic/common.go @@ -0,0 +1,185 @@ +package logic + +import ( + "fmt" + "github.com/Dreamacro/clash/common/collections" + C "github.com/Dreamacro/clash/constant" + RC "github.com/Dreamacro/clash/rule/common" + "github.com/Dreamacro/clash/rule/provider" + "regexp" + "strings" +) + +func parseRule(payload string) ([]C.Rule, error) { + regex, err := regexp.Compile("\\(.*\\)") + if err != nil { + return nil, err + } + + if regex.MatchString(payload) { + subRanges, err := format(payload) + if err != nil { + return nil, err + } + rules := make([]C.Rule, 0, len(subRanges)) + + if len(subRanges) == 1 { + subPayload := payload[subRanges[0].start+1 : subRanges[0].end-1] + rule, err := payloadToRule(subPayload) + if err != nil { + return nil, err + } + + rules = append(rules, rule) + } else { + preStart := subRanges[0].start + preEnd := subRanges[0].end + for _, sr := range subRanges[1:] { + if containRange(sr, preStart, preEnd) && sr.start-preStart > 1 { + str := "" + if preStart+1 <= sr.start-1 { + str = strings.TrimSpace(payload[preStart+1 : sr.start-1]) + } + + if str == "AND" || str == "OR" || str == "NOT" { + subPayload := payload[preStart+1 : preEnd] + rule, err := payloadToRule(subPayload) + if err != nil { + return nil, err + } + + rules = append(rules, rule) + preStart = sr.start + preEnd = sr.end + } + + continue + } + + preStart = sr.start + preEnd = sr.end + + subPayload := payload[sr.start+1 : sr.end] + rule, err := payloadToRule(subPayload) + if err != nil { + return nil, err + } + + rules = append(rules, rule) + } + } + + return rules, nil + } + + return nil, fmt.Errorf("payload format error") +} + +func containRange(r Range, preStart, preEnd int) bool { + return preStart < r.start && preEnd > r.end +} + +func payloadToRule(subPayload string) (C.Rule, error) { + splitStr := strings.SplitN(subPayload, ",", 2) + tp := splitStr[0] + payload := splitStr[1] + if tp == "NOT" || tp == "OR" || tp == "AND" { + return parseSubRule(tp, payload, nil) + } + + param := strings.Split(payload, ",") + return parseSubRule(tp, param[0], param[1:]) +} + +func splitSubRule(subRuleStr string) (string, string, []string, error) { + typeAndRule := strings.Split(subRuleStr, ",") + if len(typeAndRule) < 2 { + return "", "", nil, fmt.Errorf("format error:[%s]", typeAndRule) + } + + return strings.TrimSpace(typeAndRule[0]), strings.TrimSpace(typeAndRule[1]), typeAndRule[2:], nil +} + +func parseSubRule(tp, payload string, params []string) (C.Rule, error) { + var ( + parseErr error + parsed C.Rule + ) + + switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, "", nil) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, "", nil) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, "", nil) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, "", nil) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, "", noResolve, nil) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, "", true, nil) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, "", false, nil) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, "", nil) + case "RULE-SET": + parsed, parseErr = provider.NewRuleSet(payload, "", nil) + case "NOT": + parsed, parseErr = NewNOT(payload, "") + case "AND": + parsed, parseErr = NewAND(payload, "") + case "OR": + parsed, parseErr = NewOR(payload, "") + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, "") + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + + return parsed, parseErr +} + +type Range struct { + start int + end int + index int +} + +func format(payload string) ([]Range, error) { + stack := collections.NewStack() + num := 0 + subRanges := make([]Range, 0) + for i, c := range payload { + if c == '(' { + sr := Range{ + start: i, + index: num, + } + + num++ + stack.Push(sr) + } else if c == ')' { + sr := stack.Pop().(Range) + sr.end = i + subRanges = append(subRanges, sr) + } + } + + if stack.Len() != 0 { + return nil, fmt.Errorf("format error is missing )") + } + + sortResult := make([]Range, len(subRanges)) + for _, sr := range subRanges { + sortResult[sr.index] = sr + } + + return sortResult, nil +} diff --git a/rule/logic/not.go b/rule/logic/not.go new file mode 100644 index 00000000..94ca0e3c --- /dev/null +++ b/rule/logic/not.go @@ -0,0 +1,51 @@ +package logic + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" +) + +type NOT struct { + rule C.Rule + payload string + adapter string +} + +func NewNOT(payload string, adapter string) (*NOT, error) { + not := &NOT{payload: payload, adapter: adapter} + rule, err := parseRule(payload) + if err != nil { + return nil, err + } + + if len(rule) < 1 { + return nil, fmt.Errorf("the parsed rule is empty") + } + + not.rule = rule[0] + return not, nil +} + +func (not *NOT) RuleType() C.RuleType { + return C.NOT +} + +func (not *NOT) Match(metadata *C.Metadata) bool { + return !not.rule.Match(metadata) +} + +func (not *NOT) Adapter() string { + return not.adapter +} + +func (not *NOT) Payload() string { + return not.payload +} + +func (not *NOT) ShouldResolveIP() bool { + return not.rule.ShouldResolveIP() +} + +func (not *NOT) RuleExtra() *C.RuleExtra { + return nil +} diff --git a/rule/logic/or.go b/rule/logic/or.go new file mode 100644 index 00000000..fb38a20f --- /dev/null +++ b/rule/logic/or.go @@ -0,0 +1,58 @@ +package logic + +import C "github.com/Dreamacro/clash/constant" + +type OR struct { + rules []C.Rule + payload string + adapter string + needIP bool +} + +func (or *OR) RuleType() C.RuleType { + return C.OR +} + +func (or *OR) Match(metadata *C.Metadata) bool { + for _, rule := range or.rules { + if rule.Match(metadata) { + return true + } + } + + return false +} + +func (or *OR) Adapter() string { + return or.adapter +} + +func (or *OR) Payload() string { + return or.payload +} + +func (or *OR) ShouldResolveIP() bool { + return or.needIP +} + +func (or *OR) RuleExtra() *C.RuleExtra { + return nil +} + +func NewOR(payload string, adapter string) (*OR, error) { + or := &OR{payload: payload, adapter: adapter} + rules, err := parseRule(payload) + if err != nil { + return nil, err + } + + or.rules = rules + for _, rule := range rules { + if rule.ShouldResolveIP() { + or.needIP = true + break + } + } + + return or, nil +} diff --git a/rule/parser.go b/rule/parser.go index 79c67143..700c51fd 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -1,12 +1,11 @@ -package rules +package rule import ( "fmt" - "github.com/Dreamacro/clash/adapter/provider" - "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" - P "github.com/Dreamacro/clash/constant/provider" - "time" + RC "github.com/Dreamacro/clash/rule/common" + "github.com/Dreamacro/clash/rule/logic" + RP "github.com/Dreamacro/clash/rule/provider" ) func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { @@ -16,81 +15,48 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { ) ruleExtra := &C.RuleExtra{ - Network: findNetwork(params), - SourceIPs: findSourceIPs(params), + Network: RC.FindNetwork(params), + SourceIPs: RC.FindSourceIPs(params), } switch tp { case "DOMAIN": - parsed = NewDomain(payload, target, ruleExtra) + parsed = RC.NewDomain(payload, target, ruleExtra) case "DOMAIN-SUFFIX": - parsed = NewDomainSuffix(payload, target, ruleExtra) + parsed = RC.NewDomainSuffix(payload, target, ruleExtra) case "DOMAIN-KEYWORD": - parsed = NewDomainKeyword(payload, target, ruleExtra) + parsed = RC.NewDomainKeyword(payload, target, ruleExtra) case "GEOSITE": - parsed, parseErr = NewGEOSITE(payload, target, ruleExtra) + parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra) case "GEOIP": - noResolve := HasNoResolve(params) - parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra) + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra) case "IP-CIDR", "IP-CIDR6": - noResolve := HasNoResolve(params) - parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve)) + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": - parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) case "SRC-PORT": - parsed, parseErr = NewPort(payload, target, true, ruleExtra) + parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra) case "DST-PORT": - parsed, parseErr = NewPort(payload, target, false, ruleExtra) + parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra) case "PROCESS-NAME": - parsed, parseErr = NewProcess(payload, target, ruleExtra) + parsed, parseErr = RC.NewProcess(payload, target, ruleExtra) case "MATCH": - parsed = NewMatch(target, ruleExtra) + parsed = RC.NewMatch(target, ruleExtra) case "RULE-SET": - parsed, parseErr = NewRuleSet(payload, target, ruleExtra) + parsed, parseErr = RP.NewRuleSet(payload, target, ruleExtra) + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, target) + case "AND": + parsed, parseErr = logic.NewAND(payload, target) + case "OR": + parsed, parseErr = logic.NewOR(payload, target) + case "NOT": + parsed, parseErr = logic.NewNOT(payload, target) default: parseErr = fmt.Errorf("unsupported rule type %s", tp) } return parsed, parseErr } - -type ruleProviderSchema struct { - Type string `provider:"type"` - Behavior string `provider:"behavior"` - Path string `provider:"path"` - URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` -} - -func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) { - schema := &ruleProviderSchema{} - decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) - if err := decoder.Decode(mapping, schema); err != nil { - return nil, err - } - var behavior P.RuleType - - switch schema.Behavior { - case "domain": - behavior = P.Domain - case "ipcidr": - behavior = P.IPCIDR - case "classical": - behavior = P.Classical - default: - return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior) - } - - path := C.Path.Resolve(schema.Path) - var vehicle P.Vehicle - switch schema.Type { - case "file": - vehicle = provider.NewFileVehicle(path) - case "http": - vehicle = provider.NewHTTPVehicle(schema.URL, path) - default: - return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) - } - - return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil -} diff --git a/rule/fetcher.go b/rule/provider/fetcher.go similarity index 99% rename from rule/fetcher.go rename to rule/provider/fetcher.go index 89f45ab1..c8204dae 100644 --- a/rule/fetcher.go +++ b/rule/provider/fetcher.go @@ -1,4 +1,4 @@ -package rules +package provider import ( "bytes" diff --git a/rule/provider/parse.go b/rule/provider/parse.go new file mode 100644 index 00000000..070dd8e0 --- /dev/null +++ b/rule/provider/parse.go @@ -0,0 +1,93 @@ +package provider + +import ( + "fmt" + "github.com/Dreamacro/clash/adapter/provider" + "github.com/Dreamacro/clash/common/structure" + C "github.com/Dreamacro/clash/constant" + P "github.com/Dreamacro/clash/constant/provider" + RC "github.com/Dreamacro/clash/rule/common" + "time" +) + +type ruleProviderSchema struct { + Type string `provider:"type"` + Behavior string `provider:"behavior"` + Path string `provider:"path"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` +} + +func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) { + schema := &ruleProviderSchema{} + decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) + if err := decoder.Decode(mapping, schema); err != nil { + return nil, err + } + var behavior P.RuleType + + switch schema.Behavior { + case "domain": + behavior = P.Domain + case "ipcidr": + behavior = P.IPCIDR + case "classical": + behavior = P.Classical + default: + return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior) + } + + path := C.Path.Resolve(schema.Path) + var vehicle P.Vehicle + switch schema.Type { + case "file": + vehicle = provider.NewFileVehicle(path) + case "http": + vehicle = provider.NewHTTPVehicle(schema.URL, path) + default: + return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) + } + + return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil +} + +func parseRule(tp, payload, target string, params []string) (C.Rule, error) { + var ( + parseErr error + parsed C.Rule + ) + + ruleExtra := &C.RuleExtra{ + Network: RC.FindNetwork(params), + SourceIPs: RC.FindSourceIPs(params), + } + + switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, target, ruleExtra) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, target, ruleExtra) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, target, ruleExtra) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, target, ruleExtra) + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + + return parsed, parseErr +} diff --git a/rule/provider.go b/rule/provider/provider.go similarity index 98% rename from rule/provider.go rename to rule/provider/provider.go index 561dd12a..f37e6289 100644 --- a/rule/provider.go +++ b/rule/provider/provider.go @@ -1,4 +1,4 @@ -package rules +package provider import ( "encoding/json" @@ -205,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) { return nil, errors.New("error rule type") } - r, err := ParseRule(ruleType, rule, "", params) + r, err := parseRule(ruleType, rule, "", params) if err != nil { return nil, err } diff --git a/rule/rule_set.go b/rule/provider/rule_set.go similarity index 98% rename from rule/rule_set.go rename to rule/provider/rule_set.go index 03e7b2a3..cd06c821 100644 --- a/rule/rule_set.go +++ b/rule/provider/rule_set.go @@ -1,4 +1,4 @@ -package rules +package provider import ( "fmt" diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 05354f2d..c321e9e7 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -3,6 +3,7 @@ package tunnel import ( "context" "fmt" + R "github.com/Dreamacro/clash/rule/common" "net" "runtime" "sync" @@ -15,7 +16,6 @@ import ( "github.com/Dreamacro/clash/constant/provider" icontext "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" - R "github.com/Dreamacro/clash/rule" "github.com/Dreamacro/clash/tunnel/statistic" ) From 62b70725ef62105137efd46c7d62a876dfb2194a Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sun, 23 Jan 2022 18:27:44 +0800 Subject: [PATCH 21/27] [Fixed] GEOSITE rule load fail --- config/config.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config/config.go b/config/config.go index a302a63a..0e822f31 100644 --- a/config/config.go +++ b/config/config.go @@ -543,15 +543,14 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin params = trimArr(params) + if ruleName == "GEOSITE" { + if err := initGeoSite(); err != nil { + return nil, nil, fmt.Errorf("can't initial GeoSite: %w", err) + } + } parsed, parseErr := R.ParseRule(ruleName, payload, target, params) if parseErr != nil { return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) - } else { - if parsed.RuleType() == C.GEOSITE { - if err := initGeoSite(); err != nil { - return nil, nil, fmt.Errorf("can't initial GeoSite: %w", err) - } - } } if mode != T.Script { From 2f8e575308d531c87390a0a806b85c9bb5678f58 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sun, 23 Jan 2022 18:31:49 +0800 Subject: [PATCH 22/27] [Fixed] modified RULE-SET supported rule --- rule/provider/parse.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/rule/provider/parse.go b/rule/provider/parse.go index 070dd8e0..f0c094c6 100644 --- a/rule/provider/parse.go +++ b/rule/provider/parse.go @@ -71,9 +71,6 @@ func parseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed = RC.NewDomainKeyword(payload, target, ruleExtra) case "GEOSITE": parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra) - case "GEOIP": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra) case "IP-CIDR", "IP-CIDR6": noResolve := RC.HasNoResolve(params) parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve)) From cd5b73597303d9422aff2cd8a0c3fe1ceda02ca4 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Wed, 26 Jan 2022 14:02:10 +0800 Subject: [PATCH 23/27] [Refactor] logic rule parse --- rule/logic/and.go | 2 +- rule/logic/common.go | 89 ++++++++++++++++++-------------------------- rule/logic/not.go | 2 +- rule/logic/or.go | 2 +- 4 files changed, 39 insertions(+), 56 deletions(-) diff --git a/rule/logic/and.go b/rule/logic/and.go index f75de048..01862e51 100644 --- a/rule/logic/and.go +++ b/rule/logic/and.go @@ -11,7 +11,7 @@ type AND struct { func NewAND(payload string, adapter string) (*AND, error) { and := &AND{payload: payload, adapter: adapter} - rules, err := parseRule(payload) + rules, err := parseRuleByPayload(payload) if err != nil { return nil, err } diff --git a/rule/logic/common.go b/rule/logic/common.go index 4c1aa44c..11d67915 100644 --- a/rule/logic/common.go +++ b/rule/logic/common.go @@ -10,63 +10,29 @@ import ( "strings" ) -func parseRule(payload string) ([]C.Rule, error) { +func parseRuleByPayload(payload string) ([]C.Rule, error) { regex, err := regexp.Compile("\\(.*\\)") if err != nil { return nil, err } if regex.MatchString(payload) { - subRanges, err := format(payload) + subAllRanges, err := format(payload) if err != nil { return nil, err } - rules := make([]C.Rule, 0, len(subRanges)) + rules := make([]C.Rule, 0, len(subAllRanges)) + + subRanges := findSubRuleRange(payload, subAllRanges) + for _, subRange := range subRanges { + subPayload := payload[subRange.start+1 : subRange.end] - if len(subRanges) == 1 { - subPayload := payload[subRanges[0].start+1 : subRanges[0].end-1] rule, err := payloadToRule(subPayload) if err != nil { return nil, err } rules = append(rules, rule) - } else { - preStart := subRanges[0].start - preEnd := subRanges[0].end - for _, sr := range subRanges[1:] { - if containRange(sr, preStart, preEnd) && sr.start-preStart > 1 { - str := "" - if preStart+1 <= sr.start-1 { - str = strings.TrimSpace(payload[preStart+1 : sr.start-1]) - } - - if str == "AND" || str == "OR" || str == "NOT" { - subPayload := payload[preStart+1 : preEnd] - rule, err := payloadToRule(subPayload) - if err != nil { - return nil, err - } - - rules = append(rules, rule) - preStart = sr.start - preEnd = sr.end - } - - continue - } - - preStart = sr.start - preEnd = sr.end - - subPayload := payload[sr.start+1 : sr.end] - rule, err := payloadToRule(subPayload) - if err != nil { - return nil, err - } - - rules = append(rules, rule) - } } return rules, nil @@ -84,23 +50,14 @@ func payloadToRule(subPayload string) (C.Rule, error) { tp := splitStr[0] payload := splitStr[1] if tp == "NOT" || tp == "OR" || tp == "AND" { - return parseSubRule(tp, payload, nil) + return parseRule(tp, payload, nil) } param := strings.Split(payload, ",") - return parseSubRule(tp, param[0], param[1:]) + return parseRule(tp, param[0], param[1:]) } -func splitSubRule(subRuleStr string) (string, string, []string, error) { - typeAndRule := strings.Split(subRuleStr, ",") - if len(typeAndRule) < 2 { - return "", "", nil, fmt.Errorf("format error:[%s]", typeAndRule) - } - - return strings.TrimSpace(typeAndRule[0]), strings.TrimSpace(typeAndRule[1]), typeAndRule[2:], nil -} - -func parseSubRule(tp, payload string, params []string) (C.Rule, error) { +func parseRule(tp, payload string, params []string) (C.Rule, error) { var ( parseErr error parsed C.Rule @@ -183,3 +140,29 @@ func format(payload string) ([]Range, error) { return sortResult, nil } + +func findSubRuleRange(payload string, ruleRanges []Range) []Range { + payloadLen := len(payload) + subRuleRange := make([]Range, 0) + for _, rr := range ruleRanges { + if rr.start == 0 && rr.end == payloadLen-1 { + // 最大范围跳过 + continue + } + + containInSub := false + for _, r := range subRuleRange { + if containRange(rr, r.start, r.end) { + // The subRuleRange contains a range of rr, which is the next level node of the tree + containInSub = true + break + } + } + + if !containInSub { + subRuleRange = append(subRuleRange, rr) + } + } + + return subRuleRange +} diff --git a/rule/logic/not.go b/rule/logic/not.go index 94ca0e3c..79102d4e 100644 --- a/rule/logic/not.go +++ b/rule/logic/not.go @@ -13,7 +13,7 @@ type NOT struct { func NewNOT(payload string, adapter string) (*NOT, error) { not := &NOT{payload: payload, adapter: adapter} - rule, err := parseRule(payload) + rule, err := parseRuleByPayload(payload) if err != nil { return nil, err } diff --git a/rule/logic/or.go b/rule/logic/or.go index fb38a20f..3822e315 100644 --- a/rule/logic/or.go +++ b/rule/logic/or.go @@ -41,7 +41,7 @@ func (or *OR) RuleExtra() *C.RuleExtra { func NewOR(payload string, adapter string) (*OR, error) { or := &OR{payload: payload, adapter: adapter} - rules, err := parseRule(payload) + rules, err := parseRuleByPayload(payload) if err != nil { return nil, err } From 76dccebbf67a01c081e54722eb4014e1525cd8fc Mon Sep 17 00:00:00 2001 From: Skyxim Date: Wed, 26 Jan 2022 21:29:22 +0800 Subject: [PATCH 24/27] github action build config --- .github/workflows/dev.yml | 44 +++++++++++++++++++++++++++++++++++++++ Makefile | 14 ++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/dev.yml diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 00000000..1c0b0a62 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,44 @@ +name: Dev +on: [push] +jobs: + dev-build: + if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }} + runs-on: ubuntu-latest + steps: + - name: Get latest go version + id: version + run: | + echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ steps.version.outputs.go_version }} + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Cache go module + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- +# - name: Get dependencies, run test +# run: | +# go test ./... + - name: Build + if: success() + env: + NAME: Clash.Meta + BINDIR: bin + run: make -j releases + + - name: Upload Dev + uses: softprops/action-gh-release@v1 + if: ${{ env.GIT_BRANCH != 'Meta' && success() }} + with: + tag_name: develop + files: bin/* + prerelease: true + diff --git a/Makefile b/Makefile index f547164e..7c2a5980 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ NAME=Clash.Meta BINDIR=bin -VERSION=$(shell git describe --tags || echo "unknown version") +BRANCH=$(shell git rev-parse --abbrev-ref HEAD) +VERSION=$(shell git describe --tags || echo "unknown version" ) +ifeq ($(BRANCH),Dev) +VERSION=develop-$(shell git rev-parse --short HEAD) +endif BUILDTIME=$(shell date -u) AUTOIPTABLES=Enable GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ @@ -38,7 +42,11 @@ WINDOWS_ARCH_LIST = \ windows-arm32v7 -all: linux-arm64-AutoIptables linux-amd64-AutoIptables linux-arm64 linux-amd64 darwin-amd64 darwin-arm64 windows-amd64 windows-386 # Most used +all:linux-amd64-AutoIptables linux-amd64\ + linux-arm64 linux-arm64-AutoIptables linux-armv7\ + darwin-amd64 darwin-arm64\ + windows-amd64 windows-386 \ + linux-mips-hardfloat linux-mips-softfloat linux-mips64 linux-mips64le linux-mipsle-hardfloat linux-mipsle-softfloat# Most used docker: $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ @@ -141,4 +149,4 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) releases: $(gz_releases) $(zip_releases) clean: - rm $(BINDIR)/* \ No newline at end of file + rm $(BINDIR)/* From b1a639feae485ea953f9cc5ddd9f06884fdc7091 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Wed, 26 Jan 2022 22:28:13 +0800 Subject: [PATCH 25/27] Fix: domain trie search --- component/trie/domain.go | 4 ++-- component/trie/domain_test.go | 8 ++++++++ component/trie/node.go | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/component/trie/domain.go b/component/trie/domain.go index ffd0b754..fcc9e3ba 100644 --- a/component/trie/domain.go +++ b/component/trie/domain.go @@ -109,13 +109,13 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node { } if c := node.getChild(parts[len(parts)-1]); c != nil { - if n := t.search(c, parts[:len(parts)-1]); n != nil { + if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil { return n } } if c := node.getChild(wildcard); c != nil { - if n := t.search(c, parts[:len(parts)-1]); n != nil { + if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil { return n } } diff --git a/component/trie/domain_test.go b/component/trie/domain_test.go index 61340cde..4322699a 100644 --- a/component/trie/domain_test.go +++ b/component/trie/domain_test.go @@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) { assert.NotNil(t, tree.Insert("..dev", localIP)) assert.Nil(t, tree.Search("dev")) } + +func TestTrie_WildcardBoundary(t *testing.T) { + tree := New() + tree.Insert("+.*", localIP) + tree.Insert("stun.*.*.*", localIP) + + assert.NotNil(t, tree.Search("example.com")) +} diff --git a/component/trie/node.go b/component/trie/node.go index be8ba91f..42672f0b 100644 --- a/component/trie/node.go +++ b/component/trie/node.go @@ -2,8 +2,8 @@ package trie // Node is the trie's node type Node struct { - Data interface{} children map[string]*Node + Data interface{} } func (n *Node) getChild(s string) *Node { From ae5a790510b4e1da90b73edd1fb943d563c7bede Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 4 Feb 2022 16:45:23 +0800 Subject: [PATCH 26/27] [Fixed] Abnormal rule when host is ip addr --- adapter/inbound/socket.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/adapter/inbound/socket.go b/adapter/inbound/socket.go index f761bd9b..cca260c6 100644 --- a/adapter/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -26,10 +26,20 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext { metadata.NetWork = C.TCP metadata.Type = C.INNER metadata.DNSMode = C.DNSMapping - metadata.AddrType = C.AtypDomainName metadata.Host = host - if _, port, err := parseAddr(dst); err == nil { + metadata.AddrType = C.AtypDomainName + + if ip, port, err := parseAddr(dst); err == nil { metadata.DstPort = port + if host == "" { + metadata.DstIP = ip + if ip.To4() == nil { + metadata.AddrType = C.AtypIPv6 + } else { + metadata.AddrType = C.AtypIPv4 + } + } } + return context.NewConnContext(conn, metadata) } From 90e6ed46122cb56039d54cac1124ef3a2675bcd7 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 4 Feb 2022 17:36:34 +0800 Subject: [PATCH 27/27] [Fixed] Fixed clash process name is `Clash.Meta` --- adapter/inbound/socket.go | 2 +- constant/version.go | 1 + rule/common/process.go | 34 ++++++++++++++++++---------------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/adapter/inbound/socket.go b/adapter/inbound/socket.go index cca260c6..ebb2095e 100644 --- a/adapter/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -28,7 +28,7 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext { metadata.DNSMode = C.DNSMapping metadata.Host = host metadata.AddrType = C.AtypDomainName - + metadata.Process = C.ClashName if ip, port, err := parseAddr(dst); err == nil { metadata.DstPort = port if host == "" { diff --git a/constant/version.go b/constant/version.go index 5a511382..1d4e6e7b 100644 --- a/constant/version.go +++ b/constant/version.go @@ -5,4 +5,5 @@ var ( Version = "1.9.0" BuildTime = "unknown time" AutoIptables string + ClashName = "Clash.Meta" ) diff --git a/rule/common/process.go b/rule/common/process.go index 47731443..669714ec 100644 --- a/rule/common/process.go +++ b/rule/common/process.go @@ -35,26 +35,28 @@ func (ps *Process) Match(metadata *C.Metadata) bool { } key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) - cached, hit := processCache.Get(key) - if !hit { - srcPort, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - processCache.Set(key, "") - return false + if strings.TrimSpace(metadata.Process) == "" { + cached, hit := processCache.Get(key) + if !hit { + srcPort, err := strconv.Atoi(metadata.SrcPort) + if err != nil { + processCache.Set(key, "") + return false + } + + name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) + if err != nil { + log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error()) + } + + processCache.Set(key, name) + + cached = name } - name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) - if err != nil { - log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error()) - } - - processCache.Set(key, name) - - cached = name + metadata.Process = cached.(string) } - metadata.Process = cached.(string) - return strings.EqualFold(metadata.Process, ps.process) }