diff --git a/Dockerfile b/Dockerfile index 44dc3b52..c7a864e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,11 +16,11 @@ RUN go mod download &&\ FROM alpine:latest LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta" -RUN apk add --no-cache ca-certificates tzdata +RUN apk add --no-cache ca-certificates tzdata iptables VOLUME ["/root/.config/clash/"] COPY --from=builder /clash-config/ /root/.config/clash/ COPY --from=builder /clash /clash RUN chmod +x /clash -ENTRYPOINT [ "/clash" ] \ No newline at end of file +ENTRYPOINT [ "/clash" ] diff --git a/Makefile b/Makefile index 0df75db0..3c079aa1 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ VERSION=$(shell git rev-parse --short HEAD) endif BUILDTIME=$(shell date -u) -GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ +GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -w -s -buildid=' diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index 1112732e..145a3f97 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -90,7 +90,7 @@ func (b *Base) Addr() string { } // Unwrap implements C.ProxyAdapter -func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy { +func (b *Base) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { return nil } diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index 9446cf93..490b14e1 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -4,27 +4,29 @@ import ( "context" "crypto/sha256" "crypto/tls" + "encoding/base64" "encoding/hex" "encoding/pem" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" - "github.com/Dreamacro/clash/transport/hysteria/core" - "github.com/Dreamacro/clash/transport/hysteria/obfs" - "github.com/Dreamacro/clash/transport/hysteria/pmtud_fix" - "github.com/Dreamacro/clash/transport/hysteria/transport" - "github.com/lucas-clemente/quic-go" "net" "os" "regexp" "strconv" "time" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/congestion" + M "github.com/sagernet/sing/common/metadata" + "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion" - "github.com/lucas-clemente/quic-go/congestion" - M "github.com/sagernet/sing/common/metadata" + "github.com/Dreamacro/clash/transport/hysteria/core" + "github.com/Dreamacro/clash/transport/hysteria/obfs" + "github.com/Dreamacro/clash/transport/hysteria/pmtud_fix" + "github.com/Dreamacro/clash/transport/hysteria/transport" ) const ( @@ -85,23 +87,27 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata type HysteriaOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Protocol string `proxy:"protocol,omitempty"` - Up string `proxy:"up"` - Down string `proxy:"down"` - AuthString string `proxy:"auth_str,omitempty"` - Obfs string `proxy:"obfs,omitempty"` - SNI string `proxy:"sni,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - Fingerprint string `proxy:"fingerprint,omitempty"` - ALPN string `proxy:"alpn,omitempty"` - CustomCA string `proxy:"ca,omitempty"` - CustomCAString string `proxy:"ca_str,omitempty"` - ReceiveWindowConn int `proxy:"recv_window_conn,omitempty"` - ReceiveWindow int `proxy:"recv_window,omitempty"` - DisableMTUDiscovery bool `proxy:"disable_mtu_discovery,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Protocol string `proxy:"protocol,omitempty"` + ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash + Up string `proxy:"up"` + UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash + Down string `proxy:"down"` + DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash + Auth string `proxy:"auth,omitempty"` + AuthString string `proxy:"auth_str,omitempty"` + Obfs string `proxy:"obfs,omitempty"` + SNI string `proxy:"sni,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + ALPN []string `proxy:"alpn,omitempty"` + CustomCA string `proxy:"ca,omitempty"` + CustomCAString string `proxy:"ca_str,omitempty"` + ReceiveWindowConn int `proxy:"recv_window_conn,omitempty"` + ReceiveWindow int `proxy:"recv_window,omitempty"` + DisableMTUDiscovery bool `proxy:"disable_mtu_discovery,omitempty"` } func (c *HysteriaOption) Speed() (uint64, uint64, error) { @@ -172,7 +178,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { } if len(option.ALPN) > 0 { - tlsConfig.NextProtos = []string{option.ALPN} + tlsConfig.NextProtos = option.ALPN } else { tlsConfig.NextProtos = []string{DefaultALPN} } @@ -186,6 +192,9 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { DisablePathMTUDiscovery: option.DisableMTUDiscovery, EnableDatagrams: true, } + if option.ObfsProtocol != "" { + option.Protocol = option.ObfsProtocol + } if option.Protocol == "" { option.Protocol = DefaultProtocol } @@ -202,6 +211,12 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { } var auth = []byte(option.AuthString) + if option.Auth != "" { + auth, err = base64.StdEncoding.DecodeString(option.Auth) + if err != nil { + return nil, err + } + } var obfuscator obfs.Obfuscator if len(option.Obfs) > 0 { obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs)) @@ -211,7 +226,12 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { if err != nil { return nil, err } - + if option.UpSpeed != 0 { + up = uint64(option.UpSpeed * mbpsToBps) + } + if option.DownSpeed != 0 { + down = uint64(option.DownSpeed * mbpsToBps) + } client, err := core.NewClient( addr, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index b26f8802..6eeacf45 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -7,7 +7,6 @@ import ( "net" "strconv" - "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" @@ -16,16 +15,11 @@ import ( v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" "github.com/sagernet/sing-shadowsocks" "github.com/sagernet/sing-shadowsocks/shadowimpl" - "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/uot" ) -func init() { - buf.DefaultAllocator = pool.DefaultAllocator -} - type ShadowSocks struct { *Base method shadowsocks.Method @@ -82,8 +76,7 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e } } if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { - metadata.Host = uot.UOTMagicAddress - metadata.DstPort = "443" + return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")) } return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) } diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index fc8e1e87..a66ee40f 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -52,6 +52,9 @@ type VmessOption struct { GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"` PacketAddr bool `proxy:"packet-addr,omitempty"` + XUDP bool `proxy:"xudp,omitempty"` + PacketEncoding string `proxy:"packet-encoding,omitempty"` + GlobalPadding bool `proxy:"global-padding,omitempty"` AuthenticatedLength bool `proxy:"authenticated-length,omitempty"` } @@ -197,7 +200,11 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return nil, err } if metadata.NetWork == C.UDP { - return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + if v.option.XUDP { + return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + } else { + return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + } } else { return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) } @@ -244,6 +251,8 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } if v.option.PacketAddr { + _metadata := *metadata // make a copy + metadata = &_metadata metadata.Host = packetaddr.SeqPacketMagicAddress metadata.DstPort = "443" } @@ -257,7 +266,11 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } defer safeConnClose(c, err) - c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + if v.option.XUDP { + c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + } else { + c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + } } else { c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { @@ -274,7 +287,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } if v.option.PacketAddr { - return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindClient(c)}, v), nil + return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil } else if pc, ok := c.(net.PacketConn); ok { return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil } @@ -284,7 +297,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o // ListenPacketOnStreamConn implements C.ProxyAdapter func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { if v.option.PacketAddr { - return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindClient(c)}, v), nil + return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil } else if pc, ok := c.(net.PacketConn); ok { return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil } @@ -299,6 +312,9 @@ func (v *Vmess) SupportUOT() bool { func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) var options []vmess.ClientOption + if option.GlobalPadding { + options = append(options, vmess.ClientWithGlobalPadding()) + } if option.AuthenticatedLength { options = append(options, vmess.ClientWithAuthenticatedLength()) } @@ -307,6 +323,16 @@ func NewVmess(option VmessOption) (*Vmess, error) { return nil, err } + switch option.PacketEncoding { + case "packetaddr", "packet": + option.PacketAddr = true + case "xudp": + option.XUDP = true + } + if option.XUDP { + option.PacketAddr = false + } + switch option.Network { case "h2", "grpc": if !option.TLS { diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 3e4c8927..a433d54d 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -31,7 +31,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts . c.AppendToChains(f) f.onDialSuccess() } else { - f.onDialFailed() + f.onDialFailed(proxy.Type(), err) } return c, err @@ -72,8 +72,8 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { } // Unwrap implements C.ProxyAdapter -func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { - proxy := f.findAliveProxy(true) +func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { + proxy := f.findAliveProxy(touch) return proxy } diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 7754d220..fbe887e3 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -11,13 +11,14 @@ import ( "github.com/Dreamacro/clash/tunnel" "github.com/dlclark/regexp2" "go.uber.org/atomic" + "strings" "sync" "time" ) type GroupBase struct { *outbound.Base - filter *regexp2.Regexp + filterRegs []*regexp2.Regexp providers []provider.ProxyProvider failedTestMux sync.Mutex failedTimes int @@ -34,14 +35,17 @@ type GroupBaseOption struct { } func NewGroupBase(opt GroupBaseOption) *GroupBase { - var filter *regexp2.Regexp = nil + var filterRegs []*regexp2.Regexp if opt.filter != "" { - filter = regexp2.MustCompile(opt.filter, 0) + for _, filter := range strings.Split(opt.filter, "`") { + filterReg := regexp2.MustCompile(filter, 0) + filterRegs = append(filterRegs, filterReg) + } } gb := &GroupBase{ Base: outbound.NewBase(opt.BaseOption), - filter: filter, + filterRegs: filterRegs, providers: opt.providers, failedTesting: atomic.NewBool(false), } @@ -52,8 +56,14 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { return gb } +func (gb *GroupBase) Touch() { + for _, pd := range gb.providers { + pd.Touch() + } +} + func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { - if gb.filter == nil { + if len(gb.filterRegs) == 0 { var proxies []C.Proxy for _, pd := range gb.providers { if touch { @@ -79,16 +89,23 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { } version := gb.versions[i].Load() - if version != pd.Version() && gb.versions[i].CAS(version, pd.Version()) { + if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) { var ( proxies []C.Proxy newProxies []C.Proxy ) proxies = pd.Proxies() - for _, p := range proxies { - if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil { - newProxies = append(newProxies, p) + proxiesSet := map[string]struct{}{} + for _, filterReg := range gb.filterRegs { + for _, p := range proxies { + name := p.Name() + if mat, _ := filterReg.FindStringMatch(name); mat != nil { + if _, ok := proxiesSet[name]; !ok { + proxiesSet[name] = struct{}{} + newProxies = append(newProxies, p) + } + } } } @@ -105,6 +122,30 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { return append(proxies, tunnel.Proxies()["COMPATIBLE"]) } + if len(gb.providers) > 1 && len(gb.filterRegs) > 1 { + var newProxies []C.Proxy + proxiesSet := map[string]struct{}{} + for _, filterReg := range gb.filterRegs { + for _, p := range proxies { + name := p.Name() + if mat, _ := filterReg.FindStringMatch(name); mat != nil { + if _, ok := proxiesSet[name]; !ok { + proxiesSet[name] = struct{}{} + newProxies = append(newProxies, p) + } + } + } + } + for _, p := range proxies { // add not matched proxies at the end + name := p.Name() + if _, ok := proxiesSet[name]; !ok { + proxiesSet[name] = struct{}{} + newProxies = append(newProxies, p) + } + } + proxies = newProxies + } + return proxies } @@ -136,8 +177,13 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16 } } -func (gb *GroupBase) onDialFailed() { - if gb.failedTesting.Load() { +func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) { + if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass { + return + } + + if strings.Contains(err.Error(), "connection refused") { + go gb.healthCheck() return } @@ -157,26 +203,34 @@ func (gb *GroupBase) onDialFailed() { log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes) if gb.failedTimes >= gb.maxFailedTimes() { - gb.failedTesting.Store(true) log.Warnln("because %s failed multiple times, active health check", gb.Name()) - wg := sync.WaitGroup{} - for _, proxyProvider := range gb.providers { - wg.Add(1) - proxyProvider := proxyProvider - go func() { - defer wg.Done() - proxyProvider.HealthCheck() - }() - } - - wg.Wait() - gb.failedTesting.Store(false) - gb.failedTimes = 0 + gb.healthCheck() } } }() } +func (gb *GroupBase) healthCheck() { + if gb.failedTesting.Load() { + return + } + + gb.failedTesting.Store(true) + wg := sync.WaitGroup{} + for _, proxyProvider := range gb.providers { + wg.Add(1) + proxyProvider := proxyProvider + go func() { + defer wg.Done() + proxyProvider.HealthCheck() + }() + } + + wg.Wait() + gb.failedTesting.Store(false) + gb.failedTimes = 0 +} + func (gb *GroupBase) failedIntervalTime() int64 { return 5 * time.Second.Milliseconds() } diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 430aed82..87d7de7b 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -82,17 +82,17 @@ func jumpHash(key uint64, buckets int32) int32 { // DialContext implements C.ProxyAdapter func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { + proxy := lb.Unwrap(metadata, true) + defer func() { if err == nil { c.AppendToChains(lb) lb.onDialSuccess() } else { - lb.onDialFailed() + lb.onDialFailed(proxy.Type(), err) } }() - proxy := lb.Unwrap(metadata) - c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...) return } @@ -105,7 +105,7 @@ func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Meta } }() - proxy := lb.Unwrap(metadata) + proxy := lb.Unwrap(metadata, true) return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...) } @@ -190,8 +190,8 @@ func strategyStickySessions() strategyFn { } // Unwrap implements C.ProxyAdapter -func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { - proxies := lb.GetProxies(true) +func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { + proxies := lb.GetProxies(touch) return lb.strategyFn(proxies, metadata) } diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 79403eaf..729f4137 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -153,11 +153,11 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) for n, proxy := range rawProxies { proxies = append(proxies, proxy) chainProxies = append(chainProxies, proxy) - subproxy := proxy.Unwrap(metadata) + subproxy := proxy.Unwrap(metadata, touch) for subproxy != nil { chainProxies = append(chainProxies, subproxy) proxies[n] = subproxy - subproxy = subproxy.Unwrap(metadata) + subproxy = subproxy.Unwrap(metadata, touch) } } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index dd36eeb7..dcf07707 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -74,8 +74,8 @@ func (s *Selector) Set(name string) error { } // Unwrap implements C.ProxyAdapter -func (s *Selector) Unwrap(*C.Metadata) C.Proxy { - return s.selectedProxy(true) +func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { + return s.selectedProxy(touch) } func (s *Selector) selectedProxy(touch bool) C.Proxy { diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 9386e1ef..1e69652c 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -34,12 +34,13 @@ func (u *URLTest) Now() string { // DialContext implements C.ProxyAdapter func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { - c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) + proxy := u.fast(true) + c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(u) u.onDialSuccess() } else { - u.onDialFailed() + u.onDialFailed(proxy.Type(), err) } return c, err } @@ -55,12 +56,12 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, } // Unwrap implements C.ProxyAdapter -func (u *URLTest) Unwrap(*C.Metadata) C.Proxy { - return u.fast(true) +func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { + return u.fast(touch) } func (u *URLTest) fast(touch bool) C.Proxy { - elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) { + elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { proxies := u.GetProxies(touch) fast := proxies[0] min := fast.LastDelay() @@ -89,6 +90,9 @@ func (u *URLTest) fast(touch bool) C.Proxy { return u.fastNode, nil }) + if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again + u.Touch() + } return elm } diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index 8e736f6c..16b9ad61 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -2,12 +2,14 @@ package provider import ( "context" - "github.com/Dreamacro/clash/common/singledo" "time" "github.com/Dreamacro/clash/common/batch" + "github.com/Dreamacro/clash/common/singledo" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/gofrs/uuid" "go.uber.org/atomic" ) @@ -35,16 +37,13 @@ func (hc *HealthCheck) process() { go func() { time.Sleep(30 * time.Second) - hc.check() + hc.lazyCheck() }() for { select { case <-ticker.C: - now := time.Now().Unix() - if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { - hc.check() - } + hc.lazyCheck() case <-hc.done: ticker.Stop() return @@ -52,6 +51,17 @@ func (hc *HealthCheck) process() { } } +func (hc *HealthCheck) lazyCheck() bool { + now := time.Now().Unix() + if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { + hc.check() + return true + } else { + log.Debugln("Skip once health check because we are lazy") + return false + } +} + func (hc *HealthCheck) setProxy(proxies []C.Proxy) { hc.proxies = proxies } @@ -66,18 +76,26 @@ func (hc *HealthCheck) touch() { func (hc *HealthCheck) check() { _, _, _ = hc.singleDo.Do(func() (struct{}, error) { + id := "" + if uid, err := uuid.NewV4(); err == nil { + id = uid.String() + } + log.Debugln("Start New Health Checking {%s}", id) b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) for _, proxy := range hc.proxies { p := proxy b.Go(p.Name(), func() (bool, error) { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) defer cancel() + log.Debugln("Health Checking %s {%s}", p.Name(), id) _, _ = p.URLTest(ctx, hc.url) + log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id) return false, nil }) } b.Wait() + log.Debugln("Finish A Health Checking {%s}", id) return struct{}{}, nil }) } diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index e3d91cfe..e55ab914 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -21,12 +21,13 @@ type healthCheckSchema struct { } type proxyProviderSchema struct { - Type string `provider:"type"` - Path string `provider:"path"` - URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` - Filter string `provider:"filter,omitempty"` - HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Type string `provider:"type"` + Path string `provider:"path"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` + Filter string `provider:"filter,omitempty"` + ExcludeFilter string `provider:"exclude-filter,omitempty"` + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` } func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { @@ -61,5 +62,6 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter - return NewProxySetProvider(name, interval, filter, vehicle, hc) + excludeFilter := schema.ExcludeFilter + return NewProxySetProvider(name, interval, filter, excludeFilter, vehicle, hc) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 00bcde1d..ebacf291 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -8,6 +8,7 @@ import ( "github.com/Dreamacro/clash/component/resource" "github.com/dlclark/regexp2" "runtime" + "strings" "time" "github.com/Dreamacro/clash/adapter" @@ -92,7 +93,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { pp.proxies = proxies pp.healthCheck.setProxy(proxies) if pp.healthCheck.auto() { - defer func() { go pp.healthCheck.check() }() + defer func() { go pp.healthCheck.lazyCheck() }() } } @@ -101,10 +102,18 @@ func stopProxyProvider(pd *ProxySetProvider) { _ = pd.Fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { - filterReg, err := regexp2.Compile(filter, 0) +func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { + excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) if err != nil { - return nil, fmt.Errorf("invalid filter regex: %w", err) + return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) + } + var filterRegs []*regexp2.Regexp + for _, filter := range strings.Split(filter, "`") { + filterReg, err := regexp2.Compile(filter, 0) + if err != nil { + return nil, fmt.Errorf("invalid filter regex: %w", err) + } + filterRegs = append(filterRegs, filterReg) } if hc.auto() { @@ -116,7 +125,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh healthCheck: hc, } - fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg), proxiesOnUpdate(pd)) + fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) pd.Fetcher = fetcher wrapper := &ProxySetProvider{pd} @@ -212,7 +221,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { } } -func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { +func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { return func(buf []byte) ([]C.Proxy, error) { schema := &ProxySchema{} @@ -229,17 +238,37 @@ func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) resource.Pa } proxies := []C.Proxy{} - for idx, mapping := range schema.Proxies { - name, ok := mapping["name"] - mat, _ := filterReg.FindStringMatch(name.(string)) - if ok && len(filter) > 0 && mat == nil { - continue + proxiesSet := map[string]struct{}{} + for _, filterReg := range filterRegs { + for idx, mapping := range schema.Proxies { + mName, ok := mapping["name"] + if !ok { + continue + } + name, ok := mName.(string) + if !ok { + continue + } + if len(excludeFilter) > 0 { + if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil { + continue + } + } + if len(filter) > 0 { + if mat, _ := filterReg.FindStringMatch(name); mat == nil { + continue + } + } + if _, ok := proxiesSet[name]; ok { + continue + } + proxy, err := adapter.ParseProxy(mapping) + if err != nil { + return nil, fmt.Errorf("proxy %d error: %w", idx, err) + } + proxiesSet[name] = struct{}{} + proxies = append(proxies, proxy) } - proxy, err := adapter.ParseProxy(mapping) - if err != nil { - return nil, fmt.Errorf("proxy %d error: %w", idx, err) - } - proxies = append(proxies, proxy) } if len(proxies) == 0 { diff --git a/common/convert/converter.go b/common/convert/converter.go index 9876e51f..238e241b 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -47,7 +47,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { hysteria["port"] = urlHysteria.Port() hysteria["sni"] = query.Get("peer") hysteria["obfs"] = query.Get("obfs") - hysteria["alpn"] = query.Get("alpn") + hysteria["alpn"] = []string{query.Get("alpn")} hysteria["auth_str"] = query.Get("auth") hysteria["protocol"] = query.Get("protocol") up := query.Get("up") @@ -144,6 +144,14 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if encryption := query.Get("encryption"); encryption != "" { vmess["cipher"] = encryption } + if packetEncoding := query.Get("packetEncoding"); packetEncoding != "" { + switch packetEncoding { + case "packet": + vmess["packet-addr"] = true + case "xudp": + vmess["xudp"] = true + } + } proxies = append(proxies, vmess) continue } diff --git a/common/net/relay.go b/common/net/relay.go index e7157639..d4edede2 100644 --- a/common/net/relay.go +++ b/common/net/relay.go @@ -4,8 +4,6 @@ import ( "io" "net" "time" - - "github.com/Dreamacro/clash/common/pool" ) // Relay copies between left and right bidirectionally. @@ -13,18 +11,14 @@ func Relay(leftConn, rightConn net.Conn) { ch := make(chan error) go func() { - buf := pool.Get(pool.RelayBufferSize) // Wrapping to avoid using *net.TCPConn.(ReadFrom) // See also https://github.com/Dreamacro/clash/pull/1209 - _, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf) - pool.Put(buf) + _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) leftConn.SetReadDeadline(time.Now()) ch <- err }() - buf := pool.Get(pool.RelayBufferSize) - io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf) - pool.Put(buf) + _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}) rightConn.SetReadDeadline(time.Now()) <-ch } diff --git a/common/pool/alloc.go b/common/pool/alloc.go index 96fd059e..25f79897 100644 --- a/common/pool/alloc.go +++ b/common/pool/alloc.go @@ -8,7 +8,7 @@ import ( "sync" ) -var DefaultAllocator = NewAllocator() +var defaultAllocator = NewAllocator() // Allocator for incoming frames, optimized to prevent overwriting after zeroing type Allocator struct { diff --git a/common/pool/pool.go b/common/pool/pool.go index a2beb082..bee4887f 100644 --- a/common/pool/pool.go +++ b/common/pool/pool.go @@ -13,9 +13,9 @@ const ( ) func Get(size int) []byte { - return DefaultAllocator.Get(size) + return defaultAllocator.Get(size) } func Put(buf []byte) error { - return DefaultAllocator.Put(buf) + return defaultAllocator.Put(buf) } diff --git a/common/pool/sing.go b/common/pool/sing.go new file mode 100644 index 00000000..c246ae9f --- /dev/null +++ b/common/pool/sing.go @@ -0,0 +1,7 @@ +package pool + +import "github.com/sagernet/sing/common/buf" + +func init() { + buf.DefaultAllocator = defaultAllocator +} diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index fa1b17a2..ea4d2ece 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -290,6 +290,7 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr connCount := len(ips) var fallback dialResult + var primaryError error for i := 0; i < connCount; i++ { select { case res := <-results: @@ -303,6 +304,7 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr } } else { if res.isPrimary { + primaryError = res.error preferCount.Add(-1) if preferCount.Load() == 0 && fallback.done && fallback.error == nil { return fallback.Conn, nil @@ -321,6 +323,14 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr return fallback.Conn, nil } + if primaryError != nil { + return nil, primaryError + } + + if fallback.error != nil { + return nil, fallback.error + } + return nil, fmt.Errorf("all ips %v tcp shake hands failed", ips) } diff --git a/component/ebpf/ebpf_linux.go b/component/ebpf/ebpf_linux.go index 712770c6..bf41d6cb 100644 --- a/component/ebpf/ebpf_linux.go +++ b/component/ebpf/ebpf_linux.go @@ -15,6 +15,30 @@ import ( C "github.com/Dreamacro/clash/constant" ) +func GetAutoDetectInterface() (string, error) { + routes, err := netlink.RouteList(nil, netlink.FAMILY_V4) + if err != nil { + return "", err + } + + for _, route := range routes { + if route.Dst == nil { + lk, err := netlink.LinkByIndex(route.LinkIndex) + if err != nil { + return "", err + } + + if lk.Type() == "tuntap" { + continue + } + + return lk.Attrs().Name, nil + } + } + + return "", fmt.Errorf("interface not found") +} + // NewTcEBpfProgram new redirect to tun ebpf program func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) { tunIface, err := netlink.LinkByName(tunName) diff --git a/component/ebpf/ebpf_others.go b/component/ebpf/ebpf_others.go index 0ecf5caa..44cf1c3a 100644 --- a/component/ebpf/ebpf_others.go +++ b/component/ebpf/ebpf_others.go @@ -15,3 +15,7 @@ func NewTcEBpfProgram(_ []string, _ string) (*TcEBpfProgram, error) { func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) { return nil, fmt.Errorf("system not supported") } + +func GetAutoDetectInterface() (string, error) { + return "", fmt.Errorf("system not supported") +} diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 0137f705..58a75a94 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -166,7 +166,7 @@ func New(options Options) (*Pool, error) { var ( hostAddr = options.IPNet.Masked().Addr() gateway = hostAddr.Next() - first = gateway.Next().Next() + first = gateway.Next().Next().Next() // default start with 198.18.0.4 last = nnip.UnMasked(*options.IPNet) ) diff --git a/component/fakeip/pool_test.go b/component/fakeip/pool_test.go index 1f6b3f09..0e9dd630 100644 --- a/component/fakeip/pool_test.go +++ b/component/fakeip/pool_test.go @@ -62,16 +62,16 @@ func TestPool_Basic(t *testing.T) { last := pool.Lookup("bar.com") bar, exist := pool.LookBack(last) - assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3})) - assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 3})) - assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4})) + assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4})) + assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 4})) + assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.True(t, exist) assert.Equal(t, bar, "bar.com") assert.True(t, pool.Gateway() == netip.AddrFrom4([4]byte{192, 168, 0, 1})) assert.True(t, pool.Broadcast() == netip.AddrFrom4([4]byte{192, 168, 0, 15})) assert.Equal(t, pool.IPNet().String(), ipnet.String()) - assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 4}))) - assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5}))) + assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5}))) + assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6}))) assert.False(t, pool.Exist(netip.MustParseAddr("::1"))) } } @@ -90,16 +90,16 @@ func TestPool_BasicV6(t *testing.T) { last := pool.Lookup("bar.com") bar, exist := pool.LookBack(last) - assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803")) - assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803")) - assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) + assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) + assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) + assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")) assert.True(t, exist) assert.Equal(t, bar, "bar.com") assert.True(t, pool.Gateway() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801")) assert.True(t, pool.Broadcast() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff")) assert.Equal(t, pool.IPNet().String(), ipnet.String()) - assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))) - assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))) + assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))) + assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806"))) assert.False(t, pool.Exist(netip.MustParseAddr("127.0.0.1"))) } } @@ -116,7 +116,7 @@ func TestPool_CycleUsed(t *testing.T) { for _, pool := range pools { foo := pool.Lookup("foo.com") bar := pool.Lookup("bar.com") - for i := 0; i < 10; i++ { + for i := 0; i < 9; i++ { pool.Lookup(fmt.Sprintf("%d.com", i)) } baz := pool.Lookup("baz.com") @@ -198,8 +198,8 @@ func TestPool_Clone(t *testing.T) { first := pool.Lookup("foo.com") last := pool.Lookup("bar.com") - assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3})) - assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4})) + assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4})) + assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) newPool, _ := New(Options{ IPNet: &ipnet, diff --git a/component/process/process_darwin.go b/component/process/process_darwin.go index 5615b985..c27aed04 100644 --- a/component/process/process_darwin.go +++ b/component/process/process_darwin.go @@ -3,6 +3,8 @@ package process import ( "encoding/binary" "net/netip" + "strconv" + "strings" "syscall" "unsafe" @@ -15,6 +17,22 @@ const ( proccallnumpidinfo = 0x2 ) +var structSize = func() int { + value, _ := syscall.Sysctl("kern.osrelease") + major, _, _ := strings.Cut(value, ".") + n, _ := strconv.ParseInt(major, 10, 64) + switch true { + case n >= 22: + return 408 + default: + // from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n + // size/offset are round up (aligned) to 8 bytes in darwin + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + + // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) + return 384 + } +}() + func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { return 0, 0, ErrPlatformNotSupport } @@ -38,12 +56,7 @@ func findProcessName(network string, ip netip.Addr, port int) (int32, string, er } buf := []byte(value) - - // from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n - // size/offset are round up (aligned) to 8 bytes in darwin - // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + - // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) - itemSize := 384 + itemSize := structSize if network == TCP { // rup8(sizeof(xtcpcb_n)) itemSize += 208 diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go index cca8f719..6a5e632a 100644 --- a/component/sniffer/dispatcher.go +++ b/component/sniffer/dispatcher.go @@ -2,17 +2,19 @@ package sniffer import ( "errors" - "github.com/Dreamacro/clash/constant/sniffer" + "fmt" "net" "net/netip" "strconv" + "sync" "time" - "github.com/Dreamacro/clash/component/trie" - - CN "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/common/cache" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/utils" + "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/sniffer" "github.com/Dreamacro/clash/log" ) @@ -22,27 +24,30 @@ var ( ErrNoClue = errors.New("not enough information for making a decision") ) -var Dispatcher SnifferDispatcher +var Dispatcher *SnifferDispatcher -type ( - SnifferDispatcher struct { - enable bool +type SnifferDispatcher struct { + enable bool - sniffers []sniffer.Sniffer + sniffers []sniffer.Sniffer - foreDomain *trie.DomainTrie[bool] - skipSNI *trie.DomainTrie[bool] - portRanges *[]utils.Range[uint16] - } -) + forceDomain *trie.DomainTrie[bool] + skipSNI *trie.DomainTrie[bool] + portRanges *[]utils.Range[uint16] + skipList *cache.LruCache[string, uint8] + rwMux sync.RWMutex + + forceDnsMapping bool + parsePureIp bool +} func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { - bufConn, ok := conn.(*CN.BufferedConn) + bufConn, ok := conn.(*N.BufferedConn) if !ok { return } - if metadata.Host == "" || sd.foreDomain.Search(metadata.Host) != nil { + if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) { port, err := strconv.ParseUint(metadata.DstPort, 10, 16) if err != nil { log.Debugln("[Sniffer] Dst port is error") @@ -61,7 +66,17 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { return } + sd.rwMux.RLock() + dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort) + if count, ok := sd.skipList.Get(dst); ok && count > 5 { + log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst) + defer sd.rwMux.RUnlock() + return + } + sd.rwMux.RUnlock() + if host, err := sd.sniffDomain(bufConn, metadata); err != nil { + sd.cacheSniffFailed(metadata) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) return } else { @@ -70,16 +85,32 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { return } + sd.rwMux.RLock() + sd.skipList.Delete(dst) + sd.rwMux.RUnlock() + sd.replaceDomain(metadata, host) } } } func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) { - log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]", - metadata.SrcIP, metadata.SrcPort, - metadata.DstIP, metadata.DstPort, - metadata.Host, host) + dstIP := "" + if metadata.DstIP.IsValid() { + dstIP = metadata.DstIP.String() + } + originHost := metadata.Host + if originHost != host { + log.Infoln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]", + metadata.SrcIP, metadata.SrcPort, + dstIP, metadata.DstPort, + metadata.Host, host) + } else { + log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]", + metadata.SrcIP, metadata.SrcPort, + dstIP, metadata.DstPort, + metadata.Host, host) + } metadata.AddrType = C.AtypDomainName metadata.Host = host @@ -90,15 +121,16 @@ func (sd *SnifferDispatcher) Enable() bool { return sd.enable } -func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) { - for _, sniffer := range sd.sniffers { - if sniffer.SupportNetwork() == C.TCP { - _ = conn.SetReadDeadline(time.Now().Add(3 * time.Second)) +func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) { + for _, s := range sd.sniffers { + if s.SupportNetwork() == C.TCP { + _ = conn.SetReadDeadline(time.Now().Add(1 * time.Second)) _, err := conn.Peek(1) _ = conn.SetReadDeadline(time.Time{}) if err != nil { _, ok := err.(*net.OpError) if ok { + sd.cacheSniffFailed(metadata) log.Errorln("[Sniffer] [%s] may not have any sent data, Consider adding skip", metadata.DstIP.String()) _ = conn.Close() } @@ -113,15 +145,15 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta continue } - host, err := sniffer.SniffTCP(bytes) + host, err := s.SniffTCP(bytes) if err != nil { - //log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP) + //log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP) continue } _, err = netip.ParseAddr(host) if err == nil { - //log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP) + //log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP) continue } @@ -132,6 +164,17 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta return "", ErrorSniffFailed } +func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) { + sd.rwMux.Lock() + dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort) + count, _ := sd.skipList.Get(dst) + if count <= 5 { + count++ + } + sd.skipList.Set(dst, count) + sd.rwMux.Unlock() +} + func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) { dispatcher := SnifferDispatcher{ enable: false, @@ -141,22 +184,26 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) { } func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[bool], - skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) { + skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16], + forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) { dispatcher := SnifferDispatcher{ - enable: true, - foreDomain: forceDomain, - skipSNI: skipSNI, - portRanges: ports, + enable: true, + forceDomain: forceDomain, + skipSNI: skipSNI, + portRanges: ports, + skipList: cache.NewLRUCache[string, uint8](cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)), + forceDnsMapping: forceDnsMapping, + parsePureIp: parsePureIp, } for _, snifferName := range needSniffer { - sniffer, err := NewSniffer(snifferName) + s, err := NewSniffer(snifferName) if err != nil { log.Errorln("Sniffer name[%s] is error", snifferName) return &SnifferDispatcher{enable: false}, err } - dispatcher.sniffers = append(dispatcher.sniffers, sniffer) + dispatcher.sniffers = append(dispatcher.sniffers, s) } return &dispatcher, nil diff --git a/component/sniffer/http_sniffer.go b/component/sniffer/http_sniffer.go index f75285ed..551b20c8 100644 --- a/component/sniffer/http_sniffer.go +++ b/component/sniffer/http_sniffer.go @@ -4,9 +4,10 @@ import ( "bytes" "errors" "fmt" - C "github.com/Dreamacro/clash/constant" "net" "strings" + + C "github.com/Dreamacro/clash/constant" ) var ( diff --git a/component/tls/config.go b/component/tls/config.go index fc28dc81..4470658f 100644 --- a/component/tls/config.go +++ b/component/tls/config.go @@ -12,10 +12,10 @@ import ( "time" ) -var globalFingerprints [][32]byte +var globalFingerprints = make([][32]byte, 0, 0) var mutex sync.Mutex -func verifyPeerCertificateAndFingerprints(fingerprints [][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { +func verifyPeerCertificateAndFingerprints(fingerprints *[][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { if insecureSkipVerify { return nil @@ -34,7 +34,7 @@ func verifyPeerCertificateAndFingerprints(fingerprints [][32]byte, insecureSkipV return nil } else { fingerprint := sha256.Sum256(cert.Raw) - for _, fp := range fingerprints { + for _, fp := range *fingerprints { if bytes.Equal(fingerprint[:], fp[:]) { return nil } @@ -85,10 +85,10 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) if tlsConfig == nil { return &tls.Config{ InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, false), + VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), }, nil } else { - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) + tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) tlsConfig.InsecureSkipVerify = true return tlsConfig, nil } @@ -99,11 +99,11 @@ func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config { if tlsConfig == nil { return &tls.Config{ InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(globalFingerprints, false), + VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false), } } - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(globalFingerprints, tlsConfig.InsecureSkipVerify) + tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) tlsConfig.InsecureSkipVerify = true return tlsConfig } @@ -116,10 +116,10 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin if tlsConfig == nil { return &xtls.Config{ InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, false), + VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), }, nil } else { - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) + tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) tlsConfig.InsecureSkipVerify = true return tlsConfig, nil } @@ -130,11 +130,11 @@ func GetGlobalFingerprintXTLCConfig(tlsConfig *xtls.Config) *xtls.Config { if tlsConfig == nil { return &xtls.Config{ InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(globalFingerprints, false), + VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false), } } - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(globalFingerprints, tlsConfig.InsecureSkipVerify) + tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) tlsConfig.InsecureSkipVerify = true return tlsConfig } diff --git a/config/config.go b/config/config.go index 59d493c2..ee4fbb16 100644 --- a/config/config.go +++ b/config/config.go @@ -2,10 +2,9 @@ package config import ( "container/list" + "encoding/json" "errors" "fmt" - "github.com/Dreamacro/clash/constant/sniffer" - "github.com/Dreamacro/clash/listener/tun/ipstack/commons" "net" "net/netip" "net/url" @@ -31,6 +30,7 @@ import ( "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" providerTypes "github.com/Dreamacro/clash/constant/provider" + "github.com/Dreamacro/clash/constant/sniffer" snifferTypes "github.com/Dreamacro/clash/constant/sniffer" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" @@ -118,8 +118,73 @@ type Tun struct { DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"` AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` - TunAddressPrefix netip.Prefix `yaml:"-" json:"-"` RedirectToTun []string `yaml:"-" json:"-"` + + MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"` + Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"` + StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` + Inet4RouteAddress []ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` + Inet6RouteAddress []ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` + IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` + IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` + ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` + ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` + IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` + ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` + EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` +} + +type ListenPrefix netip.Prefix + +func (p ListenPrefix) MarshalJSON() ([]byte, error) { + prefix := netip.Prefix(p) + if !prefix.IsValid() { + return json.Marshal(nil) + } + return json.Marshal(prefix.String()) +} + +func (p ListenPrefix) MarshalYAML() (interface{}, error) { + prefix := netip.Prefix(p) + if !prefix.IsValid() { + return nil, nil + } + return prefix.String(), nil +} + +func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { + var value string + err := json.Unmarshal(bytes, &value) + if err != nil { + return err + } + prefix, err := netip.ParsePrefix(value) + if err != nil { + return err + } + *p = ListenPrefix(prefix) + return nil +} + +func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error { + var value string + err := node.Decode(&value) + if err != nil { + return err + } + prefix, err := netip.ParsePrefix(value) + if err != nil { + return err + } + *p = ListenPrefix(prefix) + return nil +} + +func (p ListenPrefix) Build() netip.Prefix { + return netip.Prefix(p) } // IPTables config @@ -130,12 +195,14 @@ type IPTables struct { } type Sniffer struct { - Enable bool - Sniffers []sniffer.Type - Reverses *trie.DomainTrie[bool] - ForceDomain *trie.DomainTrie[bool] - SkipDomain *trie.DomainTrie[bool] - Ports *[]utils.Range[uint16] + Enable bool + Sniffers []sniffer.Type + Reverses *trie.DomainTrie[bool] + ForceDomain *trie.DomainTrie[bool] + SkipDomain *trie.DomainTrie[bool] + Ports *[]utils.Range[uint16] + ForceDnsMapping bool + ParsePureIp bool } // Experimental config @@ -153,6 +220,7 @@ type Config struct { Hosts *trie.DomainTrie[netip.Addr] Profile *Profile Rules []C.Rule + SubRules *map[string][]C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy Providers map[string]providerTypes.ProxyProvider @@ -193,6 +261,22 @@ type RawTun struct { AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoDetectInterface bool `yaml:"auto-detect-interface"` RedirectToTun []string `yaml:"-" json:"-"` + + MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + //Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"` + Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"` + StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` + Inet4RouteAddress []ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` + Inet6RouteAddress []ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` + IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` + IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` + ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` + ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` + IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` + ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` + EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` } type RawConfig struct { @@ -233,6 +317,7 @@ type RawConfig struct { Proxy []map[string]any `yaml:"proxies"` ProxyGroup []map[string]any `yaml:"proxy-groups"` Rule []string `yaml:"rules"` + SubRules map[string][]string `yaml:"sub-rules"` } type RawGeoXUrl struct { @@ -242,11 +327,13 @@ type RawGeoXUrl struct { } type RawSniffer struct { - Enable bool `yaml:"enable" json:"enable"` - Sniffing []string `yaml:"sniffing" json:"sniffing"` - ForceDomain []string `yaml:"force-domain" json:"force-domain"` - SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` - Ports []string `yaml:"port-whitelist" json:"port-whitelist"` + Enable bool `yaml:"enable" json:"enable"` + Sniffing []string `yaml:"sniffing" json:"sniffing"` + ForceDomain []string `yaml:"force-domain" json:"force-domain"` + SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` + Ports []string `yaml:"port-whitelist" json:"port-whitelist"` + ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"` + ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"` } // EBpf config @@ -294,8 +381,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Device: "", Stack: C.TunGvisor, DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query - AutoRoute: false, - AutoDetectInterface: false, + AutoRoute: true, + AutoDetectInterface: true, + Inet6Address: []ListenPrefix{ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))}, }, EBpf: EBpf{ RedirectToTun: []string{}, @@ -335,11 +423,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { }, }, Sniffer: RawSniffer{ - Enable: false, - Sniffing: []string{}, - ForceDomain: []string{}, - SkipDomain: []string{}, - Ports: []string{}, + Enable: false, + Sniffing: []string{}, + ForceDomain: []string{}, + SkipDomain: []string{}, + Ports: []string{}, + ForceDnsMapping: true, + ParsePureIp: true, }, Profile: Profile{ StoreSelected: true, @@ -381,12 +471,18 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Proxies = proxies config.Providers = providers - rules, ruleProviders, err := parseRules(rawCfg, proxies) + subRules, ruleProviders, err := parseSubRules(rawCfg, proxies) + if err != nil { + return nil, err + } + config.SubRules = subRules + config.RuleProviders = ruleProviders + + rules, err := parseRules(rawCfg, proxies, subRules) if err != nil { return nil, err } config.Rules = rules - config.RuleProviders = ruleProviders hosts, err := parseHosts(rawCfg) if err != nil { @@ -563,8 +659,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return proxies, providersMap, nil } -func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]providerTypes.RuleProvider, error) { - ruleProviders := map[string]providerTypes.RuleProvider{} +func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules *map[string][]C.Rule, ruleProviders map[string]providerTypes.RuleProvider, err error) { + ruleProviders = map[string]providerTypes.RuleProvider{} + subRules = &map[string][]C.Rule{} log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) // parse rule provider for name, mapping := range cfg.RuleProvider { @@ -577,6 +674,102 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin RP.SetRuleProvider(rp) } + for name, rawRules := range cfg.SubRules { + var rules []C.Rule + for idx, line := range rawRules { + rawRule := trimArr(strings.Split(line, ",")) + var ( + payload string + target string + params []string + ruleName = strings.ToUpper(rawRule[0]) + ) + + l := len(rawRule) + + if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" { + target = rawRule[l-1] + payload = strings.Join(rawRule[1:l-1], ",") + } else { + if l < 2 { + return nil, nil, fmt.Errorf("sub-rules[%d] [%s] error: format invalid", idx, line) + } + if l < 4 { + rawRule = append(rawRule, make([]string, 4-l)...) + } + if ruleName == "MATCH" { + l = 2 + } + if l >= 3 { + l = 3 + payload = rawRule[1] + } + target = rawRule[l-1] + params = rawRule[l:] + } + + if _, ok := proxies[target]; !ok && ruleName != "SUB-RULE" { + return nil, nil, fmt.Errorf("sub-rules[%d:%s] [%s] error: proxy [%s] not found", idx, name, line, target) + } + + params = trimArr(params) + parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules) + if parseErr != nil { + return nil, nil, fmt.Errorf("sub-rules[%d] [%s] error: %s", idx, line, parseErr.Error()) + } + + rules = append(rules, parsed) + } + (*subRules)[name] = rules + } + + if err = verifySubRule(subRules); err != nil { + return nil, nil, err + } + + return +} + +func verifySubRule(subRules *map[string][]C.Rule) error { + for name := range *subRules { + err := verifySubRuleCircularReferences(name, subRules, []string{}) + if err != nil { + return err + } + } + return nil +} + +func verifySubRuleCircularReferences(n string, subRules *map[string][]C.Rule, arr []string) error { + isInArray := func(v string, array []string) bool { + for _, c := range array { + if v == c { + return true + } + } + return false + } + + arr = append(arr, n) + for i, rule := range (*subRules)[n] { + if rule.RuleType() == C.SubRules { + if _, ok := (*subRules)[rule.Adapter()]; !ok { + return fmt.Errorf("sub-rule[%d:%s] error: [%s] not found", i, n, rule.Adapter()) + } + if isInArray(rule.Adapter(), arr) { + arr = append(arr, rule.Adapter()) + return fmt.Errorf("sub-rule error: circular references [%s]", strings.Join(arr, "->")) + } + + if err := verifySubRuleCircularReferences(rule.Adapter(), subRules, arr); err != nil { + return err + } + } + } + return nil +} + +func parseRules(cfg *RawConfig, proxies map[string]C.Proxy, subRules *map[string][]C.Rule) ([]C.Rule, error) { var rules []C.Rule rulesConfig := cfg.Rule @@ -592,12 +785,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin l := len(rule) - if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" { + if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" { target = rule[l-1] payload = strings.Join(rule[1:l-1], ",") } else { if l < 2 { - return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) + return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) } if l < 4 { rule = append(rule, make([]string, 4-l)...) @@ -612,15 +805,18 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin target = rule[l-1] params = rule[l:] } - if _, ok := proxies[target]; !ok { - return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target) + if ruleName != "SUB-RULE" { + return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target) + } else if _, ok = (*subRules)[target]; !ok { + return nil, fmt.Errorf("rules[%d] [%s] error: sub-rule [%s] not found", idx, line, target) + } } params = trimArr(params) - parsed, parseErr := R.ParseRule(ruleName, payload, target, params) + parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules) if parseErr != nil { - return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) + return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } rules = append(rules, parsed) @@ -628,7 +824,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin runtime.GC() - return rules, ruleProviders, nil + return rules, nil } func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) { @@ -931,17 +1127,6 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser { } func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) { - if rawTun.Enable && rawTun.AutoDetectInterface { - autoDetectInterfaceName, err := commons.GetAutoDetectInterface() - if err != nil { - log.Warnln("Can not find auto detect interface.[%s]", err) - } else { - log.Warnln("Auto detect interface: %s", autoDetectInterfaceName) - } - - general.Interface = autoDetectInterfaceName - } - var dnsHijack []netip.AddrPort for _, d := range rawTun.DNSHijack { @@ -963,6 +1148,7 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) { } else { tunAddressPrefix = netip.MustParsePrefix("198.18.0.1/16") } + tunAddressPrefix = netip.PrefixFrom(tunAddressPrefix.Addr(), 30) return &Tun{ Enable: rawTun.Enable, @@ -971,14 +1157,31 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) { DNSHijack: dnsHijack, AutoRoute: rawTun.AutoRoute, AutoDetectInterface: rawTun.AutoDetectInterface, - TunAddressPrefix: tunAddressPrefix, RedirectToTun: rawTun.RedirectToTun, + + MTU: rawTun.MTU, + Inet4Address: []ListenPrefix{ListenPrefix(tunAddressPrefix)}, + Inet6Address: rawTun.Inet6Address, + StrictRoute: rawTun.StrictRoute, + Inet4RouteAddress: rawTun.Inet4RouteAddress, + Inet6RouteAddress: rawTun.Inet6RouteAddress, + IncludeUID: rawTun.IncludeUID, + IncludeUIDRange: rawTun.IncludeUIDRange, + ExcludeUID: rawTun.ExcludeUID, + ExcludeUIDRange: rawTun.ExcludeUIDRange, + IncludeAndroidUser: rawTun.IncludeAndroidUser, + IncludePackage: rawTun.IncludePackage, + ExcludePackage: rawTun.ExcludePackage, + EndpointIndependentNat: rawTun.EndpointIndependentNat, + UDPTimeout: rawTun.UDPTimeout, }, nil } func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) { sniffer := &Sniffer{ - Enable: snifferRaw.Enable, + Enable: snifferRaw.Enable, + ForceDnsMapping: snifferRaw.ForceDnsMapping, + ParsePureIp: snifferRaw.ParsePureIp, } var ports []utils.Range[uint16] diff --git a/constant/adapters.go b/constant/adapters.go index f1603fd1..7fc31667 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -106,12 +106,13 @@ type ProxyAdapter interface { ListenPacketOnStreamConn(c net.Conn, metadata *Metadata) (PacketConn, error) // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. - Unwrap(metadata *Metadata) Proxy + Unwrap(metadata *Metadata, touch bool) Proxy } type Group interface { URLTest(ctx context.Context, url string) (mp map[string]uint16, err error) GetProxies(touch bool) []Proxy + Touch() } type DelayHistory struct { diff --git a/constant/dns.go b/constant/dns.go index 4d94ad6d..be8b4a17 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -16,6 +16,7 @@ const ( DNSNormal DNSMode = iota DNSFakeIP DNSMapping + DNSHosts ) type DNSMode int @@ -64,6 +65,8 @@ func (e DNSMode) String() string { return "fake-ip" case DNSMapping: return "redir-host" + case DNSHosts: + return "hosts" default: return "unknown" } diff --git a/constant/metadata.go b/constant/metadata.go index b58847ff..f248cd21 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -145,7 +145,7 @@ func (m *Metadata) Resolved() bool { // Pure is used to solve unexpected behavior // when dialing proxy connection in DNSMapping mode. func (m *Metadata) Pure() *Metadata { - if m.DNSMode == DNSMapping && m.DstIP.IsValid() { + if (m.DNSMode == DNSMapping || m.DNSMode == DNSHosts) && m.DstIP.IsValid() { copyM := *m copyM.Host = "" if copyM.DstIP.Is4() { diff --git a/constant/rule.go b/constant/rule.go index a403ac63..223dec6e 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -19,6 +19,7 @@ const ( Network Uid INTYPE + SubRules MATCH AND OR @@ -65,6 +66,8 @@ func (rt RuleType) String() string { return "Uid" case INTYPE: return "InType" + case SubRules: + return "SubRules" case AND: return "AND" case OR: @@ -78,7 +81,7 @@ func (rt RuleType) String() string { type Rule interface { RuleType() RuleType - Match(metadata *Metadata) bool + Match(metadata *Metadata) (bool, string) Adapter() string Payload() string ShouldResolveIP() bool diff --git a/constant/tun.go b/constant/tun.go index 10e24861..38f51155 100644 --- a/constant/tun.go +++ b/constant/tun.go @@ -7,13 +7,15 @@ import ( ) var StackTypeMapping = map[string]TUNStack{ - strings.ToUpper(TunGvisor.String()): TunGvisor, - strings.ToUpper(TunSystem.String()): TunSystem, + strings.ToLower(TunGvisor.String()): TunGvisor, + strings.ToLower(TunSystem.String()): TunSystem, + strings.ToLower(TunLWIP.String()): TunLWIP, } const ( TunGvisor TUNStack = iota TunSystem + TunLWIP ) type TUNStack int @@ -24,7 +26,7 @@ func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error { if err := unmarshal(&tp); err != nil { return err } - mode, exist := StackTypeMapping[strings.ToUpper(tp)] + mode, exist := StackTypeMapping[strings.ToLower(tp)] if !exist { return errors.New("invalid tun stack") } @@ -41,7 +43,7 @@ func (e TUNStack) MarshalYAML() (any, error) { func (e *TUNStack) UnmarshalJSON(data []byte) error { var tp string json.Unmarshal(data, &tp) - mode, exist := StackTypeMapping[strings.ToUpper(tp)] + mode, exist := StackTypeMapping[strings.ToLower(tp)] if !exist { return errors.New("invalid tun stack") } @@ -60,6 +62,8 @@ func (e TUNStack) String() string { return "gVisor" case TunSystem: return "System" + case TunLWIP: + return "LWIP" default: return "unknown" } diff --git a/context/conn.go b/context/conn.go index 00a125f3..08bbe3c7 100644 --- a/context/conn.go +++ b/context/conn.go @@ -1,10 +1,11 @@ package context import ( - CN "github.com/Dreamacro/clash/common/net" "net" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" + "github.com/gofrs/uuid" ) @@ -20,7 +21,7 @@ func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { return &ConnContext{ id: id, metadata: metadata, - conn: CN.NewBufferedConn(conn), + conn: N.NewBufferedConn(conn), } } diff --git a/docs/config.yaml b/docs/config.yaml index 53ed7416..61fb319a 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -134,7 +134,7 @@ dns: # 配置强制 fallback,优先于 IP 判断,具体分类自行查看 geosite 库 # geosite: # - gfw - # 配置不需要使用 fallback 的 IP CIDR + # 如果不匹配 ipcidr 则使用 nameservers 中的结果 # ipcidr: # - 240.0.0.0/4 # domain: @@ -162,14 +162,14 @@ proxies: port: 443 cipher: chacha20-ietf-poly1305 password: "password" - # udp: true - # udp-over-tcp: false - # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual - # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 - # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, - # UDP 则为双栈解析,获取结果中的第一个 IPv4 - # ipv6-prefer 同 ipv4-prefer - # 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效 + # udp: true + # udp-over-tcp: false + # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual + # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 + # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, + # UDP 则为双栈解析,获取结果中的第一个 IPv4 + # ipv6-prefer 同 ipv4-prefer + # 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效 - name: "ss2" type: ss server: server @@ -391,13 +391,16 @@ proxies: path: "/" headers: Host: example.com + + #hysteria - name: "hysteria" type: hysteria server: server.com port: 443 auth_str: yourpassword # obfs: obfs_str - # alpn: h3 + # alpn: + # - h3 protocol: udp # 支持 udp/wechat-video/faketcp up: "30 Mbps" # 若不写单位,默认为 Mbps down: "200 Mbps" # 若不写单位,默认为 Mbps @@ -538,3 +541,29 @@ rules: - DOMAIN-KEYWORD,google,ss1 - IP-CIDR,1.1.1.1/32,ss1 - IP-CIDR6,2409::/64,DIRECT + - SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1 # 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 当规则集 + - SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2 +# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用 +# google.com(not match)--> baidu.com(match) +# / | +# / | +# https://baidu.com --> rule1 --> rule2 --> sub-rule-name1(match tcp) 使用 DIRECT +# +# +# google.com(not match)--> baidu.com(not match) +# / | +# / | +# dns 1.1.1.1 --> rule1 --> rule2 --> sub-rule-name1(match udp) sub-rule-name2(match udp) +# | +# | +# 使用 REJECT <-- 1.1.1.1/32(match) +# + +sub-rules: + sub-rule-name1: + - DOMAIN,google.com,ss1 + - DOMAIN,baidu.com,DIRECT + sub-rule-name2: + - IP-CIDR,1.1.1.1/32,REJECT + - IP-CIDR,8.8.8.8/32,ss1 + - DOMAIN,dns.alidns.com,REJECT diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..7da95ac3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1664705630, + "narHash": "sha256-MLi1J9tIZQFj8v9RKmG89HJAE5ja3z4ui4Tf9+wG/bM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f71b215225dec75df6266ff7764d54c2e44ef226", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "master", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..2b775e2f --- /dev/null +++ b/flake.nix @@ -0,0 +1,61 @@ +{ + description = "Another Clash Kernel"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/master"; + + inputs.utils.url = "github:numtide/flake-utils"; + + outputs = { self, nixpkgs, utils }: + utils.lib.eachDefaultSystem + (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ self.overlay ]; + }; + in + rec { + packages.default = pkgs.clash-meta; + } + ) // + ( + let version = nixpkgs.lib.substring 0 8 self.lastModifiedDate or self.lastModified or "19700101"; in + { + overlay = final: prev: { + + clash-meta = final.buildGoModule { + pname = "clash-meta"; + inherit version; + src = ./.; + + vendorSha256 = "sha256-yhq4WHQcS4CrdcO6KJ5tSn4m7l5g1lNgE9/2BWd9Iys="; + + # Do not build testing suit + excludedPackages = [ "./test" ]; + + CGO_ENABLED = 0; + + ldflags = [ + "-s" + "-w" + "-X github.com/Dreamacro/clash/constant.Version=dev-${version}" + "-X github.com/Dreamacro/clash/constant.BuildTime=${version}" + ]; + + tags = [ + "with_gvisor" + ]; + + # Network required + doCheck = false; + + postInstall = '' + mv $out/bin/clash $out/bin/clash-meta + ''; + + }; + }; + } + ); +} + diff --git a/go.mod b/go.mod index a6ecb70e..0c772909 100644 --- a/go.mod +++ b/go.mod @@ -3,43 +3,41 @@ module github.com/Dreamacro/clash go 1.19 require ( - github.com/cilium/ebpf v0.9.1 + github.com/cilium/ebpf v0.9.3 github.com/coreos/go-iptables v0.6.0 - github.com/database64128/tfo-go v1.1.0 - github.com/dlclark/regexp2 v1.4.0 + github.com/database64128/tfo-go v1.1.2 + github.com/dlclark/regexp2 v1.7.0 github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/cors v1.2.1 - github.com/go-chi/render v1.0.1 - github.com/gofrs/uuid v4.2.0+incompatible + github.com/go-chi/render v1.0.2 + github.com/gofrs/uuid v4.3.0+incompatible github.com/google/gopacket v1.1.19 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.4 - github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f - github.com/lucas-clemente/quic-go v0.27.2 + github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c + github.com/lucas-clemente/quic-go v0.29.1 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 - github.com/miekg/dns v1.1.49 - github.com/oschwald/geoip2-golang v1.7.0 - github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c - github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c - github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec - github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.7.2 + github.com/miekg/dns v1.1.50 + github.com/oschwald/geoip2-golang v1.8.0 + github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186 + github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 + github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd + github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f + github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.0 github.com/vishvananda/netlink v1.2.1-beta.2 - github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 + github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 go.etcd.io/bbolt v1.3.6 - go.uber.org/atomic v1.9.0 + go.uber.org/atomic v1.10.0 go.uber.org/automaxprocs v1.5.1 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/exp v0.0.0-20220608143224-64259d1afd70 - golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e - golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f - golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e - golang.org/x/time v0.0.0-20220411224347-583f2d630306 - golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 - golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e - google.golang.org/protobuf v1.28.0 + golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 + golang.org/x/net v0.0.0-20221004154528-8021a29435af + golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 + golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 + google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 - gvisor.dev/gvisor v0.0.0-20220810234332-45096a971e66 + ) replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 @@ -47,29 +45,33 @@ replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-be replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599 require ( + github.com/ajg/form v1.5.1 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/klauspost/cpuid/v2 v2.0.12 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect - github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect + github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/oschwald/maxminddb-golang v1.9.0 // indirect + github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect + github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect + github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect - golang.org/x/tools v0.1.10 // indirect - golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect - golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect + golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect + golang.org/x/tools v0.1.12 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect lukechampine.com/blake3 v1.1.7 // indirect ) diff --git a/go.sum b/go.sum index a1c42dc0..d45870ef 100644 --- a/go.sum +++ b/go.sum @@ -10,25 +10,27 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc= github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= -github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4= -github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= +github.com/cilium/ebpf v0.9.3 h1:5KtxXZU+scyERvkJMEm16TbScVvuuMrlhPly78ZMbSc= +github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/database64128/tfo-go v1.1.0 h1:VO0polyGNSAmr99nYw9GQeMz7ZOcQ/QbjlTwniHwfTQ= -github.com/database64128/tfo-go v1.1.0/go.mod h1:95pOT8bnV3P2Lmu9upHNWFHz6dYGJ9cr7pnb0tGQAG8= +github.com/database64128/tfo-go v1.1.2 h1:GwxtJp09BdUTVEoeT421t231eNZoGOCRkklbl4WI1kU= +github.com/database64128/tfo-go v1.1.2/go.mod h1:jgrSUPyOvTGQyn6irCOpk7L2W/q/0VLZZcovQiMi+bI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -44,13 +46,13 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= -github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= +github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= -github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= +github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -71,15 +73,15 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -97,8 +99,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= -github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ= +github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= @@ -109,8 +111,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= +github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -129,8 +131,9 @@ github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ= github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= +github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= +github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= @@ -140,8 +143,8 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= -github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= @@ -161,10 +164,10 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= -github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ= -github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y= -github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY= +github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs= +github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw= +github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= +github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -175,12 +178,21 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c h1:98QC0wtaD648MFPw82KaT1O9LloQgR4ZyIDtNtsno8Y= -github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c/go.mod h1:I67R/q5f67xDExL2kL3RLIP7kGJBOPkYXkpRAykgC+E= -github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c h1:Jhgjyb2jXL4GtwJec6/kgeTqaQXsvMiNX2wAkGOSD3I= -github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c/go.mod h1:ng5pxdNnKZWlxzZTXRqWeY0ftzhScPZmjgJGJeRuPYY= -github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec h1:jUSfKmyL6K9O2TvIvcVacZ4eNXHYbNSfdph+DRPyVlU= -github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec/go.mod h1:jDZ8fJgOea7Y7MMHWgfqwLBVLnhtW2zuxS5wjtDaB84= +github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34= +github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g= +github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= +github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= +github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= +github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= +github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186 h1:ZDlgH6dTozS3ODaYq1GxCj+H8NvYESaex90iX72gadw= +github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4= +github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4= +github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM= +github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd h1:TtoZDwg09Cpqi+gCmCtL6w4oEUZ5lHz+vHIjdr1UBNY= +github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd/go.mod h1:1u3pjXA9HmH7kRiBJqM3C/zPxrxnCLd3svmqtub/RFU= +github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f h1:xyJ3Wbibcug4DxLi/FCHX2Td667SfieyZv645b8+eEE= +github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= @@ -204,19 +216,21 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599 h1:We+z04jRpTGxFggeGWf+GbinhlIk1I1kMMEgujhUfiA= github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= @@ -226,17 +240,17 @@ github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ= -github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU= +github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 h1:nn7SOQy8xCu3iXNv7oiBhhEQtbWdnEOMnuKBlHvrqIM= +github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M= +github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -247,11 +261,11 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20220608143224-64259d1afd70 h1:8uGxpY2cLF9H/NSHUiEWUIBZqIcsMzMWIMPCCUkyYgc= -golang.org/x/exp v0.0.0-20220608143224-64259d1afd70/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -259,8 +273,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -285,8 +299,9 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= +golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -299,8 +314,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -315,7 +330,6 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -337,8 +351,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -351,8 +368,8 @@ golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9G golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= -golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -365,20 +382,12 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= -golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ= -golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= -golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w= -golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -403,8 +412,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -423,8 +432,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -gvisor.dev/gvisor v0.0.0-20220810234332-45096a971e66 h1:GrHxpIMY0lHZ3Q8rp3m4iOb0pJsnCQ/5AHaN9SXE69E= -gvisor.dev/gvisor v0.0.0-20220810234332-45096a971e66/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= +gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4= +gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index a8999e36..e5bb1ad7 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -265,7 +265,10 @@ func updateTun(tun *config.Tun) { func updateSniffer(sniffer *config.Sniffer) { if sniffer.Enable { - dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports) + dispatcher, err := SNI.NewSnifferDispatcher( + sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports, + sniffer.ForceDnsMapping, sniffer.ParsePureIp, + ) if err != nil { log.Warnln("initial sniffer failed, err:%v", err) } diff --git a/listener/listener.go b/listener/listener.go index debba89d..8512c0b1 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -2,10 +2,7 @@ package proxy import ( "fmt" - "github.com/Dreamacro/clash/component/ebpf" - "github.com/Dreamacro/clash/listener/autoredir" - "github.com/Dreamacro/clash/listener/inner" - "github.com/Dreamacro/clash/listener/tun/ipstack/commons" + "github.com/Dreamacro/clash/listener/sing_tun" "golang.org/x/exp/slices" "net" "sort" @@ -13,15 +10,16 @@ import ( "sync" "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/component/ebpf" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/autoredir" "github.com/Dreamacro/clash/listener/http" + "github.com/Dreamacro/clash/listener/inner" "github.com/Dreamacro/clash/listener/mixed" "github.com/Dreamacro/clash/listener/redir" "github.com/Dreamacro/clash/listener/socks" "github.com/Dreamacro/clash/listener/tproxy" - "github.com/Dreamacro/clash/listener/tun" - "github.com/Dreamacro/clash/listener/tun/ipstack" "github.com/Dreamacro/clash/log" ) @@ -40,7 +38,7 @@ var ( tproxyUDPListener *tproxy.UDPListener mixedListener *mixed.Listener mixedUDPLister *socks.UDPListener - tunStackListener ipstack.Stack + tunLister *sing_tun.Listener autoRedirListener *autoredir.Listener autoRedirProgram *ebpf.TcEBpfProgram tcProgram *ebpf.TcEBpfProgram @@ -350,6 +348,9 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- * }() if !hasTunConfigChange(tunConf) { + if tunLister != nil { + tunLister.FlushDefaultInterface() + } return } @@ -359,7 +360,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- * return } - tunStackListener, err = tun.New(tunConf, tcpIn, udpIn) + tunLister, err = sing_tun.New(*tunConf, tcpIn, udpIn) lastTunConf = tunConf } @@ -429,7 +430,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- return } - defaultRouteInterfaceName, err := commons.GetAutoDetectInterface() + defaultRouteInterfaceName, err := ebpf.GetAutoDetectInterface() if err != nil { return } @@ -538,7 +539,7 @@ func hasTunConfigChange(tunConf *config.Tun) bool { return true } - if tunConf.TunAddressPrefix.String() != lastTunConf.TunAddressPrefix.String() { + if slices.Equal(tunConf.Inet4Address, lastTunConf.Inet4Address) && slices.Equal(tunConf.Inet6Address, lastTunConf.Inet6Address) { return true } @@ -546,16 +547,9 @@ func hasTunConfigChange(tunConf *config.Tun) bool { } func Cleanup(wait bool) { - if tunStackListener != nil { - _ = tunStackListener.Close() - commons.StopDefaultInterfaceChangeMonitor() - - if wait { - commons.WaitForTunClose(lastTunConf.Device) - } - - commons.CleanupRule() + if tunLister != nil { + tunLister.Close() + tunLister = nil } - tunStackListener = nil lastTunConf = nil } diff --git a/listener/sing/log.go b/listener/sing/log.go new file mode 100644 index 00000000..4847e063 --- /dev/null +++ b/listener/sing/log.go @@ -0,0 +1,41 @@ +package sing + +import ( + "fmt" + + "github.com/Dreamacro/clash/log" + + L "github.com/sagernet/sing/common/logger" +) + +type logger struct{} + +func (l logger) Trace(args ...any) { + log.Debugln(fmt.Sprint(args...)) +} + +func (l logger) Debug(args ...any) { + log.Debugln(fmt.Sprint(args...)) +} + +func (l logger) Info(args ...any) { + log.Infoln(fmt.Sprint(args...)) +} + +func (l logger) Warn(args ...any) { + log.Warnln(fmt.Sprint(args...)) +} + +func (l logger) Error(args ...any) { + log.Errorln(fmt.Sprint(args...)) +} + +func (l logger) Fatal(args ...any) { + log.Fatalln(fmt.Sprint(args...)) +} + +func (l logger) Panic(args ...any) { + log.Fatalln(fmt.Sprint(args...)) +} + +var Logger L.Logger = logger{} diff --git a/listener/sing/sing.go b/listener/sing/sing.go new file mode 100644 index 00000000..df55f100 --- /dev/null +++ b/listener/sing/sing.go @@ -0,0 +1,151 @@ +package sing + +import ( + "context" + "errors" + "net" + "sync" + "time" + + "github.com/Dreamacro/clash/adapter/inbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/socks5" + + vmess "github.com/sagernet/sing-vmess" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" +) + +const UDPTimeout = 5 * time.Minute + +type ListenerHandler struct { + TcpIn chan<- C.ConnContext + UdpIn chan<- *inbound.PacketAdapter + Type C.Type +} + +type waitCloseConn struct { + net.Conn + wg *sync.WaitGroup + close sync.Once + rAddr net.Addr +} + +func (c *waitCloseConn) Close() error { // call from handleTCPConn(connCtx C.ConnContext) + c.close.Do(func() { + c.wg.Done() + }) + return c.Conn.Close() +} + +func (c *waitCloseConn) RemoteAddr() net.Addr { + return c.rAddr +} + +func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + switch metadata.Destination.Fqdn { + case vmess.MuxDestination.Fqdn: + return vmess.HandleMuxConnection(ctx, conn, h) + case uot.UOTMagicAddress: + metadata.Destination = M.Socksaddr{} + return h.NewPacketConnection(ctx, uot.NewClientConn(conn), metadata) + } + target := socks5.ParseAddr(metadata.Destination.String()) + wg := &sync.WaitGroup{} + defer wg.Wait() // this goroutine must exit after conn.Close() + wg.Add(1) + h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{Conn: conn, wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type) + return nil +} + +func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { + defer func() { _ = conn.Close() }() + mutex := sync.Mutex{} + conn2 := conn // a new interface to set nil in defer + defer func() { + mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running + defer mutex.Unlock() + conn2 = nil + }() + for { + buff := buf.NewPacket() // do not use stack buffer + dest, err := conn.ReadPacket(buff) + if err != nil { + buff.Release() + if E.IsClosed(err) { + break + } + return err + } + target := socks5.ParseAddr(dest.String()) + packet := &packet{ + conn: &conn2, + mutex: &mutex, + rAddr: metadata.Source.UDPAddr(), + lAddr: conn.LocalAddr(), + buff: buff, + } + select { + case h.UdpIn <- inbound.NewPacket(target, packet, h.Type): + default: + } + } + return nil +} + +func (h *ListenerHandler) NewError(ctx context.Context, err error) { + log.Warnln("%s listener get error: %+v", h.Type.String(), err) +} + +type packet struct { + conn *network.PacketConn + mutex *sync.Mutex + rAddr net.Addr + lAddr net.Addr + buff *buf.Buffer +} + +func (c *packet) Data() []byte { + return c.buff.Bytes() +} + +// WriteBack wirtes UDP packet with source(ip, port) = `addr` +func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { + if addr == nil { + err = errors.New("address is invalid") + return + } + buff := buf.NewPacket() + defer buff.Release() + n, err = buff.Write(b) + if err != nil { + return + } + + c.mutex.Lock() + defer c.mutex.Unlock() + conn := *c.conn + if conn == nil { + err = errors.New("writeBack to closed connection") + return + } + err = conn.WritePacket(buff, M.ParseSocksaddr(addr.String())) + return +} + +// LocalAddr returns the source IP/Port of UDP Packet +func (c *packet) LocalAddr() net.Addr { + return c.rAddr +} + +func (c *packet) Drop() { + c.buff.Release() +} + +func (c *packet) InAddr() net.Addr { + return c.lAddr +} diff --git a/listener/sing_tun/dns.go b/listener/sing_tun/dns.go new file mode 100644 index 00000000..39e2b1e5 --- /dev/null +++ b/listener/sing_tun/dns.go @@ -0,0 +1,165 @@ +package sing_tun + +import ( + "context" + "encoding/binary" + "io" + "net" + "net/netip" + "sync" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/resolver" + "github.com/Dreamacro/clash/listener/sing" + "github.com/Dreamacro/clash/log" + + D "github.com/miekg/dns" + + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/network" +) + +const DefaultDnsReadTimeout = time.Second * 10 + +type ListenerHandler struct { + sing.ListenerHandler + DnsAdds []netip.AddrPort +} + +func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool { + if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack + return true + } + for _, addrPort := range h.DnsAdds { + if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) { + return true + } + } + return false +} + +func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + if h.ShouldHijackDns(metadata.Destination.AddrPort()) { + log.Debugln("[DNS] hijack tcp:%s", metadata.Destination.String()) + buff := pool.Get(pool.UDPBufferSize) + defer func() { + _ = pool.Put(buff) + _ = conn.Close() + }() + for { + if conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) != nil { + break + } + + length := uint16(0) + if err := binary.Read(conn, binary.BigEndian, &length); err != nil { + break + } + + if int(length) > len(buff) { + break + } + + n, err := io.ReadFull(conn, buff[:length]) + if err != nil { + break + } + + err = func() error { + inData := buff[:n] + msg, err := RelayDnsPacket(inData) + if err != nil { + return err + } + + err = binary.Write(conn, binary.BigEndian, uint16(len(msg))) + if err != nil { + return err + } + + _, err = conn.Write(msg) + if err != nil { + return err + } + return nil + }() + if err != nil { + return err + } + } + return nil + } + return h.ListenerHandler.NewConnection(ctx, conn, metadata) +} + +func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { + if h.ShouldHijackDns(metadata.Destination.AddrPort()) { + log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String()) + defer func() { _ = conn.Close() }() + mutex := sync.Mutex{} + conn2 := conn // a new interface to set nil in defer + defer func() { + mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running + defer mutex.Unlock() + conn2 = nil + }() + for { + buff := buf.NewPacket() + dest, err := conn.ReadPacket(buff) + if err != nil { + buff.Release() + if E.IsClosed(err) { + break + } + return err + } + go func() { + inData := buff.Bytes() + msg, err := RelayDnsPacket(inData) + if err != nil { + buff.Release() + return + } + buff.Reset() + _, err = buff.Write(msg) + if err != nil { + buff.Release() + return + } + mutex.Lock() + defer mutex.Unlock() + conn := conn2 + if conn == nil { + return + } + err = conn.WritePacket(buff, dest) // WritePacket will release buff + if err != nil { + return + } + }() + } + return nil + } + return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) +} + +func RelayDnsPacket(payload []byte) ([]byte, error) { + msg := &D.Msg{} + if err := msg.Unpack(payload); err != nil { + return nil, err + } + + r, err := resolver.ServeMsg(msg) + if err != nil { + m := new(D.Msg) + m.SetRcode(msg, D.RcodeServerFailure) + return m.Pack() + } + + r.SetRcode(msg, r.Rcode) + r.Compress = true + return r.Pack() +} diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go new file mode 100644 index 00000000..44824b75 --- /dev/null +++ b/listener/sing_tun/server.go @@ -0,0 +1,284 @@ +package sing_tun + +import ( + "context" + "net" + "net/netip" + "runtime" + "strconv" + "strings" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/iface" + "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/sing" + "github.com/Dreamacro/clash/log" + + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/ranges" +) + +var InterfaceName = "Meta" + +type Listener struct { + closed bool + options config.Tun + handler *ListenerHandler + tunName string + + tunIf tun.Tun + tunStack tun.Stack + + networkUpdateMonitor tun.NetworkUpdateMonitor + defaultInterfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager +} + +func CalculateInterfaceName(name string) (tunName string) { + if runtime.GOOS == "darwin" { + tunName = "utun" + } else if name != "" { + tunName = name + return + } else { + tunName = "tun" + } + interfaces, err := net.Interfaces() + if err != nil { + return + } + var tunIndex int + for _, netInterface := range interfaces { + if strings.HasPrefix(netInterface.Name, tunName) { + index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16) + if parseErr == nil { + tunIndex = int(index) + 1 + } + } + } + tunName = F.ToString(tunName, tunIndex) + return +} + +func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (l *Listener, err error) { + tunName := options.Device + if tunName == "" { + tunName = CalculateInterfaceName(InterfaceName) + } + tunMTU := options.MTU + if tunMTU == 0 { + tunMTU = 9000 + } + var udpTimeout int64 + if options.UDPTimeout != 0 { + udpTimeout = options.UDPTimeout + } else { + udpTimeout = int64(sing.UDPTimeout.Seconds()) + } + includeUID := uidToRange(options.IncludeUID) + if len(options.IncludeUIDRange) > 0 { + var err error + includeUID, err = parseRange(includeUID, options.IncludeUIDRange) + if err != nil { + return nil, E.Cause(err, "parse include_uid_range") + } + } + excludeUID := uidToRange(options.ExcludeUID) + if len(options.ExcludeUIDRange) > 0 { + var err error + excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange) + if err != nil { + return nil, E.Cause(err, "parse exclude_uid_range") + } + } + + var dnsAdds []netip.AddrPort + + for _, d := range options.DNSHijack { + dnsAdds = append(dnsAdds, d) + } + for _, a := range options.Inet4Address { + addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53) + dnsAdds = append(dnsAdds, addrPort) + } + for _, a := range options.Inet6Address { + addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53) + dnsAdds = append(dnsAdds, addrPort) + } + + handler := &ListenerHandler{ + ListenerHandler: sing.ListenerHandler{ + TcpIn: tcpIn, + UdpIn: udpIn, + Type: C.TUN, + }, + DnsAdds: dnsAdds, + } + l = &Listener{ + closed: false, + options: options, + handler: handler, + } + defer func() { + if err != nil { + l.Close() + l = nil + } + }() + + networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(handler) + if err != nil { + err = E.Cause(err, "create NetworkUpdateMonitor") + return + } + l.networkUpdateMonitor = networkUpdateMonitor + err = networkUpdateMonitor.Start() + if err != nil { + err = E.Cause(err, "start NetworkUpdateMonitor") + return + } + + defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, tun.DefaultInterfaceMonitorOptions{OverrideAndroidVPN: true}) + if err != nil { + err = E.Cause(err, "create DefaultInterfaceMonitor") + return + } + l.defaultInterfaceMonitor = defaultInterfaceMonitor + defaultInterfaceMonitor.RegisterCallback(func(event int) error { + l.FlushDefaultInterface() + return nil + }) + err = defaultInterfaceMonitor.Start() + if err != nil { + err = E.Cause(err, "start DefaultInterfaceMonitor") + return + } + + tunOptions := tun.Options{ + Name: tunName, + MTU: tunMTU, + Inet4Address: common.Map(options.Inet4Address, config.ListenPrefix.Build), + Inet6Address: common.Map(options.Inet6Address, config.ListenPrefix.Build), + AutoRoute: options.AutoRoute, + StrictRoute: options.StrictRoute, + Inet4RouteAddress: common.Map(options.Inet4RouteAddress, config.ListenPrefix.Build), + Inet6RouteAddress: common.Map(options.Inet6RouteAddress, config.ListenPrefix.Build), + IncludeUID: includeUID, + ExcludeUID: excludeUID, + IncludeAndroidUser: options.IncludeAndroidUser, + IncludePackage: options.IncludePackage, + ExcludePackage: options.ExcludePackage, + InterfaceMonitor: defaultInterfaceMonitor, + TableIndex: 2022, + } + + err = l.buildAndroidRules(&tunOptions) + if err != nil { + err = E.Cause(err, "build android rules") + return + } + tunIf, err := tunOpen(tunOptions) + if err != nil { + err = E.Cause(err, "configure tun interface") + return + } + l.tunIf = tunIf + l.tunStack, err = tun.NewStack(strings.ToLower(options.Stack.String()), tun.StackOptions{ + Context: context.TODO(), + Tun: tunIf, + MTU: tunOptions.MTU, + Name: tunOptions.Name, + Inet4Address: tunOptions.Inet4Address, + Inet6Address: tunOptions.Inet6Address, + EndpointIndependentNat: options.EndpointIndependentNat, + UDPTimeout: udpTimeout, + Handler: handler, + Logger: sing.Logger, + }) + if err != nil { + return + } + err = l.tunStack.Start() + if err != nil { + return + } + log.Infoln("Tun adapter listening at: %s(%s,%s), mtu: %d, auto route: %v, ip stack: %s", + tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.Stack) + return +} + +func (l *Listener) FlushDefaultInterface() { + if l.options.AutoDetectInterface { + targetInterface := dialer.DefaultInterface.Load() + for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} { + autoDetectInterfaceName := l.defaultInterfaceMonitor.DefaultInterfaceName(destination) + if autoDetectInterfaceName == l.tunName { + log.Warnln("Auto detect interface by %s get same name with tun", destination.String()) + } else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "" { + log.Warnln("Auto detect interface by %s get empty name.", destination.String()) + } else { + targetInterface = autoDetectInterfaceName + if old := dialer.DefaultInterface.Load(); old != targetInterface { + log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, targetInterface) + + dialer.DefaultInterface.Store(targetInterface) + + iface.FlushCache() + } + return + } + } + } +} + +func uidToRange(uidList []uint32) []ranges.Range[uint32] { + return common.Map(uidList, func(uid uint32) ranges.Range[uint32] { + return ranges.NewSingle(uid) + }) +} + +func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) { + for _, uidRange := range rangeList { + if !strings.Contains(uidRange, ":") { + return nil, E.New("missing ':' in range: ", uidRange) + } + subIndex := strings.Index(uidRange, ":") + if subIndex == 0 { + return nil, E.New("missing range start: ", uidRange) + } else if subIndex == len(uidRange)-1 { + return nil, E.New("missing range end: ", uidRange) + } + var start, end uint64 + var err error + start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32) + if err != nil { + return nil, E.Cause(err, "parse range start") + } + end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32) + if err != nil { + return nil, E.Cause(err, "parse range end") + } + uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end))) + } + return uidRanges, nil +} + +func (l *Listener) Close() { + l.closed = true + _ = common.Close( + l.tunStack, + l.tunIf, + l.defaultInterfaceMonitor, + l.networkUpdateMonitor, + l.packageManager, + ) +} + +func (l *Listener) Config() config.Tun { + return l.options +} diff --git a/listener/sing_tun/server_android.go b/listener/sing_tun/server_android.go new file mode 100644 index 00000000..e9d36f66 --- /dev/null +++ b/listener/sing_tun/server_android.go @@ -0,0 +1,23 @@ +package sing_tun + +import ( + tun "github.com/sagernet/sing-tun" +) + +func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error { + packageManager, err := tun.NewPackageManager(l.handler) + if err != nil { + return err + } + err = packageManager.Start() + if err != nil { + return err + } + l.packageManager = packageManager + tunOptions.BuildAndroidRules(packageManager, l.handler) + return nil +} + +func (h *ListenerHandler) OnPackagesUpdated(packages int, sharedUsers int) { + return +} diff --git a/listener/sing_tun/server_notandroid.go b/listener/sing_tun/server_notandroid.go new file mode 100644 index 00000000..f621c71e --- /dev/null +++ b/listener/sing_tun/server_notandroid.go @@ -0,0 +1,11 @@ +//go:build !android + +package sing_tun + +import ( + tun "github.com/sagernet/sing-tun" +) + +func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error { + return nil +} diff --git a/listener/sing_tun/server_notwindows.go b/listener/sing_tun/server_notwindows.go new file mode 100644 index 00000000..27872d8e --- /dev/null +++ b/listener/sing_tun/server_notwindows.go @@ -0,0 +1,11 @@ +//go:build !windows + +package sing_tun + +import ( + tun "github.com/sagernet/sing-tun" +) + +func tunOpen(options tun.Options) (tun.Tun, error) { + return tun.Open(options) +} diff --git a/listener/sing_tun/server_windows.go b/listener/sing_tun/server_windows.go new file mode 100644 index 00000000..20fc7031 --- /dev/null +++ b/listener/sing_tun/server_windows.go @@ -0,0 +1,30 @@ +package sing_tun + +import ( + "time" + + "github.com/Dreamacro/clash/log" + + tun "github.com/sagernet/sing-tun" +) + +func tunOpen(options tun.Options) (tunIf tun.Tun, err error) { + maxRetry := 3 + for i := 0; i < maxRetry; i++ { + timeBegin := time.Now() + tunIf, err = tun.Open(options) + if err == nil { + return + } + timeEnd := time.Now() + if timeEnd.Sub(timeBegin) < 1*time.Second { // retrying for "Cannot create a file when that file already exists." + return + } + log.Warnln("Start Tun interface timeout: %s [retrying %d/%d]", err, i+1, maxRetry) + } + return +} + +func init() { + tun.TunnelType = InterfaceName +} diff --git a/listener/tun/device/device.go b/listener/tun/device/device.go deleted file mode 100644 index f7c3552a..00000000 --- a/listener/tun/device/device.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !no_gvisor - -package device - -import ( - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -// Device is the interface that implemented by network layer devices (e.g. tun), -// and easy to use as stack.LinkEndpoint. -type Device interface { - stack.LinkEndpoint - - // Name returns the current name of the device. - Name() string - - // Type returns the driver type of the device. - Type() string - - // Read packets from tun device - Read(packet []byte) (int, error) - - // Write packets to tun device - Write(packet []byte) (int, error) - - // Close stops and closes the device. - Close() error - - // UseEndpoint work for gVisor stack - UseEndpoint() error - - // UseIOBased work for other ip stack - UseIOBased() error -} diff --git a/listener/tun/device/device_no_gvisor.go b/listener/tun/device/device_no_gvisor.go deleted file mode 100644 index b2d7225d..00000000 --- a/listener/tun/device/device_no_gvisor.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build no_gvisor - -package device - -// Device is the interface that implemented by network layer devices (e.g. tun), -// and easy to use as stack.LinkEndpoint. -type Device interface { - - // Name returns the current name of the device. - Name() string - - // Type returns the driver type of the device. - Type() string - - // Read packets from tun device - Read(packet []byte) (int, error) - - // Write packets to tun device - Write(packet []byte) (int, error) - - // Close stops and closes the device. - Close() error - - // UseEndpoint work for gVisor stack - UseEndpoint() error - - // UseIOBased work for other ip stack - UseIOBased() error -} diff --git a/listener/tun/device/fdbased/fd.go b/listener/tun/device/fdbased/fd.go deleted file mode 100644 index 20ae3489..00000000 --- a/listener/tun/device/fdbased/fd.go +++ /dev/null @@ -1,5 +0,0 @@ -package fdbased - -const Driver = "fd" - -const defaultMTU = 1500 diff --git a/listener/tun/device/fdbased/fd_unix.go b/listener/tun/device/fdbased/fd_unix.go deleted file mode 100644 index 63f6e839..00000000 --- a/listener/tun/device/fdbased/fd_unix.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build !windows - -package fdbased - -import ( - "fmt" - "strconv" - - "github.com/Dreamacro/clash/listener/tun/device" - - "golang.org/x/sys/unix" -) - -func Open(name string, mtu uint32) (device.Device, error) { - fd, err := strconv.Atoi(name) - if err != nil { - return nil, fmt.Errorf("cannot open fd: %s", name) - } - if mtu == 0 { - mtu = defaultMTU - } - return open(fd, mtu) -} - -func (f *FD) Type() string { - return Driver -} - -func (f *FD) Name() string { - return strconv.Itoa(f.fd) -} - -func (f *FD) Close() error { - err := unix.Close(f.fd) - if f.file != nil { - _ = f.file.Close() - } - return err -} - -func (f *FD) UseEndpoint() error { - return f.useEndpoint() -} - -func (f *FD) UseIOBased() error { - return f.useIOBased() -} - -var _ device.Device = (*FD)(nil) diff --git a/listener/tun/device/fdbased/fd_unix_gvisor.go b/listener/tun/device/fdbased/fd_unix_gvisor.go deleted file mode 100644 index cc327c77..00000000 --- a/listener/tun/device/fdbased/fd_unix_gvisor.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build !no_gvisor - -package fdbased - -import ( - "gvisor.dev/gvisor/pkg/tcpip/stack" - "os" -) - -type FD struct { - stack.LinkEndpoint - - fd int - mtu uint32 - - file *os.File -} diff --git a/listener/tun/device/fdbased/fd_unix_no_gvisor.go b/listener/tun/device/fdbased/fd_unix_no_gvisor.go deleted file mode 100644 index a4ecf8b9..00000000 --- a/listener/tun/device/fdbased/fd_unix_no_gvisor.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build no_gvisor - -package fdbased - -import ( - "os" -) - -type FD struct { - fd int - mtu uint32 - - file *os.File -} diff --git a/listener/tun/device/fdbased/fd_windows.go b/listener/tun/device/fdbased/fd_windows.go deleted file mode 100644 index a04f3356..00000000 --- a/listener/tun/device/fdbased/fd_windows.go +++ /dev/null @@ -1,11 +0,0 @@ -package fdbased - -import ( - "errors" - - "github.com/Dreamacro/clash/listener/tun/device" -) - -func Open(name string, mtu uint32) (device.Device, error) { - return nil, errors.New("not supported") -} diff --git a/listener/tun/device/fdbased/open_linux.go b/listener/tun/device/fdbased/open_linux.go deleted file mode 100644 index 0df81ff3..00000000 --- a/listener/tun/device/fdbased/open_linux.go +++ /dev/null @@ -1,27 +0,0 @@ -package fdbased - -import ( - "github.com/Dreamacro/clash/listener/tun/device" -) - -func open(fd int, mtu uint32) (device.Device, error) { - f := &FD{fd: fd, mtu: mtu} - - return f, nil -} - -func (f *FD) useEndpoint() error { - return f.newLinuxEp() -} - -func (f *FD) useIOBased() error { - return nil -} - -func (f *FD) Read(packet []byte) (int, error) { - return f.read(packet) -} - -func (f *FD) Write(packet []byte) (int, error) { - return f.write(packet) -} diff --git a/listener/tun/device/fdbased/open_linux_gvisor.go b/listener/tun/device/fdbased/open_linux_gvisor.go deleted file mode 100644 index dc250692..00000000 --- a/listener/tun/device/fdbased/open_linux_gvisor.go +++ /dev/null @@ -1,45 +0,0 @@ -//go:build !no_gvisor && (linux || android) - -package fdbased - -import ( - "fmt" - "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" - "gvisor.dev/gvisor/pkg/tcpip/link/rawfile" -) - -func (f *FD) newLinuxEp() error { - ep, err := fdbased.New(&fdbased.Options{ - FDs: []int{f.fd}, - MTU: f.mtu, - // TUN only, ignore ethernet header. - EthernetHeader: false, - }) - if err != nil { - return fmt.Errorf("create endpoint: %w", err) - } - f.LinkEndpoint = ep - return nil -} - -func (f *FD) read(packet []byte) (int, error) { - n, gvErr := rawfile.BlockingRead(f.fd, packet) - if gvErr != nil { - return 0, fmt.Errorf("read error: %s", gvErr.String()) - } - - return n, nil -} - -func (f *FD) write(packet []byte) (int, error) { - n := len(packet) - if n == 0 { - return 0, nil - } - - gvErr := rawfile.NonBlockingWrite(f.fd, packet) - if gvErr != nil { - return 0, fmt.Errorf("write error: %s", gvErr.String()) - } - return n, nil -} diff --git a/listener/tun/device/fdbased/open_linux_no_gvisor.go b/listener/tun/device/fdbased/open_linux_no_gvisor.go deleted file mode 100644 index ec76d991..00000000 --- a/listener/tun/device/fdbased/open_linux_no_gvisor.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build no_gvisor - -package fdbased - -import ( - "fmt" -) - -func (f *FD) newLinuxEp() error { - return fmt.Errorf("unsupported gvisor on the build") -} - -func (f *FD) read(packet []byte) (int, error) { - return 0, fmt.Errorf("unsupported gvisor on the build") -} - -func (f *FD) write(packet []byte) (int, error) { - return 0, fmt.Errorf("unsupported gvisor on the build") -} diff --git a/listener/tun/device/fdbased/open_others.go b/listener/tun/device/fdbased/open_others.go deleted file mode 100644 index e5c98276..00000000 --- a/listener/tun/device/fdbased/open_others.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build !linux && !windows - -package fdbased - -import ( - "fmt" - "os" - - "github.com/Dreamacro/clash/listener/tun/device" -) - -func open(fd int, mtu uint32) (device.Device, error) { - f := &FD{fd: fd, mtu: mtu} - - return f, nil -} - -func (f *FD) useEndpoint() error { - return f.newEpOther() -} - -func (f *FD) useIOBased() error { - f.file = os.NewFile(uintptr(f.fd), f.Name()) - if f.file == nil { - return fmt.Errorf("create IOBased failed, can not open file: %s", f.Name()) - } - return nil -} - -func (f *FD) Read(packet []byte) (int, error) { - return f.file.Read(packet) -} - -func (f *FD) Write(packet []byte) (int, error) { - return f.file.Write(packet) -} diff --git a/listener/tun/device/fdbased/open_others_gvisor.go b/listener/tun/device/fdbased/open_others_gvisor.go deleted file mode 100644 index 9cb00511..00000000 --- a/listener/tun/device/fdbased/open_others_gvisor.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build !no_gvisor && !linux && !windows - -package fdbased - -import ( - "fmt" - "os" - - "github.com/Dreamacro/clash/listener/tun/device/iobased" -) - -func (f *FD) newEpOther() error { - ep, err := iobased.New(os.NewFile(uintptr(f.fd), f.Name()), f.mtu, 0) - if err != nil { - return fmt.Errorf("create endpoint: %w", err) - } - f.LinkEndpoint = ep - return nil -} diff --git a/listener/tun/device/fdbased/open_others_no_gvisor.go b/listener/tun/device/fdbased/open_others_no_gvisor.go deleted file mode 100644 index ed616f76..00000000 --- a/listener/tun/device/fdbased/open_others_no_gvisor.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build no_gvisor && !linux && !windows - -package fdbased - -import ( - "fmt" -) - -func (f *FD) newEpOther() error { - return fmt.Errorf("unsupported gvisor on the build") -} diff --git a/listener/tun/device/iobased/endpoint.go b/listener/tun/device/iobased/endpoint.go deleted file mode 100644 index 35eb74b3..00000000 --- a/listener/tun/device/iobased/endpoint.go +++ /dev/null @@ -1,166 +0,0 @@ -//go:build !no_gvisor - -// Package iobased provides the implementation of io.ReadWriter -// based data-link layer endpoints. -package iobased - -import ( - "context" - "errors" - "gvisor.dev/gvisor/pkg/bufferv2" - "io" - "sync" - - "github.com/Dreamacro/clash/common/pool" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/link/channel" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -const ( - // Queue length for outbound packet, arriving for read. Overflow - // causes packet drops. - defaultOutQueueLen = 1 << 10 -) - -// Endpoint implements the interface of stack.LinkEndpoint from io.ReadWriter. -type Endpoint struct { - *channel.Endpoint - - // rw is the io.ReadWriter for reading and writing packets. - rw io.ReadWriter - - // mtu (maximum transmission unit) is the maximum size of a packet. - mtu uint32 - - // offset can be useful when perform TUN device I/O with TUN_PI enabled. - offset int - - // once is used to perform the init action once when attaching. - once sync.Once - - // wg keeps track of running goroutines. - wg sync.WaitGroup -} - -// New returns stack.LinkEndpoint(.*Endpoint) and error. -func New(rw io.ReadWriter, mtu uint32, offset int) (*Endpoint, error) { - if mtu == 0 { - return nil, errors.New("MTU size is zero") - } - - if rw == nil { - return nil, errors.New("RW interface is nil") - } - - if offset < 0 { - return nil, errors.New("offset must be non-negative") - } - - return &Endpoint{ - Endpoint: channel.New(defaultOutQueueLen, mtu, ""), - rw: rw, - mtu: mtu, - offset: offset, - }, nil -} - -func (e *Endpoint) Wait() { - e.wg.Wait() -} - -// Attach launches the goroutine that reads packets from io.Reader and -// dispatches them via the provided dispatcher. -func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) { - e.Endpoint.Attach(dispatcher) - e.once.Do(func() { - ctx, cancel := context.WithCancel(context.Background()) - e.wg.Add(2) - go func() { - e.outboundLoop(ctx) - e.wg.Done() - }() - go func() { - e.dispatchLoop(cancel) - e.wg.Done() - }() - }) -} - -// dispatchLoop dispatches packets to upper layer. -func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) { - // Call cancel() to ensure (*Endpoint).outboundLoop(context.Context) exits - // gracefully after (*Endpoint).dispatchLoop(context.CancelFunc) returns. - defer cancel() - - mtu := int(e.mtu) - for { - data := pool.Get(mtu) - - n, err := e.rw.Read(data) - if err != nil { - _ = pool.Put(data) - break - } - - if n == 0 || n > mtu { - _ = pool.Put(data) - continue - } - - if !e.IsAttached() { - _ = pool.Put(data) - continue /* unattached, drop packet */ - } - - var p tcpip.NetworkProtocolNumber - switch header.IPVersion(data) { - case header.IPv4Version: - p = header.IPv4ProtocolNumber - case header.IPv6Version: - p = header.IPv6ProtocolNumber - default: - _ = pool.Put(data) - continue - } - - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Payload: bufferv2.MakeWithData(data), - OnRelease: func() { - _ = pool.Put(data) - }, - }) - - e.InjectInbound(p, pkt) - - pkt.DecRef() - } -} - -// outboundLoop reads outbound packets from channel, and then it calls -// writePacket to send those packets back to lower layer. -func (e *Endpoint) outboundLoop(ctx context.Context) { - for { - pkt := e.ReadContext(ctx) - if pkt == nil { - break - } - e.writePacket(pkt) - } -} - -// writePacket writes outbound packets to the io.Writer. -func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error { - pktView := pkt.ToView() - - defer func() { - pktView.Release() - pkt.DecRef() - }() - - if _, err := e.rw.Write(pktView.AsSlice()); err != nil { - return &tcpip.ErrInvalidEndpointState{} - } - return nil -} diff --git a/listener/tun/device/iobased/iobased.go b/listener/tun/device/iobased/iobased.go deleted file mode 100644 index c16ee55c..00000000 --- a/listener/tun/device/iobased/iobased.go +++ /dev/null @@ -1 +0,0 @@ -package iobased diff --git a/listener/tun/device/tun/driver/amd64/wintun.dll b/listener/tun/device/tun/driver/amd64/wintun.dll deleted file mode 100755 index aee04e77..00000000 Binary files a/listener/tun/device/tun/driver/amd64/wintun.dll and /dev/null differ diff --git a/listener/tun/device/tun/driver/arm/wintun.dll b/listener/tun/device/tun/driver/arm/wintun.dll deleted file mode 100755 index 0017794f..00000000 Binary files a/listener/tun/device/tun/driver/arm/wintun.dll and /dev/null differ diff --git a/listener/tun/device/tun/driver/arm64/wintun.dll b/listener/tun/device/tun/driver/arm64/wintun.dll deleted file mode 100755 index dc4e4aee..00000000 Binary files a/listener/tun/device/tun/driver/arm64/wintun.dll and /dev/null differ diff --git a/listener/tun/device/tun/driver/dll_windows.go b/listener/tun/device/tun/driver/dll_windows.go deleted file mode 100644 index 5f4a5dae..00000000 --- a/listener/tun/device/tun/driver/dll_windows.go +++ /dev/null @@ -1,233 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package driver - -import ( - "fmt" - "runtime" - "sync" - "sync/atomic" - "syscall" - "unsafe" - - "github.com/Dreamacro/clash/log" - - "golang.org/x/sys/windows" - "golang.zx2c4.com/wireguard/windows/driver/memmod" -) - -//go:linkname modwintun golang.zx2c4.com/wintun.modwintun - -//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter - -//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter - -//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter - -//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver - -//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID - -//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion - -//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket - -//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession - -//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent - -//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket - -//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket - -//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket - -//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession - -var ( - modwintun *lazyDLL - procWintunCreateAdapter *lazyProc - procWintunOpenAdapter *lazyProc - procWintunCloseAdapter *lazyProc - procWintunDeleteDriver *lazyProc - procWintunGetAdapterLUID *lazyProc - procWintunGetRunningDriverVersion *lazyProc - procWintunAllocateSendPacket *lazyProc - procWintunEndSession *lazyProc - procWintunGetReadWaitEvent *lazyProc - procWintunReceivePacket *lazyProc - procWintunReleaseReceivePacket *lazyProc - procWintunSendPacket *lazyProc - procWintunStartSession *lazyProc -) - -type loggerLevel int - -const ( - logInfo loggerLevel = iota - logWarn - logErr -) - -func init() { - modwintun = newLazyDLL("wintun.dll", setupLogger) - procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter") - procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter") - procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter") - procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver") - procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID") - procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion") - procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket") - procWintunEndSession = modwintun.NewProc("WintunEndSession") - procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent") - procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket") - procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket") - procWintunSendPacket = modwintun.NewProc("WintunSendPacket") - procWintunStartSession = modwintun.NewProc("WintunStartSession") -} - -func InitWintun() (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("init wintun.dll error: %v", r) - } - }() - - if err = modwintun.Load(); err != nil { - return - } - - procWintunCreateAdapter.Addr() - procWintunOpenAdapter.Addr() - procWintunCloseAdapter.Addr() - procWintunDeleteDriver.Addr() - procWintunGetAdapterLUID.Addr() - procWintunGetRunningDriverVersion.Addr() - procWintunAllocateSendPacket.Addr() - procWintunEndSession.Addr() - procWintunGetReadWaitEvent.Addr() - procWintunReceivePacket.Addr() - procWintunReleaseReceivePacket.Addr() - procWintunSendPacket.Addr() - procWintunStartSession.Addr() - - return -} - -func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL { - return &lazyDLL{Name: name, onLoad: onLoad} -} - -func logMessage(level loggerLevel, _ uint64, msg *uint16) int { - switch level { - case logInfo: - log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg)) - case logWarn: - log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg)) - case logErr: - log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg)) - default: - log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg)) - } - return 0 -} - -func setupLogger(dll *lazyDLL) { - var callback uintptr - if runtime.GOARCH == "386" { - callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int { - return logMessage(level, 0, msg) - }) - } else if runtime.GOARCH == "arm" { - callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int { - return logMessage(level, 0, msg) - }) - } else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { - callback = windows.NewCallback(logMessage) - } - _, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback) -} - -func (d *lazyDLL) NewProc(name string) *lazyProc { - return &lazyProc{dll: d, Name: name} -} - -type lazyProc struct { - Name string - mu sync.Mutex - dll *lazyDLL - addr uintptr -} - -func (p *lazyProc) Find() error { - if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil { - return nil - } - p.mu.Lock() - defer p.mu.Unlock() - if p.addr != 0 { - return nil - } - - err := p.dll.Load() - if err != nil { - return fmt.Errorf("error loading DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err) - } - addr, err := p.nameToAddr() - if err != nil { - return fmt.Errorf("error getting %s address: %w", p.Name, err) - } - - atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr)) - return nil -} - -func (p *lazyProc) Addr() uintptr { - err := p.Find() - if err != nil { - panic(err) - } - return p.addr -} - -func (p *lazyProc) Load() error { - return p.dll.Load() -} - -type lazyDLL struct { - Name string - Base windows.Handle - mu sync.Mutex - module *memmod.Module - onLoad func(d *lazyDLL) -} - -func (d *lazyDLL) Load() error { - if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil { - return nil - } - d.mu.Lock() - defer d.mu.Unlock() - if d.module != nil { - return nil - } - - module, err := memmod.LoadLibrary(dllContent) - if err != nil { - return fmt.Errorf("unable to load library: %w", err) - } - d.Base = windows.Handle(module.BaseAddr()) - - atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module)) - if d.onLoad != nil { - d.onLoad(d) - } - return nil -} - -func (p *lazyProc) nameToAddr() (uintptr, error) { - return p.dll.module.ProcAddressByName(p.Name) -} diff --git a/listener/tun/device/tun/driver/dll_windows_386.go b/listener/tun/device/tun/driver/dll_windows_386.go deleted file mode 100644 index 12ace4e8..00000000 --- a/listener/tun/device/tun/driver/dll_windows_386.go +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package driver - -import ( - _ "embed" -) - -//go:embed x86/wintun.dll -var dllContent []byte diff --git a/listener/tun/device/tun/driver/dll_windows_amd64.go b/listener/tun/device/tun/driver/dll_windows_amd64.go deleted file mode 100644 index 58f3e01e..00000000 --- a/listener/tun/device/tun/driver/dll_windows_amd64.go +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package driver - -import ( - _ "embed" -) - -//go:embed amd64/wintun.dll -var dllContent []byte diff --git a/listener/tun/device/tun/driver/dll_windows_arm.go b/listener/tun/device/tun/driver/dll_windows_arm.go deleted file mode 100644 index bb941068..00000000 --- a/listener/tun/device/tun/driver/dll_windows_arm.go +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package driver - -import ( - _ "embed" -) - -//go:embed arm/wintun.dll -var dllContent []byte diff --git a/listener/tun/device/tun/driver/dll_windows_arm64.go b/listener/tun/device/tun/driver/dll_windows_arm64.go deleted file mode 100644 index 05bd1cbd..00000000 --- a/listener/tun/device/tun/driver/dll_windows_arm64.go +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package driver - -import ( - _ "embed" -) - -//go:embed arm64/wintun.dll -var dllContent []byte diff --git a/listener/tun/device/tun/driver/package_info.go b/listener/tun/device/tun/driver/package_info.go deleted file mode 100644 index 0c1bf7e4..00000000 --- a/listener/tun/device/tun/driver/package_info.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build windows - -// https://git.zx2c4.com/wireguard-go/tree/tun/wintun - -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. - */ - -package driver diff --git a/listener/tun/device/tun/driver/x86/wintun.dll b/listener/tun/device/tun/driver/x86/wintun.dll deleted file mode 100755 index 2ab97dba..00000000 Binary files a/listener/tun/device/tun/driver/x86/wintun.dll and /dev/null differ diff --git a/listener/tun/device/tun/tun.go b/listener/tun/device/tun/tun.go deleted file mode 100644 index bf04f289..00000000 --- a/listener/tun/device/tun/tun.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package tun provides TUN which implemented device.Device interface. -package tun - -import ( - "github.com/Dreamacro/clash/listener/tun/device" -) - -const Driver = "tun" - -func (t *TUN) Type() string { - return Driver -} - -var _ device.Device = (*TUN)(nil) diff --git a/listener/tun/device/tun/tun_netstack.go b/listener/tun/device/tun/tun_netstack.go deleted file mode 100644 index 89dab248..00000000 --- a/listener/tun/device/tun/tun_netstack.go +++ /dev/null @@ -1,145 +0,0 @@ -//go:build linux - -package tun - -import ( - "fmt" - "unsafe" - - "github.com/Dreamacro/clash/listener/tun/device" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" - "gvisor.dev/gvisor/pkg/tcpip/link/rawfile" - "gvisor.dev/gvisor/pkg/tcpip/link/tun" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -type TUN struct { - stack.LinkEndpoint - - fd int - mtu uint32 - name string -} - -func Open(name string, mtu uint32) (device.Device, error) { - t := &TUN{name: name, mtu: mtu} - - if len(t.name) >= unix.IFNAMSIZ { - return nil, fmt.Errorf("interface name too long: %s", t.name) - } - - fd, err := tun.Open(t.name) - if err != nil { - return nil, fmt.Errorf("create tun: %w", err) - } - t.fd = fd - - if t.mtu > 0 { - if err := setMTU(t.name, t.mtu); err != nil { - return nil, fmt.Errorf("set mtu: %w", err) - } - } - - _mtu, err := rawfile.GetMTU(t.name) - if err != nil { - return nil, fmt.Errorf("get mtu: %w", err) - } - t.mtu = _mtu - - return t, nil -} - -func (t *TUN) Name() string { - return t.name -} - -func (t *TUN) Read(packet []byte) (int, error) { - n, gvErr := rawfile.BlockingRead(t.fd, packet) - if gvErr != nil { - return 0, fmt.Errorf("read error: %s", gvErr.String()) - } - - return n, nil -} - -func (t *TUN) Write(packet []byte) (int, error) { - n := len(packet) - if n == 0 { - return 0, nil - } - - gvErr := rawfile.NonBlockingWrite(t.fd, packet) - if gvErr != nil { - return 0, fmt.Errorf("write error: %s", gvErr.String()) - } - return n, nil -} - -func (t *TUN) Close() error { - return unix.Close(t.fd) -} - -func (t *TUN) UseEndpoint() error { - ep, err := fdbased.New(&fdbased.Options{ - FDs: []int{t.fd}, - MTU: t.mtu, - // TUN only, ignore ethernet header. - EthernetHeader: false, - // SYS_READV support only for TUN fd. - PacketDispatchMode: fdbased.Readv, - // TAP/TUN fd's are not sockets and using the WritePackets calls results - // in errors as it always defaults to using SendMMsg which is not supported - // for tap/tun device fds. - // - // This CL changes WritePackets to gracefully degrade to using writev instead - // of sendmmsg if the underlying fd is not a socket. - // - // Fixed: https://github.com/google/gvisor/commit/f33d034fecd7723a1e560ccc62aeeba328454fd0 - MaxSyscallHeaderBytes: 0x00, - }) - if err != nil { - return fmt.Errorf("create endpoint: %w", err) - } - t.LinkEndpoint = ep - return nil -} - -func (t *TUN) UseIOBased() error { - return nil -} - -// Ref: wireguard tun/tun_linux.go setMTU. -func setMTU(name string, n uint32) error { - // open datagram socket - fd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) - if err != nil { - return err - } - - defer unix.Close(fd) - - const ifReqSize = unix.IFNAMSIZ + 64 - - // do ioctl call - var ifr [ifReqSize]byte - copy(ifr[:], name) - *(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = n - _, _, errno := unix.Syscall( - unix.SYS_IOCTL, - uintptr(fd), - uintptr(unix.SIOCSIFMTU), - uintptr(unsafe.Pointer(&ifr[0])), - ) - - if errno != 0 { - return fmt.Errorf("failed to set MTU: %w", errno) - } - - return nil -} diff --git a/listener/tun/device/tun/tun_wireguard.go b/listener/tun/device/tun/tun_wireguard.go deleted file mode 100644 index 828dd56d..00000000 --- a/listener/tun/device/tun/tun_wireguard.go +++ /dev/null @@ -1,97 +0,0 @@ -//go:build !linux - -package tun - -import ( - "fmt" - "os" - "runtime" - - "github.com/Dreamacro/clash/listener/tun/device" - - "golang.zx2c4.com/wireguard/tun" -) - -func Open(name string, mtu uint32) (_ device.Device, err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("open tun: %v", r) - } - }() - - t := &TUN{ - name: name, - mtu: mtu, - offset: offset, - } - - forcedMTU := defaultMTU - if t.mtu > 0 { - forcedMTU = int(t.mtu) - } - - nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way - - // retry if abnormal exit at last time on Windows - if err != nil && runtime.GOOS == "windows" && os.IsExist(err) { - nt, err = newDevice(t.name, forcedMTU) - } - - if err != nil { - return nil, fmt.Errorf("create tun: %w", err) - } - - t.nt = nt.(*tun.NativeTun) - - tunMTU, err := nt.MTU() - if err != nil { - return nil, fmt.Errorf("get mtu: %w", err) - } - t.mtu = uint32(tunMTU) - - if t.offset > 0 { - t.cache = make([]byte, 65535) - } - - return t, nil -} - -func (t *TUN) Read(packet []byte) (int, error) { - if t.offset == 0 { - return t.nt.Read(packet, t.offset) - } - - n, err := t.nt.Read(t.cache, t.offset) - - copy(packet, t.cache[t.offset:t.offset+n]) - - return n, err -} - -func (t *TUN) Write(packet []byte) (int, error) { - if t.offset == 0 { - return t.nt.Write(packet, t.offset) - } - - packet = append(t.cache[:t.offset], packet...) - - return t.nt.Write(packet, t.offset) -} - -func (t *TUN) Close() error { - defer closeIO(t) - return t.nt.Close() -} - -func (t *TUN) Name() string { - name, _ := t.nt.Name() - return name -} - -func (t *TUN) UseEndpoint() error { - return newEq(t) -} - -func (t *TUN) UseIOBased() error { - return nil -} diff --git a/listener/tun/device/tun/tun_wireguard_gvisor.go b/listener/tun/device/tun/tun_wireguard_gvisor.go deleted file mode 100644 index 400b4219..00000000 --- a/listener/tun/device/tun/tun_wireguard_gvisor.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !linux && !no_gvisor - -package tun - -import ( - "fmt" - "github.com/Dreamacro/clash/listener/tun/device/iobased" - "golang.zx2c4.com/wireguard/tun" -) - -type TUN struct { - *iobased.Endpoint - nt *tun.NativeTun - mtu uint32 - name string - offset int - - cache []byte -} - -func closeIO(t *TUN) { - if t.Endpoint != nil { - t.Endpoint.Close() - } -} - -func newEq(t *TUN) error { - ep, err := iobased.New(t, t.mtu, t.offset) - if err != nil { - return fmt.Errorf("create endpoint: %w", err) - } - t.Endpoint = ep - return nil -} diff --git a/listener/tun/device/tun/tun_wireguard_no_gvisor.go b/listener/tun/device/tun/tun_wireguard_no_gvisor.go deleted file mode 100644 index c55487c7..00000000 --- a/listener/tun/device/tun/tun_wireguard_no_gvisor.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build !linux && no_gvisor - -package tun - -import ( - "golang.zx2c4.com/wireguard/tun" -) - -type TUN struct { - nt *tun.NativeTun - mtu uint32 - name string - offset int - - cache []byte -} - -func closeIO(t *TUN) { - -} - -func newEq(t *TUN) error { - return nil -} diff --git a/listener/tun/device/tun/tun_wireguard_unix.go b/listener/tun/device/tun/tun_wireguard_unix.go deleted file mode 100644 index b6d3addf..00000000 --- a/listener/tun/device/tun/tun_wireguard_unix.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build !linux && !windows - -package tun - -import "golang.zx2c4.com/wireguard/tun" - -const ( - offset = 4 /* 4 bytes TUN_PI */ - defaultMTU = 1500 -) - -func newDevice(name string, mtu int) (tun.Device, error) { - return tun.CreateTUN(name, mtu) -} diff --git a/listener/tun/device/tun/tun_wireguard_windows.go b/listener/tun/device/tun/tun_wireguard_windows.go deleted file mode 100644 index c5c16a1d..00000000 --- a/listener/tun/device/tun/tun_wireguard_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -package tun - -import ( - "github.com/Dreamacro/clash/listener/tun/device/tun/driver" - - "golang.org/x/sys/windows" - "golang.zx2c4.com/wireguard/tun" -) - -const ( - offset = 0 - defaultMTU = 0 /* auto */ -) - -func init() { - guid, _ := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}") - - tun.WintunTunnelType = "Meta" - tun.WintunStaticRequestedGUID = &guid -} - -func (t *TUN) LUID() uint64 { - return t.nt.LUID() -} - -func newDevice(name string, mtu int) (nt tun.Device, err error) { - if err = driver.InitWintun(); err != nil { - return - } - - return tun.CreateTUN(name, mtu) -} diff --git a/listener/tun/ipstack/commons/auto_linux.go b/listener/tun/ipstack/commons/auto_linux.go deleted file mode 100644 index 36be5a66..00000000 --- a/listener/tun/ipstack/commons/auto_linux.go +++ /dev/null @@ -1,90 +0,0 @@ -package commons - -import ( - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/iface" - "github.com/Dreamacro/clash/log" - "github.com/vishvananda/netlink" - "go.uber.org/atomic" - "time" -) - -var ( - monitorStarted = atomic.NewBool(false) - monitorStop = make(chan struct{}, 2) -) - -func StartDefaultInterfaceChangeMonitor() { - go func() { - if monitorStarted.Load() { - return - } - monitorStarted.Store(true) - - done := make(chan struct{}) - ch := make(chan netlink.RouteUpdate, 2) - err := netlink.RouteSubscribe(ch, done) - if err != nil { - log.Warnln("[TUN] auto detect interface fail: %s", err) - return - } - log.Debugln("[TUN] start auto detect interface monitor") - defer func() { - close(done) - monitorStarted.Store(false) - log.Debugln("[TUN] stop auto detect interface monitor") - }() - - select { - case <-monitorStop: - default: - } - - for { - select { - case <-monitorStop: - return - case <-ch: - } - - interfaceName, err := GetAutoDetectInterface() - if err != nil { - t := time.NewTicker(2 * time.Second) - for { - select { - case ch <- <-ch: - break - case <-t.C: - interfaceName, err = GetAutoDetectInterface() - if err != nil { - continue - } - } - break - } - t.Stop() - } - - if err != nil { - log.Debugln("[TUN] detect interface: %s", err) - continue - } - - old := dialer.DefaultInterface.Load() - if interfaceName == old { - continue - } - - dialer.DefaultInterface.Store(interfaceName) - iface.FlushCache() - - log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) - } - }() -} - -func StopDefaultInterfaceChangeMonitor() { - if monitorStarted.Load() { - monitorStop <- struct{}{} - } -} diff --git a/listener/tun/ipstack/commons/auto_others.go b/listener/tun/ipstack/commons/auto_others.go deleted file mode 100644 index 23772c5e..00000000 --- a/listener/tun/ipstack/commons/auto_others.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build !linux - -package commons - -import ( - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/iface" - "github.com/Dreamacro/clash/log" - "go.uber.org/atomic" - "time" -) - -var ( - monitorDuration = 3 * time.Second - monitorStarted = atomic.NewBool(false) - monitorStop = make(chan struct{}, 2) -) - -func StartDefaultInterfaceChangeMonitor() { - go func() { - if monitorStarted.Load() { - return - } - monitorStarted.Store(true) - t := time.NewTicker(monitorDuration) - log.Debugln("[TUN] start auto detect interface monitor") - defer func() { - monitorStarted.Store(false) - t.Stop() - log.Debugln("[TUN] stop auto detect interface monitor") - }() - - select { - case <-monitorStop: - default: - } - - for { - select { - case <-t.C: - interfaceName, err := GetAutoDetectInterface() - if err != nil { - log.Warnln("[TUN] default interface monitor err: %v", err) - continue - } - - old := dialer.DefaultInterface.Load() - if interfaceName == old { - continue - } - - dialer.DefaultInterface.Store(interfaceName) - iface.FlushCache() - - log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) - case <-monitorStop: - break - } - } - }() -} - -func StopDefaultInterfaceChangeMonitor() { - if monitorStarted.Load() { - monitorStop <- struct{}{} - } -} diff --git a/listener/tun/ipstack/commons/dns.go b/listener/tun/ipstack/commons/dns.go deleted file mode 100644 index 06049708..00000000 --- a/listener/tun/ipstack/commons/dns.go +++ /dev/null @@ -1,38 +0,0 @@ -package commons - -import ( - "net/netip" - "time" - - "github.com/Dreamacro/clash/component/resolver" - D "github.com/miekg/dns" -) - -const DefaultDnsReadTimeout = time.Second * 10 - -func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool { - for _, addrPort := range dnsAdds { - if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) { - return true - } - } - return false -} - -func RelayDnsPacket(payload []byte) ([]byte, error) { - msg := &D.Msg{} - if err := msg.Unpack(payload); err != nil { - return nil, err - } - - r, err := resolver.ServeMsg(msg) - if err != nil { - m := new(D.Msg) - m.SetRcode(msg, D.RcodeServerFailure) - return m.Pack() - } - - r.SetRcode(msg, r.Rcode) - r.Compress = true - return r.Pack() -} diff --git a/listener/tun/ipstack/commons/router.go b/listener/tun/ipstack/commons/router.go deleted file mode 100644 index 57c99502..00000000 --- a/listener/tun/ipstack/commons/router.go +++ /dev/null @@ -1,46 +0,0 @@ -package commons - -import ( - "fmt" - "github.com/Dreamacro/clash/log" - "net" - "time" -) - -var ( - defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"} -) - -func ipv4MaskString(bits int) string { - m := net.CIDRMask(bits, 32) - if len(m) != 4 { - panic("ipv4Mask: len must be 4 bytes") - } - - return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) -} - -func WaitForTunClose(deviceName string) { - t := time.NewTicker(600 * time.Millisecond) - defer t.Stop() - log.Debugln("[TUN] waiting for device close") - for { - <-t.C - interfaces, err := net.Interfaces() - if err != nil { - break - } - - found := false - for i := len(interfaces) - 1; i > -1; i-- { - if interfaces[i].Name == deviceName { - found = true - break - } - } - - if !found { - break - } - } -} diff --git a/listener/tun/ipstack/commons/router_android.go b/listener/tun/ipstack/commons/router_android.go deleted file mode 100644 index 139dbca9..00000000 --- a/listener/tun/ipstack/commons/router_android.go +++ /dev/null @@ -1,140 +0,0 @@ -package commons - -import ( - "fmt" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/Dreamacro/clash/log" - "github.com/vishvananda/netlink" - "net" - "net/netip" -) - -func GetAutoDetectInterface() (ifn string, err error) { - routes, err := netlink.RouteGetWithOptions( - net.ParseIP("1.1.1.1"), - &netlink.RouteGetOptions{ - Uid: &netlink.UID{Uid: 4294967295}, - }) - if err != nil { - return "", err - } - - for _, route := range routes { - if lk, err := netlink.LinkByIndex(route.LinkIndex); err == nil { - return lk.Attrs().Name, nil - } - } - - return "", fmt.Errorf("interface not found") -} - -func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { - var ( - interfaceName = dev.Name() - ip = addr.Masked().Addr().Next() - ) - - metaLink, err := netlink.LinkByName(interfaceName) - if err != nil { - return err - } - - naddr, err := netlink.ParseAddr(addr.String()) - if err != nil { - return err - } - - if err = netlink.AddrAdd(metaLink, naddr); err != nil && err.Error() != "file exists" { - return err - } - - if err = netlink.LinkSetUp(metaLink); err != nil { - return err - } - - if autoRoute { - err = configInterfaceRouting(metaLink.Attrs().Index, interfaceName, ip) - } - return err -} - -func configInterfaceRouting(index int, interfaceName string, ip netip.Addr) error { - const tableId = 1981801 - var pref = 9000 - - for _, route := range defaultRoutes { - _, ipn, err := net.ParseCIDR(route) - if err != nil { - return err - } - - if err := netlink.RouteAdd(&netlink.Route{ - LinkIndex: index, - Scope: netlink.SCOPE_LINK, - Protocol: 2, - Src: ip.AsSlice(), - Dst: ipn, - Table: tableId, - }); err != nil { - return err - } - } - - logIfErr := func(e error) { - if e != nil { - log.Warnln("[TOUTE] config route rule: %s", e) - } - } - - var r *netlink.Rule - r = netlink.NewRule() - r.Table = 254 - r.Priority = pref - logIfErr(netlink.RuleAdd(r)) - pref += 10 - - r = netlink.NewRule() - _, nl, _ := net.ParseCIDR("0.0.0.0/32") - r.Table = tableId - r.Priority = pref - r.Src = nl - r.IifName = "lo" - r.UID = netlink.NewRuleUIDRange(0, 4294967294) - logIfErr(netlink.RuleAdd(r)) - pref += 10 - - _, nl, _ = net.ParseCIDR(ip.String()) - r.Priority = pref - r.Src = nl - logIfErr(netlink.RuleAdd(r)) - pref += 10 - - r = netlink.NewRule() - r.Table = 254 - r.Priority = pref - r.IifName = interfaceName - r.SuppressPrefixlen = 0 - logIfErr(netlink.RuleAdd(r)) - pref += 10 - - r = netlink.NewRule() - r.Table = tableId - r.Priority = pref - r.IifName = "lo" - r.SuppressPrefixlen = 0 - r.Invert = true - logIfErr(netlink.RuleAdd(r)) - - return nil -} - -func CleanupRule() { - r := netlink.NewRule() - for i := 0; i < 5; i++ { - r.Priority = 9000 + i*10 - err := netlink.RuleDel(r) - if err != nil { - log.Warnln("[TOUTE] cleanup route rule: %s", err) - } - } -} diff --git a/listener/tun/ipstack/commons/router_darwin.go b/listener/tun/ipstack/commons/router_darwin.go deleted file mode 100644 index 3132fced..00000000 --- a/listener/tun/ipstack/commons/router_darwin.go +++ /dev/null @@ -1,65 +0,0 @@ -package commons - -import ( - "fmt" - "net/netip" - - "github.com/Dreamacro/clash/common/cmd" - "github.com/Dreamacro/clash/listener/tun/device" -) - -func GetAutoDetectInterface() (string, error) { - return cmd.ExecCmd("bash -c route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") -} - -func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { - if !addr.Addr().Is4() { - return fmt.Errorf("supported ipv4 only") - } - - var ( - interfaceName = dev.Name() - ip = addr.Masked().Addr().Next() - gw = ip.Next() - netmask = ipv4MaskString(addr.Bits()) - ) - - cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw) - - _, err := cmd.ExecCmd(cmdStr) - if err != nil { - return err - } - - _, err = cmd.ExecCmd(fmt.Sprintf("ipconfig set %s automatic-v6", interfaceName)) - if err != nil { - return err - } - - if autoRoute { - err = configInterfaceRouting(interfaceName, addr) - } - return err -} - -func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { - var ( - routes = append(defaultRoutes, addr.String()) - gateway = addr.Masked().Addr().Next() - ) - - for _, destination := range routes { - if _, err := cmd.ExecCmd(fmt.Sprintf("route add -net %s %s", destination, gateway)); err != nil { - return err - } - } - - return execRouterCmd("add", "-inet6", "2000::/3", interfaceName) -} - -func execRouterCmd(action, inet, route string, interfaceName string) error { - _, err := cmd.ExecCmd(fmt.Sprintf("route %s %s %s -interface %s", action, inet, route, interfaceName)) - return err -} - -func CleanupRule() {} diff --git a/listener/tun/ipstack/commons/router_linux.go b/listener/tun/ipstack/commons/router_linux.go deleted file mode 100644 index 37a92d2e..00000000 --- a/listener/tun/ipstack/commons/router_linux.go +++ /dev/null @@ -1,89 +0,0 @@ -//go:build !android - -package commons - -import ( - "fmt" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/vishvananda/netlink" - "net" - "net/netip" -) - -func GetAutoDetectInterface() (string, error) { - routes, err := netlink.RouteList(nil, netlink.FAMILY_V4) - if err != nil { - return "", err - } - - for _, route := range routes { - if route.Dst == nil { - lk, err := netlink.LinkByIndex(route.LinkIndex) - if err != nil { - return "", err - } - - if lk.Type() == "tuntap" { - continue - } - - return lk.Attrs().Name, nil - } - } - - return "", fmt.Errorf("interface not found") -} - -func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { - var ( - interfaceName = dev.Name() - ip = addr.Masked().Addr().Next() - ) - - metaLink, err := netlink.LinkByName(interfaceName) - if err != nil { - return err - } - - nlAddr, err := netlink.ParseAddr(addr.String()) - if err != nil { - return err - } - - if err = netlink.AddrAdd(metaLink, nlAddr); err != nil && err.Error() != "file exists" { - return err - } - - if err = netlink.LinkSetUp(metaLink); err != nil { - return err - } - - if autoRoute { - _ = configInterfaceRouting(metaLink.Attrs().Index, ip) - } - return nil -} - -func configInterfaceRouting(index int, ip netip.Addr) error { - for _, route := range defaultRoutes { - _, ipn, err := net.ParseCIDR(route) - if err != nil { - return err - } - - if err := netlink.RouteAdd(&netlink.Route{ - LinkIndex: index, - Scope: netlink.SCOPE_LINK, - Protocol: 2, - Src: ip.AsSlice(), - Dst: ipn, - Table: 254, - }); err != nil { - return err - } - } - - return nil -} - -func CleanupRule() {} diff --git a/listener/tun/ipstack/commons/router_others.go b/listener/tun/ipstack/commons/router_others.go deleted file mode 100644 index dfc409f4..00000000 --- a/listener/tun/ipstack/commons/router_others.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !darwin && !linux && !windows - -package commons - -import ( - "fmt" - "net/netip" - "runtime" - - "github.com/Dreamacro/clash/listener/tun/device" -) - -func GetAutoDetectInterface() (string, error) { - return "", fmt.Errorf("can not auto detect interface-name on this OS: %s, you must be detecting interface-name by manual", runtime.GOOS) -} - -func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error { - return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS) -} - -func CleanupRule() {} diff --git a/listener/tun/ipstack/commons/router_windows.go b/listener/tun/ipstack/commons/router_windows.go deleted file mode 100644 index 2abfa0ff..00000000 --- a/listener/tun/ipstack/commons/router_windows.go +++ /dev/null @@ -1,273 +0,0 @@ -package commons - -import ( - "errors" - "fmt" - "net/netip" - "time" - - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/Dreamacro/clash/listener/tun/device/tun" - "github.com/Dreamacro/clash/log" - - "golang.org/x/sys/windows" - "golang.zx2c4.com/wireguard/windows/services" - "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" -) - -var wintunInterfaceName string - -func GetAutoDetectInterface() (string, error) { - ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET)) - if err == nil { - return ifname, err - } - - return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6)) -} - -func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { - retryOnFailure := services.StartedAtBoot() - tryTimes := 0 - var err error -startOver: - if tryTimes > 0 { - log.Infoln("[TUN] retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err) - time.Sleep(time.Second) - retryOnFailure = retryOnFailure && tryTimes < 15 - } - tryTimes++ - - var ( - luid = winipcfg.LUID(dev.(*tun.TUN).LUID()) - ip = addr.Masked().Addr().Next() - gw = ip.Next() - addresses = []netip.Prefix{netip.PrefixFrom(ip, addr.Bits())} - dnsAddress = []netip.Addr{gw} - - family4 = winipcfg.AddressFamily(windows.AF_INET) - familyV6 = winipcfg.AddressFamily(windows.AF_INET6) - currentFamily = winipcfg.AddressFamily(windows.AF_INET6) - ) - - if addr.Addr().Is4() { - currentFamily = winipcfg.AddressFamily(windows.AF_INET) - } - - err = luid.FlushRoutes(windows.AF_INET6) - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return err - } - err = luid.FlushIPAddresses(windows.AF_INET6) - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return err - } - err = luid.FlushDNS(windows.AF_INET6) - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return err - } - err = luid.FlushDNS(windows.AF_INET) - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return err - } - - foundDefault4 := false - foundDefault6 := false - - if autoRoute { - var ( - allowedIPs []netip.Prefix - - // add default - routeArr = []string{"0.0.0.0/0"} - ) - - for _, route := range routeArr { - allowedIPs = append(allowedIPs, netip.MustParsePrefix(route)) - } - - estimatedRouteCount := len(allowedIPs) - routes := make(map[winipcfg.RouteData]bool, estimatedRouteCount) - - for _, allowedip := range allowedIPs { - route := winipcfg.RouteData{ - Destination: allowedip.Masked(), - Metric: 0, - } - if allowedip.Addr().Is4() { - if allowedip.Bits() == 0 { - foundDefault4 = true - } - route.NextHop = netip.IPv4Unspecified() - } else if allowedip.Addr().Is6() { - if allowedip.Bits() == 0 { - foundDefault6 = true - } - route.NextHop = netip.IPv6Unspecified() - } - routes[route] = true - } - - deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes)) - for route := range routes { - r := route - deduplicatedRoutes = append(deduplicatedRoutes, &r) - } - - // add gateway - deduplicatedRoutes = append(deduplicatedRoutes, &winipcfg.RouteData{ - Destination: addr.Masked(), - NextHop: gw, - Metric: 0, - }) - - err = luid.SetRoutesForFamily(currentFamily, deduplicatedRoutes) - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return fmt.Errorf("unable to set routes: %w", err) - } - } - - err = luid.SetIPAddressesForFamily(currentFamily, addresses) - if err == windows.ERROR_OBJECT_ALREADY_EXISTS { - cleanupAddressesOnDisconnectedInterfaces(currentFamily, addresses) - err = luid.SetIPAddressesForFamily(currentFamily, addresses) - } - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return fmt.Errorf("unable to set ips: %w", err) - } - - var ipif *winipcfg.MibIPInterfaceRow - ipif, err = luid.IPInterface(family4) - if err != nil { - return err - } - ipif.ForwardingEnabled = true - ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled - ipif.DadTransmits = 0 - ipif.ManagedAddressConfigurationSupported = false - ipif.OtherStatefulConfigurationSupported = false - if forceMTU > 0 { - ipif.NLMTU = uint32(forceMTU) - } - if foundDefault4 { - ipif.UseAutomaticMetric = false - ipif.Metric = 0 - } - err = ipif.Set() - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return fmt.Errorf("unable to set metric and MTU: %w", err) - } - - var ipif6 *winipcfg.MibIPInterfaceRow - ipif6, err = luid.IPInterface(familyV6) - if err != nil { - return err - } - ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled - ipif6.DadTransmits = 0 - ipif6.ManagedAddressConfigurationSupported = false - ipif6.OtherStatefulConfigurationSupported = false - if forceMTU > 0 { - ipif6.NLMTU = uint32(forceMTU) - } - if foundDefault6 { - ipif6.UseAutomaticMetric = false - ipif6.Metric = 0 - } - err = ipif6.Set() - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return fmt.Errorf("unable to set v6 metric and MTU: %w", err) - } - - err = luid.SetDNS(family4, dnsAddress, nil) - if err == windows.ERROR_NOT_FOUND && retryOnFailure { - goto startOver - } else if err != nil { - return fmt.Errorf("unable to set DNS %s %s: %w", dnsAddress[0].String(), "nil", err) - } - - wintunInterfaceName = dev.Name() - - return nil -} - -func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) { - if len(addresses) == 0 { - return - } - addrHash := make(map[netip.Addr]bool, len(addresses)) - for i := range addresses { - addrHash[addresses[i].Addr()] = true - } - interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) - if err != nil { - return - } - for _, iface := range interfaces { - if iface.OperStatus == winipcfg.IfOperStatusUp { - continue - } - for address := iface.FirstUnicastAddress; address != nil; address = address.Next { - if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] { - prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength)) - log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName()) - _ = iface.LUID.DeleteIPAddress(prefix) - } - } - } -} - -func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) { - interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways) - if err != nil { - return "", fmt.Errorf("get ethernet interface failure. %w", err) - } - - var destination netip.Prefix - if family == windows.AF_INET { - destination = netip.PrefixFrom(netip.IPv4Unspecified(), 0) - } else { - destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0) - } - - for _, iface := range interfaces { - if iface.OperStatus != winipcfg.IfOperStatusUp { - continue - } - - ifname := iface.FriendlyName() - - if wintunInterfaceName == ifname { - continue - } - - for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next { - nextHop := nnip.IpToAddr(gatewayAddress.Address.IP()) - - if _, err = iface.LUID.Route(destination, nextHop); err == nil { - return ifname, nil - } - } - } - - return "", errors.New("ethernet interface not found") -} - -func CleanupRule() {} diff --git a/listener/tun/ipstack/gvisor/adapter/adapter.go b/listener/tun/ipstack/gvisor/adapter/adapter.go deleted file mode 100644 index 08d0e780..00000000 --- a/listener/tun/ipstack/gvisor/adapter/adapter.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build !no_gvisor - -package adapter - -import ( - "net" - - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -// TCPConn implements the net.Conn interface. -type TCPConn interface { - net.Conn - - // ID returns the transport endpoint id of TCPConn. - ID() *stack.TransportEndpointID -} - -// UDPConn implements net.Conn and net.PacketConn. -type UDPConn interface { - net.Conn - net.PacketConn - - // ID returns the transport endpoint id of UDPConn. - ID() *stack.TransportEndpointID -} diff --git a/listener/tun/ipstack/gvisor/adapter/handler.go b/listener/tun/ipstack/gvisor/adapter/handler.go deleted file mode 100644 index 913922d6..00000000 --- a/listener/tun/ipstack/gvisor/adapter/handler.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !no_gvisor - -package adapter - -// Handler is a TCP/UDP connection handler that implements -// HandleTCPConn and HandleUDPConn methods. -type Handler interface { - HandleTCP(TCPConn) - HandleUDP(UDPConn) -} - -// TCPHandleFunc handles incoming TCP connection. -type TCPHandleFunc func(TCPConn) - -// UDPHandleFunc handles incoming UDP connection. -type UDPHandleFunc func(UDPConn) diff --git a/listener/tun/ipstack/gvisor/handler.go b/listener/tun/ipstack/gvisor/handler.go deleted file mode 100644 index ed119fc3..00000000 --- a/listener/tun/ipstack/gvisor/handler.go +++ /dev/null @@ -1,142 +0,0 @@ -//go:build !no_gvisor - -package gvisor - -import ( - "encoding/binary" - "net" - "net/netip" - "time" - - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - D "github.com/Dreamacro/clash/listener/tun/ipstack/commons" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter" - "github.com/Dreamacro/clash/transport/socks5" -) - -var _ adapter.Handler = (*gvHandler)(nil) - -type gvHandler struct { - gateway netip.Addr - dnsHijack []netip.AddrPort - - tcpIn chan<- C.ConnContext - udpIn chan<- *inbound.PacketAdapter -} - -func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) { - id := tunConn.ID() - - rAddr := &net.TCPAddr{ - IP: net.IP(id.LocalAddress), - Port: int(id.LocalPort), - Zone: "", - } - - rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort) - - if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) { - go func() { - buf := pool.Get(pool.UDPBufferSize) - defer func() { - _ = pool.Put(buf) - _ = tunConn.Close() - }() - - for { - if tunConn.SetReadDeadline(time.Now().Add(D.DefaultDnsReadTimeout)) != nil { - break - } - - length := uint16(0) - if err := binary.Read(tunConn, binary.BigEndian, &length); err != nil { - break - } - - if int(length) > len(buf) { - break - } - - n, err := tunConn.Read(buf[:length]) - if err != nil { - break - } - - msg, err := D.RelayDnsPacket(buf[:n]) - if err != nil { - break - } - - _, _ = tunConn.Write(msg) - } - }() - - return - } - - gh.tcpIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN) -} - -func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) { - id := tunConn.ID() - - rAddr := &net.UDPAddr{ - IP: net.IP(id.LocalAddress), - Port: int(id.LocalPort), - Zone: "", - } - - rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort) - - if rAddrPort.Addr() == gh.gateway { - _ = tunConn.Close() - return - } - - target := socks5.ParseAddrToSocksAddr(rAddr) - - go func() { - for { - buf := pool.Get(pool.UDPBufferSize) - - n, addr, err := tunConn.ReadFrom(buf) - if err != nil { - _ = pool.Put(buf) - break - } - - payload := buf[:n] - - if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) { - go func() { - defer func() { - _ = pool.Put(buf) - }() - - msg, err1 := D.RelayDnsPacket(payload) - if err1 != nil { - return - } - - _, _ = tunConn.WriteTo(msg, addr) - }() - - continue - } - - gvPacket := &packet{ - pc: tunConn, - rAddr: addr, - payload: payload, - } - - select { - case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN): - default: - } - } - }() -} diff --git a/listener/tun/ipstack/gvisor/nic.go b/listener/tun/ipstack/gvisor/nic.go deleted file mode 100644 index 77a1a349..00000000 --- a/listener/tun/ipstack/gvisor/nic.go +++ /dev/null @@ -1,60 +0,0 @@ -//go:build !no_gvisor - -package gvisor - -import ( - "fmt" - - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -const ( - // nicPromiscuousModeEnabled is the value used by stack to enable - // or disable NIC's promiscuous mode. - nicPromiscuousModeEnabled = true - - // nicSpoofingEnabled is the value used by stack to enable or disable - // NIC's spoofing. - nicSpoofingEnabled = true -) - -// withCreatingNIC creates NIC for stack. -func withCreatingNIC(nicID tcpip.NICID, ep stack.LinkEndpoint) option.Option { - return func(s *stack.Stack) error { - if err := s.CreateNICWithOptions(nicID, ep, - stack.NICOptions{ - Disabled: false, - // If no queueing discipline was specified - // provide a stub implementation that just - // delegates to the lower link endpoint. - QDisc: nil, - }); err != nil { - return fmt.Errorf("create NIC: %s", err) - } - return nil - } -} - -// withPromiscuousMode sets promiscuous mode in the given NICs. -func withPromiscuousMode(nicID tcpip.NICID, v bool) option.Option { - return func(s *stack.Stack) error { - if err := s.SetPromiscuousMode(nicID, v); err != nil { - return fmt.Errorf("set promiscuous mode: %s", err) - } - return nil - } -} - -// withSpoofing sets address spoofing in the given NICs, allowing -// endpoints to bind to any address in the NIC. -func withSpoofing(nicID tcpip.NICID, v bool) option.Option { - return func(s *stack.Stack) error { - if err := s.SetSpoofing(nicID, v); err != nil { - return fmt.Errorf("set spoofing: %s", err) - } - return nil - } -} diff --git a/listener/tun/ipstack/gvisor/option/option.go b/listener/tun/ipstack/gvisor/option/option.go deleted file mode 100644 index 88c26e62..00000000 --- a/listener/tun/ipstack/gvisor/option/option.go +++ /dev/null @@ -1,260 +0,0 @@ -package option - -import ( - "fmt" - - "golang.org/x/time/rate" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - - "github.com/Dreamacro/clash/common/pool" -) - -const ( - // defaultTimeToLive specifies the default TTL used by stack. - defaultTimeToLive uint8 = 64 - - // ipForwardingEnabled is the value used by stack to enable packet - // forwarding between NICs. - ipForwardingEnabled = true - - // icmpBurst is the default number of ICMP messages that can be sent in - // a single burst. - icmpBurst = 50 - - // icmpLimit is the default maximum number of ICMP messages permitted - // by this rate limiter. - icmpLimit rate.Limit = 1000 - - // tcpCongestionControl is the congestion control algorithm used by - // stack. ccReno is the default option in gVisor stack. - tcpCongestionControlAlgorithm = "reno" // "reno" or "cubic" - - // tcpDelayEnabled is the value used by stack to enable or disable - // tcp delay option. Disable Nagle's algorithm here by default. - tcpDelayEnabled = false - - // tcpModerateReceiveBufferEnabled is the value used by stack to - // enable or disable tcp receive buffer auto-tuning option. - tcpModerateReceiveBufferEnabled = false - - // tcpSACKEnabled is the value used by stack to enable or disable - // tcp selective ACK. - tcpSACKEnabled = true - - // tcpRecovery is the loss detection algorithm used by TCP. - tcpRecovery = tcpip.TCPRACKLossDetection - - // tcpMinBufferSize is the smallest size of a send/recv buffer. - tcpMinBufferSize = tcp.MinBufferSize - - // tcpMaxBufferSize is the maximum permitted size of a send/recv buffer. - tcpMaxBufferSize = pool.RelayBufferSize - - // tcpDefaultBufferSize is the default size of the send buffer for - // a transport endpoint. - tcpDefaultSendBufferSize = pool.RelayBufferSize - - // tcpDefaultReceiveBufferSize is the default size of the receive buffer - // for a transport endpoint. - tcpDefaultReceiveBufferSize = pool.RelayBufferSize -) - -type Option func(*stack.Stack) error - -// WithDefault sets all default values for stack. -func WithDefault() Option { - return func(s *stack.Stack) error { - opts := []Option{ - WithDefaultTTL(defaultTimeToLive), - WithForwarding(ipForwardingEnabled), - - // Config default stack ICMP settings. - WithICMPBurst(icmpBurst), WithICMPLimit(icmpLimit), - - // We expect no packet loss, therefore we can bump buffers. - // Too large buffers thrash cache, so there is little point - // in too large buffers. - // - // Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go - WithTCPSendBufferSizeRange(tcpMinBufferSize, tcpDefaultSendBufferSize, tcpMaxBufferSize), - WithTCPReceiveBufferSizeRange(tcpMinBufferSize, tcpDefaultReceiveBufferSize, tcpMaxBufferSize), - - WithTCPCongestionControl(tcpCongestionControlAlgorithm), - WithTCPDelay(tcpDelayEnabled), - - // Receive Buffer Auto-Tuning Option, see: - // https://github.com/google/gvisor/issues/1666 - WithTCPModerateReceiveBuffer(tcpModerateReceiveBufferEnabled), - - // TCP selective ACK Option, see: - // https://tools.ietf.org/html/rfc2018 - WithTCPSACKEnabled(tcpSACKEnabled), - - // TCPRACKLossDetection: indicates RACK is used for loss detection and - // recovery. - // - // TCPRACKStaticReoWnd: indicates the reordering window should not be - // adjusted when DSACK is received. - // - // TCPRACKNoDupTh: indicates RACK should not consider the classic three - // duplicate acknowledgements rule to mark the segments as lost. This - // is used when reordering is not detected. - WithTCPRecovery(tcpRecovery), - } - - for _, opt := range opts { - if err := opt(s); err != nil { - return err - } - } - - return nil - } -} - -// WithDefaultTTL sets the default TTL used by stack. -func WithDefaultTTL(ttl uint8) Option { - return func(s *stack.Stack) error { - opt := tcpip.DefaultTTLOption(ttl) - if err := s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &opt); err != nil { - return fmt.Errorf("set ipv4 default TTL: %s", err) - } - if err := s.SetNetworkProtocolOption(ipv6.ProtocolNumber, &opt); err != nil { - return fmt.Errorf("set ipv6 default TTL: %s", err) - } - return nil - } -} - -// WithForwarding sets packet forwarding between NICs for IPv4 & IPv6. -func WithForwarding(v bool) Option { - return func(s *stack.Stack) error { - if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, v); err != nil { - return fmt.Errorf("set ipv4 forwarding: %s", err) - } - if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, v); err != nil { - return fmt.Errorf("set ipv6 forwarding: %s", err) - } - return nil - } -} - -// WithICMPBurst sets the number of ICMP messages that can be sent -// in a single burst. -func WithICMPBurst(burst int) Option { - return func(s *stack.Stack) error { - s.SetICMPBurst(burst) - return nil - } -} - -// WithICMPLimit sets the maximum number of ICMP messages permitted -// by rate limiter. -func WithICMPLimit(limit rate.Limit) Option { - return func(s *stack.Stack) error { - s.SetICMPLimit(limit) - return nil - } -} - -// WithTCPSendBufferSize sets default the send buffer size for TCP. -func WithTCPSendBufferSize(size int) Option { - return func(s *stack.Stack) error { - sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize} - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil { - return fmt.Errorf("set TCP send buffer size range: %s", err) - } - return nil - } -} - -// WithTCPSendBufferSizeRange sets the send buffer size range for TCP. -func WithTCPSendBufferSizeRange(a, b, c int) Option { - return func(s *stack.Stack) error { - sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c} - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil { - return fmt.Errorf("set TCP send buffer size range: %s", err) - } - return nil - } -} - -// WithTCPReceiveBufferSize sets the default receive buffer size for TCP. -func WithTCPReceiveBufferSize(size int) Option { - return func(s *stack.Stack) error { - rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize} - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil { - return fmt.Errorf("set TCP receive buffer size range: %s", err) - } - return nil - } -} - -// WithTCPReceiveBufferSizeRange sets the receive buffer size range for TCP. -func WithTCPReceiveBufferSizeRange(a, b, c int) Option { - return func(s *stack.Stack) error { - rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c} - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil { - return fmt.Errorf("set TCP receive buffer size range: %s", err) - } - return nil - } -} - -// WithTCPCongestionControl sets the current congestion control algorithm. -func WithTCPCongestionControl(cc string) Option { - return func(s *stack.Stack) error { - opt := tcpip.CongestionControlOption(cc) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return fmt.Errorf("set TCP congestion control algorithm: %s", err) - } - return nil - } -} - -// WithTCPDelay enables or disables Nagle's algorithm in TCP. -func WithTCPDelay(v bool) Option { - return func(s *stack.Stack) error { - opt := tcpip.TCPDelayEnabled(v) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return fmt.Errorf("set TCP delay: %s", err) - } - return nil - } -} - -// WithTCPModerateReceiveBuffer sets receive buffer moderation for TCP. -func WithTCPModerateReceiveBuffer(v bool) Option { - return func(s *stack.Stack) error { - opt := tcpip.TCPModerateReceiveBufferOption(v) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return fmt.Errorf("set TCP moderate receive buffer: %s", err) - } - return nil - } -} - -// WithTCPSACKEnabled sets the SACK option for TCP. -func WithTCPSACKEnabled(v bool) Option { - return func(s *stack.Stack) error { - opt := tcpip.TCPSACKEnabled(v) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return fmt.Errorf("set TCP SACK: %s", err) - } - return nil - } -} - -// WithTCPRecovery sets the recovery option for TCP. -func WithTCPRecovery(v tcpip.TCPRecovery) Option { - return func(s *stack.Stack) error { - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &v); err != nil { - return fmt.Errorf("set TCP Recovery: %s", err) - } - return nil - } -} diff --git a/listener/tun/ipstack/gvisor/route.go b/listener/tun/ipstack/gvisor/route.go deleted file mode 100644 index 8e1ae94e..00000000 --- a/listener/tun/ipstack/gvisor/route.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build !no_gvisor - -package gvisor - -import ( - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -func withRouteTable(nicID tcpip.NICID) option.Option { - return func(s *stack.Stack) error { - s.SetRouteTable([]tcpip.Route{ - { - Destination: header.IPv4EmptySubnet, - NIC: nicID, - }, - { - Destination: header.IPv6EmptySubnet, - NIC: nicID, - }, - }) - return nil - } -} diff --git a/listener/tun/ipstack/gvisor/stack.go b/listener/tun/ipstack/gvisor/stack.go deleted file mode 100644 index c79bd0df..00000000 --- a/listener/tun/ipstack/gvisor/stack.go +++ /dev/null @@ -1,112 +0,0 @@ -//go:build !no_gvisor - -// Package gvisor provides a thin wrapper around a gVisor's stack. -package gvisor - -import ( - "net/netip" - - "github.com/Dreamacro/clash/adapter/inbound" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/Dreamacro/clash/listener/tun/ipstack" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - "gvisor.dev/gvisor/pkg/tcpip/transport/udp" -) - -type gvStack struct { - *stack.Stack - device device.Device -} - -func (s *gvStack) Close() error { - var err error - if s.device != nil { - err = s.device.Close() - } - if s.Stack != nil { - s.Stack.Close() - s.Stack.Wait() - } - return err -} - -// New allocates a new *gvStack with given options. -func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) { - s := &gvStack{ - Stack: stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ - ipv4.NewProtocol, - ipv6.NewProtocol, - }, - TransportProtocols: []stack.TransportProtocolFactory{ - tcp.NewProtocol, - udp.NewProtocol, - icmp.NewProtocol4, - icmp.NewProtocol6, - }, - }), - - device: device, - } - - handler := &gvHandler{ - gateway: tunAddress.Masked().Addr().Next(), - dnsHijack: dnsHijack, - tcpIn: tcpIn, - udpIn: udpIn, - } - - // Generate unique NIC id. - nicID := tcpip.NICID(s.Stack.UniqueID()) - defaultOpts := []option.Option{option.WithDefault()} - defaultOpts = append(defaultOpts, opts...) - opts = append(defaultOpts, - // Create stack NIC and then bind link endpoint to it. - withCreatingNIC(nicID, device), - - // In the past we did s.AddAddressRange to assign 0.0.0.0/0 - // onto the interface. We need that to be able to terminate - // all the incoming connections - to any ip. AddressRange API - // has been removed and the suggested workaround is to use - // Promiscuous mode. https://github.com/google/gvisor/issues/3876 - // - // Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go - withPromiscuousMode(nicID, nicPromiscuousModeEnabled), - - // Enable spoofing if a stack may send packets from unowned - // addresses. This change required changes to some netgophers - // since previously, promiscuous mode was enough to let the - // netstack respond to all incoming packets regardless of the - // packet's destination address. Now that a stack.Route is not - // held for each incoming packet, finding a route may fail with - // local addresses we don't own but accepted packets for while - // in promiscuous mode. Since we also want to be able to send - // from any address (in response the received promiscuous mode - // packets), we need to enable spoofing. - // - // Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127 - withSpoofing(nicID, nicSpoofingEnabled), - - // Add default route table for IPv4 and IPv6. This will handle - // all incoming ICMP packets. - withRouteTable(nicID), - - // Initiate transport protocol (TCP/UDP) with given handler. - withTCPHandler(handler.HandleTCP), withUDPHandler(handler.HandleUDP), - ) - - for _, opt := range opts { - if err := opt(s.Stack); err != nil { - return nil, err - } - } - return s, nil -} diff --git a/listener/tun/ipstack/gvisor/stack_no_gvisor.go b/listener/tun/ipstack/gvisor/stack_no_gvisor.go deleted file mode 100644 index 7a424e74..00000000 --- a/listener/tun/ipstack/gvisor/stack_no_gvisor.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build no_gvisor - -package gvisor - -import ( - "fmt" - "github.com/Dreamacro/clash/adapter/inbound" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/Dreamacro/clash/listener/tun/ipstack" - "github.com/Dreamacro/clash/log" - "net/netip" -) - -// New allocates a new *gvStack with given options. -func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) { - log.Fatalln("unsupported gvisor stack on the build") - return nil, fmt.Errorf("unsupported gvisor stack on the build") -} diff --git a/listener/tun/ipstack/gvisor/tcp.go b/listener/tun/ipstack/gvisor/tcp.go deleted file mode 100644 index 4798cb7c..00000000 --- a/listener/tun/ipstack/gvisor/tcp.go +++ /dev/null @@ -1,151 +0,0 @@ -//go:build !no_gvisor - -package gvisor - -import ( - "net" - "time" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option" - "github.com/Dreamacro/clash/log" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - "gvisor.dev/gvisor/pkg/waiter" -) - -const ( - // defaultWndSize if set to zero, the default - // receive window buffer size is used instead. - defaultWndSize = pool.RelayBufferSize - - // maxConnAttempts specifies the maximum number - // of in-flight tcp connection attempts. - maxConnAttempts = 1 << 10 - - // tcpKeepaliveCount is the maximum number of - // TCP keep-alive probes to send before giving up - // and killing the connection if no response is - // obtained from the other end. - tcpKeepaliveCount = 8 - - // tcpKeepaliveIdle specifies the time a connection - // must remain idle before the first TCP keepalive - // packet is sent. Once this time is reached, - // tcpKeepaliveInterval option is used instead. - tcpKeepaliveIdle = 60 * time.Second - - // tcpKeepaliveInterval specifies the interval - // time between sending TCP keepalive packets. - tcpKeepaliveInterval = 30 * time.Second -) - -func withTCPHandler(handle adapter.TCPHandleFunc) option.Option { - return func(s *stack.Stack) error { - tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) { - var ( - wq waiter.Queue - ep tcpip.Endpoint - err tcpip.Error - id = r.ID() - ) - - defer func() { - if err != nil { - log.Warnln("[STACK] forward tcp request %s:%d->%s:%d: %s", id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) - } - }() - - // Perform a TCP three-way handshake. - ep, err = r.CreateEndpoint(&wq) - if err != nil { - // RST: prevent potential half-open TCP connection leak. - r.Complete(true) - return - } - - err = setSocketOptions(s, ep) - if err != nil { - ep.Close() - r.Complete(true) - return - } - defer r.Complete(false) - - conn := &tcpConn{ - TCPConn: gonet.NewTCPConn(&wq, ep), - id: id, - } - - if conn.RemoteAddr() == nil { - log.Warnln("[STACK] endpoint is not connected, current state: %v", tcp.EndpointState(ep.State())) - _ = conn.Close() - return - } - - handle(conn) - }) - s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket) - return nil - } -} - -func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error { - { /* TCP keepalive options */ - ep.SocketOptions().SetKeepAlive(true) - - idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle) - if err := ep.SetSockOpt(&idle); err != nil { - return err - } - - interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval) - if err := ep.SetSockOpt(&interval); err != nil { - return err - } - - if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil { - return err - } - } - { /* TCP recv/send buffer size */ - var ss tcpip.TCPSendBufferSizeRangeOption - if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil { - ep.SocketOptions().SetReceiveBufferSize(int64(ss.Default), false) - } - - var rs tcpip.TCPReceiveBufferSizeRangeOption - if err := s.TransportProtocolOption(header.TCPProtocolNumber, &rs); err == nil { - ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false) - } - } - return nil -} - -type tcpConn struct { - *gonet.TCPConn - id stack.TransportEndpointID -} - -func (c *tcpConn) ID() *stack.TransportEndpointID { - return &c.id -} - -func (c *tcpConn) LocalAddr() net.Addr { - return &net.TCPAddr{ - IP: net.IP(c.id.LocalAddress), - Port: int(c.id.LocalPort), - } -} - -func (c *tcpConn) RemoteAddr() net.Addr { - return &net.TCPAddr{ - IP: net.IP(c.id.RemoteAddress), - Port: int(c.id.RemotePort), - } -} diff --git a/listener/tun/ipstack/gvisor/udp.go b/listener/tun/ipstack/gvisor/udp.go deleted file mode 100644 index 7abdd046..00000000 --- a/listener/tun/ipstack/gvisor/udp.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build !no_gvisor - -package gvisor - -import ( - "net" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option" - "github.com/Dreamacro/clash/log" - - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/udp" - "gvisor.dev/gvisor/pkg/waiter" -) - -func withUDPHandler(handle adapter.UDPHandleFunc) option.Option { - return func(s *stack.Stack) error { - udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) { - var ( - wq waiter.Queue - id = r.ID() - ) - ep, err := r.CreateEndpoint(&wq) - if err != nil { - log.Warnln("[STACK] udp forwarder request %s:%d->%s:%d: %s", id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) - return - } - - conn := &udpConn{ - UDPConn: gonet.NewUDPConn(s, &wq, ep), - id: id, - } - handle(conn) - }) - s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) - return nil - } -} - -type udpConn struct { - *gonet.UDPConn - id stack.TransportEndpointID -} - -func (c *udpConn) ID() *stack.TransportEndpointID { - return &c.id -} - -func (c *udpConn) LocalAddr() net.Addr { - return &net.UDPAddr{ - IP: net.IP(c.id.LocalAddress), - Port: int(c.id.LocalPort), - } -} - -func (c *udpConn) RemoteAddr() net.Addr { - return &net.UDPAddr{ - IP: net.IP(c.id.RemoteAddress), - Port: int(c.id.RemotePort), - } -} - -type packet struct { - pc adapter.UDPConn - rAddr net.Addr - payload []byte -} - -func (c *packet) Data() []byte { - return c.payload -} - -// WriteBack write UDP packet with source(ip, port) = `addr` -func (c *packet) WriteBack(b []byte, _ net.Addr) (n int, err error) { - return c.pc.WriteTo(b, c.rAddr) -} - -// LocalAddr returns the source IP/Port of UDP Packet -func (c *packet) LocalAddr() net.Addr { - return c.rAddr -} - -func (c *packet) Drop() { - _ = pool.Put(c.payload) -} diff --git a/listener/tun/ipstack/stack.go b/listener/tun/ipstack/stack.go deleted file mode 100644 index 4aa2bcb9..00000000 --- a/listener/tun/ipstack/stack.go +++ /dev/null @@ -1,5 +0,0 @@ -package ipstack - -import "io" - -type Stack io.Closer diff --git a/listener/tun/ipstack/system/mars/mars.go b/listener/tun/ipstack/system/mars/mars.go deleted file mode 100644 index a553c2d6..00000000 --- a/listener/tun/ipstack/system/mars/mars.go +++ /dev/null @@ -1,40 +0,0 @@ -package mars - -import ( - "io" - "net/netip" - - "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/nat" -) - -type StackListener struct { - device io.Closer - tcp *nat.TCP - udp *nat.UDP -} - -func StartListener(device io.ReadWriteCloser, gateway, portal, broadcast netip.Addr) (*StackListener, error) { - tcp, udp, err := nat.Start(device, gateway, portal, broadcast) - if err != nil { - return nil, err - } - - return &StackListener{ - device: device, - tcp: tcp, - udp: udp, - }, nil -} - -func (t *StackListener) Close() error { - _ = t.udp.Close() - return t.tcp.Close() -} - -func (t *StackListener) TCP() *nat.TCP { - return t.tcp -} - -func (t *StackListener) UDP() *nat.UDP { - return t.udp -} diff --git a/listener/tun/ipstack/system/mars/nat/nat.go b/listener/tun/ipstack/system/mars/nat/nat.go deleted file mode 100644 index 7954ace1..00000000 --- a/listener/tun/ipstack/system/mars/nat/nat.go +++ /dev/null @@ -1,195 +0,0 @@ -package nat - -import ( - log "github.com/sirupsen/logrus" - "io" - "net" - "net/netip" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip" -) - -func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) { - if !portal.Is4() || !gateway.Is4() { - return nil, nil, net.InvalidAddrError("only ipv4 supported") - } - - listener, err := net.ListenTCP("tcp4", nil) - if err != nil { - return nil, nil, err - } - - tab := newTable() - udp := &UDP{ - device: device, - buf: [pool.UDPBufferSize]byte{}, - } - tcp := &TCP{ - listener: listener, - portal: portal, - table: tab, - } - - gatewayPort := uint16(listener.Addr().(*net.TCPAddr).Port) - - go func() { - defer func() { - _ = tcp.Close() - _ = udp.Close() - }() - - buf := make([]byte, pool.RelayBufferSize) - - for { - n, err := device.Read(buf) - if err != nil { - log.Errorf("system error:%s", err.Error()) - return - } - - raw := buf[:n] - - var ( - ipVersion int - ip tcpip.IP - ) - - ipVersion = tcpip.IPVersion(raw) - - switch ipVersion { - case tcpip.IPv4Version: - ipv4 := tcpip.IPv4Packet(raw) - if !ipv4.Valid() { - continue - } - - if ipv4.TimeToLive() == 0x00 { - continue - } - - if ipv4.Flags()&tcpip.FlagMoreFragment != 0 { - continue - } - - if ipv4.FragmentOffset() != 0 { - continue - } - - ip = ipv4 - case tcpip.IPv6Version: - ipv6 := tcpip.IPv6Packet(raw) - if !ipv6.Valid() { - continue - } - - if ipv6.HopLimit() == 0x00 { - continue - } - - ip = ipv6 - default: - continue - } - - destinationIP := ip.DestinationIP() - - if !destinationIP.IsGlobalUnicast() || destinationIP == broadcast { - continue - } - - switch ip.Protocol() { - case tcpip.TCP: - t := tcpip.TCPPacket(ip.Payload()) - if !t.Valid() { - continue - } - - if destinationIP == portal { - if ip.SourceIP() == gateway && t.SourcePort() == gatewayPort { - tup := tab.tupleOf(t.DestinationPort()) - if tup == zeroTuple { - continue - } - - ip.SetSourceIP(tup.DestinationAddr.Addr()) - t.SetSourcePort(tup.DestinationAddr.Port()) - ip.SetDestinationIP(tup.SourceAddr.Addr()) - t.SetDestinationPort(tup.SourceAddr.Port()) - - ip.DecTimeToLive() - ip.ResetChecksum() - t.ResetChecksum(ip.PseudoSum()) - - _, _ = device.Write(raw) - } - } else { - tup := tuple{ - SourceAddr: netip.AddrPortFrom(ip.SourceIP(), t.SourcePort()), - DestinationAddr: netip.AddrPortFrom(destinationIP, t.DestinationPort()), - } - - port := tab.portOf(tup) - if port == 0 { - if t.Flags() != tcpip.TCPSyn { - continue - } - - port = tab.newConn(tup) - } - - ip.SetSourceIP(portal) - ip.SetDestinationIP(gateway) - t.SetSourcePort(port) - t.SetDestinationPort(gatewayPort) - - ip.ResetChecksum() - t.ResetChecksum(ip.PseudoSum()) - - _, _ = device.Write(raw) - } - case tcpip.UDP: - u := tcpip.UDPPacket(ip.Payload()) - if !u.Valid() { - continue - } - - udp.handleUDPPacket(ip, u) - case tcpip.ICMP: - i := tcpip.ICMPPacket(ip.Payload()) - - if i.Type() != tcpip.ICMPTypePingRequest || i.Code() != 0 { - continue - } - - i.SetType(tcpip.ICMPTypePingResponse) - - ip.SetDestinationIP(ip.SourceIP()) - ip.SetSourceIP(destinationIP) - - ip.ResetChecksum() - i.ResetChecksum() - - _, _ = device.Write(raw) - case tcpip.ICMPv6: - i := tcpip.ICMPv6Packet(ip.Payload()) - - if i.Type() != tcpip.ICMPv6EchoRequest || i.Code() != 0 { - continue - } - - i.SetType(tcpip.ICMPv6EchoReply) - - ip.SetDestinationIP(ip.SourceIP()) - ip.SetSourceIP(destinationIP) - - ip.ResetChecksum() - i.ResetChecksum(ip.PseudoSum()) - - _, _ = device.Write(raw) - } - } - }() - - return tcp, udp, nil -} diff --git a/listener/tun/ipstack/system/mars/nat/table.go b/listener/tun/ipstack/system/mars/nat/table.go deleted file mode 100644 index 38b7d6c6..00000000 --- a/listener/tun/ipstack/system/mars/nat/table.go +++ /dev/null @@ -1,84 +0,0 @@ -package nat - -import ( - "net/netip" - - "github.com/Dreamacro/clash/common/generics/list" -) - -const ( - portBegin = 30000 - portLength = 10240 -) - -var zeroTuple = tuple{} - -type tuple struct { - SourceAddr netip.AddrPort - DestinationAddr netip.AddrPort -} - -type binding struct { - tuple tuple - offset uint16 -} - -type table struct { - tuples map[tuple]*list.Element[*binding] - ports [portLength]*list.Element[*binding] - available *list.List[*binding] -} - -func (t *table) tupleOf(port uint16) tuple { - offset := port - portBegin - if offset > portLength { - return zeroTuple - } - - elm := t.ports[offset] - - t.available.MoveToFront(elm) - - return elm.Value.tuple -} - -func (t *table) portOf(tuple tuple) uint16 { - elm := t.tuples[tuple] - if elm == nil { - return 0 - } - - t.available.MoveToFront(elm) - - return portBegin + elm.Value.offset -} - -func (t *table) newConn(tuple tuple) uint16 { - elm := t.available.Back() - b := elm.Value - - delete(t.tuples, b.tuple) - t.tuples[tuple] = elm - b.tuple = tuple - - t.available.MoveToFront(elm) - - return portBegin + b.offset -} - -func newTable() *table { - result := &table{ - tuples: make(map[tuple]*list.Element[*binding], portLength), - ports: [portLength]*list.Element[*binding]{}, - available: list.New[*binding](), - } - - for idx := range result.ports { - result.ports[idx] = result.available.PushFront(&binding{ - tuple: tuple{}, - offset: uint16(idx), - }) - } - - return result -} diff --git a/listener/tun/ipstack/system/mars/nat/tcp.go b/listener/tun/ipstack/system/mars/nat/tcp.go deleted file mode 100644 index cc0abe7d..00000000 --- a/listener/tun/ipstack/system/mars/nat/tcp.go +++ /dev/null @@ -1,67 +0,0 @@ -package nat - -import ( - "net" - "net/netip" - "time" -) - -type TCP struct { - listener *net.TCPListener - portal netip.Addr - table *table -} - -type conn struct { - net.Conn - - tuple tuple -} - -func (t *TCP) Accept() (net.Conn, error) { - c, err := t.listener.AcceptTCP() - if err != nil { - return nil, err - } - - addr := c.RemoteAddr().(*net.TCPAddr) - tup := t.table.tupleOf(uint16(addr.Port)) - if !addr.IP.Equal(t.portal.AsSlice()) || tup == zeroTuple { - _ = c.Close() - - return nil, net.InvalidAddrError("unknown remote addr") - } - - addition(c) - - return &conn{ - Conn: c, - tuple: tup, - }, nil -} - -func (t *TCP) Close() error { - return t.listener.Close() -} - -func (t *TCP) Addr() net.Addr { - return t.listener.Addr() -} - -func (t *TCP) SetDeadline(time time.Time) error { - return t.listener.SetDeadline(time) -} - -func (c *conn) LocalAddr() net.Addr { - return &net.TCPAddr{ - IP: c.tuple.SourceAddr.Addr().AsSlice(), - Port: int(c.tuple.SourceAddr.Port()), - } -} - -func (c *conn) RemoteAddr() net.Addr { - return &net.TCPAddr{ - IP: c.tuple.DestinationAddr.Addr().AsSlice(), - Port: int(c.tuple.DestinationAddr.Port()), - } -} diff --git a/listener/tun/ipstack/system/mars/nat/tcp_linux.go b/listener/tun/ipstack/system/mars/nat/tcp_linux.go deleted file mode 100644 index 9f433a3a..00000000 --- a/listener/tun/ipstack/system/mars/nat/tcp_linux.go +++ /dev/null @@ -1,15 +0,0 @@ -package nat - -import ( - "net" - "syscall" -) - -func addition(c *net.TCPConn) { - sys, err := c.SyscallConn() - if err == nil { - _ = sys.Control(func(fd uintptr) { - _ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_NO_CHECK, 1) - }) - } -} diff --git a/listener/tun/ipstack/system/mars/nat/tcp_others.go b/listener/tun/ipstack/system/mars/nat/tcp_others.go deleted file mode 100644 index cc46bdbd..00000000 --- a/listener/tun/ipstack/system/mars/nat/tcp_others.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package nat - -import "net" - -func addition(*net.TCPConn) {} diff --git a/listener/tun/ipstack/system/mars/nat/udp.go b/listener/tun/ipstack/system/mars/nat/udp.go deleted file mode 100644 index 6cc7faee..00000000 --- a/listener/tun/ipstack/system/mars/nat/udp.go +++ /dev/null @@ -1,145 +0,0 @@ -package nat - -import ( - "io" - "math/rand" - "net" - "net/netip" - "sync" - - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip" -) - -type call struct { - cond *sync.Cond - buf []byte - n int - source net.Addr - destination net.Addr -} - -type UDP struct { - closed bool - device io.Writer - queueLock sync.Mutex - queue []*call - bufLock sync.Mutex - buf [pool.UDPBufferSize]byte -} - -func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) { - u.queueLock.Lock() - defer u.queueLock.Unlock() - - for !u.closed { - c := &call{ - cond: sync.NewCond(&u.queueLock), - buf: buf, - n: -1, - source: nil, - destination: nil, - } - - u.queue = append(u.queue, c) - - c.cond.Wait() - - if c.n >= 0 { - return c.n, c.source, c.destination, nil - } - } - - return -1, nil, nil, net.ErrClosed -} - -func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error) { - if u.closed { - return 0, net.ErrClosed - } - - u.bufLock.Lock() - defer u.bufLock.Unlock() - - if len(buf) > 0xffff { - return 0, net.InvalidAddrError("invalid ip version") - } - - srcAddr, srcOk := local.(*net.UDPAddr) - dstAddr, dstOk := remote.(*net.UDPAddr) - if !srcOk || !dstOk { - return 0, net.InvalidAddrError("invalid addr") - } - - srcAddrPort := netip.AddrPortFrom(nnip.IpToAddr(srcAddr.IP), uint16(srcAddr.Port)) - dstAddrPort := netip.AddrPortFrom(nnip.IpToAddr(dstAddr.IP), uint16(dstAddr.Port)) - - if !srcAddrPort.Addr().Is4() || !dstAddrPort.Addr().Is4() { - return 0, net.InvalidAddrError("invalid ip version") - } - - tcpip.SetIPv4(u.buf[:]) - - ip := tcpip.IPv4Packet(u.buf[:]) - ip.SetHeaderLen(tcpip.IPv4HeaderSize) - ip.SetTotalLength(tcpip.IPv4HeaderSize + tcpip.UDPHeaderSize + uint16(len(buf))) - ip.SetTypeOfService(0) - ip.SetIdentification(uint16(rand.Uint32())) - ip.SetFragmentOffset(0) - ip.SetTimeToLive(64) - ip.SetProtocol(tcpip.UDP) - ip.SetSourceIP(srcAddrPort.Addr()) - ip.SetDestinationIP(dstAddrPort.Addr()) - - udp := tcpip.UDPPacket(ip.Payload()) - udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf))) - udp.SetSourcePort(srcAddrPort.Port()) - udp.SetDestinationPort(dstAddrPort.Port()) - copy(udp.Payload(), buf) - - ip.ResetChecksum() - udp.ResetChecksum(ip.PseudoSum()) - - return u.device.Write(u.buf[:ip.TotalLen()]) -} - -func (u *UDP) Close() error { - u.queueLock.Lock() - defer u.queueLock.Unlock() - - u.closed = true - - for _, c := range u.queue { - c.cond.Signal() - } - - return nil -} - -func (u *UDP) handleUDPPacket(ip tcpip.IP, pkt tcpip.UDPPacket) { - var c *call - - u.queueLock.Lock() - - if len(u.queue) > 0 { - idx := len(u.queue) - 1 - c = u.queue[idx] - u.queue = u.queue[:idx] - } - - u.queueLock.Unlock() - - if c != nil { - c.source = &net.UDPAddr{ - IP: ip.SourceIP().AsSlice(), - Port: int(pkt.SourcePort()), - } - c.destination = &net.UDPAddr{ - IP: ip.DestinationIP().AsSlice(), - Port: int(pkt.DestinationPort()), - } - c.n = copy(c.buf, pkt.Payload()) - c.cond.Signal() - } -} diff --git a/listener/tun/ipstack/system/mars/tcpip/icmp.go b/listener/tun/ipstack/system/mars/tcpip/icmp.go deleted file mode 100644 index d473c25f..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/icmp.go +++ /dev/null @@ -1,40 +0,0 @@ -package tcpip - -import ( - "encoding/binary" -) - -type ICMPType = byte - -const ( - ICMPTypePingRequest byte = 0x8 - ICMPTypePingResponse byte = 0x0 -) - -type ICMPPacket []byte - -func (p ICMPPacket) Type() ICMPType { - return p[0] -} - -func (p ICMPPacket) SetType(v ICMPType) { - p[0] = v -} - -func (p ICMPPacket) Code() byte { - return p[1] -} - -func (p ICMPPacket) Checksum() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -func (p ICMPPacket) SetChecksum(sum [2]byte) { - p[2] = sum[0] - p[3] = sum[1] -} - -func (p ICMPPacket) ResetChecksum() { - p.SetChecksum(zeroChecksum) - p.SetChecksum(Checksum(0, p)) -} diff --git a/listener/tun/ipstack/system/mars/tcpip/icmpv6.go b/listener/tun/ipstack/system/mars/tcpip/icmpv6.go deleted file mode 100644 index a87f965f..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/icmpv6.go +++ /dev/null @@ -1,172 +0,0 @@ -package tcpip - -import ( - "encoding/binary" -) - -type ICMPv6Packet []byte - -const ( - ICMPv6HeaderSize = 4 - - ICMPv6MinimumSize = 8 - - ICMPv6PayloadOffset = 8 - - ICMPv6EchoMinimumSize = 8 - - ICMPv6ErrorHeaderSize = 8 - - ICMPv6DstUnreachableMinimumSize = ICMPv6MinimumSize - - ICMPv6PacketTooBigMinimumSize = ICMPv6MinimumSize - - ICMPv6ChecksumOffset = 2 - - icmpv6PointerOffset = 4 - - icmpv6MTUOffset = 4 - - icmpv6IdentOffset = 4 - - icmpv6SequenceOffset = 6 - - NDPHopLimit = 255 -) - -type ICMPv6Type byte - -const ( - ICMPv6DstUnreachable ICMPv6Type = 1 - ICMPv6PacketTooBig ICMPv6Type = 2 - ICMPv6TimeExceeded ICMPv6Type = 3 - ICMPv6ParamProblem ICMPv6Type = 4 - ICMPv6EchoRequest ICMPv6Type = 128 - ICMPv6EchoReply ICMPv6Type = 129 - - ICMPv6RouterSolicit ICMPv6Type = 133 - ICMPv6RouterAdvert ICMPv6Type = 134 - ICMPv6NeighborSolicit ICMPv6Type = 135 - ICMPv6NeighborAdvert ICMPv6Type = 136 - ICMPv6RedirectMsg ICMPv6Type = 137 - - ICMPv6MulticastListenerQuery ICMPv6Type = 130 - ICMPv6MulticastListenerReport ICMPv6Type = 131 - ICMPv6MulticastListenerDone ICMPv6Type = 132 -) - -func (typ ICMPv6Type) IsErrorType() bool { - return typ&0x80 == 0 -} - -type ICMPv6Code byte - -const ( - ICMPv6NetworkUnreachable ICMPv6Code = 0 - ICMPv6Prohibited ICMPv6Code = 1 - ICMPv6BeyondScope ICMPv6Code = 2 - ICMPv6AddressUnreachable ICMPv6Code = 3 - ICMPv6PortUnreachable ICMPv6Code = 4 - ICMPv6Policy ICMPv6Code = 5 - ICMPv6RejectRoute ICMPv6Code = 6 -) - -const ( - ICMPv6HopLimitExceeded ICMPv6Code = 0 - ICMPv6ReassemblyTimeout ICMPv6Code = 1 -) - -const ( - ICMPv6ErroneousHeader ICMPv6Code = 0 - - ICMPv6UnknownHeader ICMPv6Code = 1 - - ICMPv6UnknownOption ICMPv6Code = 2 -) - -const ICMPv6UnusedCode ICMPv6Code = 0 - -func (b ICMPv6Packet) Type() ICMPv6Type { - return ICMPv6Type(b[0]) -} - -func (b ICMPv6Packet) SetType(t ICMPv6Type) { - b[0] = byte(t) -} - -func (b ICMPv6Packet) Code() ICMPv6Code { - return ICMPv6Code(b[1]) -} - -func (b ICMPv6Packet) SetCode(c ICMPv6Code) { - b[1] = byte(c) -} - -func (b ICMPv6Packet) TypeSpecific() uint32 { - return binary.BigEndian.Uint32(b[icmpv6PointerOffset:]) -} - -func (b ICMPv6Packet) SetTypeSpecific(val uint32) { - binary.BigEndian.PutUint32(b[icmpv6PointerOffset:], val) -} - -func (b ICMPv6Packet) Checksum() uint16 { - return binary.BigEndian.Uint16(b[ICMPv6ChecksumOffset:]) -} - -func (b ICMPv6Packet) SetChecksum(sum [2]byte) { - _ = b[ICMPv6ChecksumOffset+1] - b[ICMPv6ChecksumOffset] = sum[0] - b[ICMPv6ChecksumOffset+1] = sum[1] -} - -func (ICMPv6Packet) SourcePort() uint16 { - return 0 -} - -func (ICMPv6Packet) DestinationPort() uint16 { - return 0 -} - -func (ICMPv6Packet) SetSourcePort(uint16) { -} - -func (ICMPv6Packet) SetDestinationPort(uint16) { -} - -func (b ICMPv6Packet) MTU() uint32 { - return binary.BigEndian.Uint32(b[icmpv6MTUOffset:]) -} - -func (b ICMPv6Packet) SetMTU(mtu uint32) { - binary.BigEndian.PutUint32(b[icmpv6MTUOffset:], mtu) -} - -func (b ICMPv6Packet) Ident() uint16 { - return binary.BigEndian.Uint16(b[icmpv6IdentOffset:]) -} - -func (b ICMPv6Packet) SetIdent(ident uint16) { - binary.BigEndian.PutUint16(b[icmpv6IdentOffset:], ident) -} - -func (b ICMPv6Packet) Sequence() uint16 { - return binary.BigEndian.Uint16(b[icmpv6SequenceOffset:]) -} - -func (b ICMPv6Packet) SetSequence(sequence uint16) { - binary.BigEndian.PutUint16(b[icmpv6SequenceOffset:], sequence) -} - -func (b ICMPv6Packet) MessageBody() []byte { - return b[ICMPv6HeaderSize:] -} - -func (b ICMPv6Packet) Payload() []byte { - return b[ICMPv6PayloadOffset:] -} - -func (b ICMPv6Packet) ResetChecksum(psum uint32) { - b.SetChecksum(zeroChecksum) - b.SetChecksum(Checksum(psum, b)) -} diff --git a/listener/tun/ipstack/system/mars/tcpip/ip.go b/listener/tun/ipstack/system/mars/tcpip/ip.go deleted file mode 100644 index 88237b01..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/ip.go +++ /dev/null @@ -1,209 +0,0 @@ -package tcpip - -import ( - "encoding/binary" - "errors" - "net/netip" -) - -type IPProtocol = byte - -type IP interface { - Payload() []byte - SourceIP() netip.Addr - DestinationIP() netip.Addr - SetSourceIP(ip netip.Addr) - SetDestinationIP(ip netip.Addr) - Protocol() IPProtocol - DecTimeToLive() - ResetChecksum() - PseudoSum() uint32 -} - -// IPProtocol type -const ( - ICMP IPProtocol = 0x01 - TCP IPProtocol = 0x06 - UDP IPProtocol = 0x11 - ICMPv6 IPProtocol = 0x3a -) - -const ( - FlagDontFragment = 1 << 1 - FlagMoreFragment = 1 << 2 -) - -const ( - IPv4HeaderSize = 20 - - IPv4Version = 4 - - IPv4OptionsOffset = 20 - IPv4PacketMinLength = IPv4OptionsOffset -) - -var ( - ErrInvalidLength = errors.New("invalid packet length") - ErrInvalidIPVersion = errors.New("invalid ip version") - ErrInvalidChecksum = errors.New("invalid checksum") -) - -type IPv4Packet []byte - -func (p IPv4Packet) TotalLen() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -func (p IPv4Packet) SetTotalLength(length uint16) { - binary.BigEndian.PutUint16(p[2:], length) -} - -func (p IPv4Packet) HeaderLen() uint16 { - return uint16(p[0]&0xf) * 4 -} - -func (p IPv4Packet) SetHeaderLen(length uint16) { - p[0] &= 0xF0 - p[0] |= byte(length / 4) -} - -func (p IPv4Packet) TypeOfService() byte { - return p[1] -} - -func (p IPv4Packet) SetTypeOfService(tos byte) { - p[1] = tos -} - -func (p IPv4Packet) Identification() uint16 { - return binary.BigEndian.Uint16(p[4:]) -} - -func (p IPv4Packet) SetIdentification(id uint16) { - binary.BigEndian.PutUint16(p[4:], id) -} - -func (p IPv4Packet) FragmentOffset() uint16 { - return binary.BigEndian.Uint16([]byte{p[6] & 0x7, p[7]}) * 8 -} - -func (p IPv4Packet) SetFragmentOffset(offset uint32) { - flags := p.Flags() - binary.BigEndian.PutUint16(p[6:], uint16(offset/8)) - p.SetFlags(flags) -} - -func (p IPv4Packet) DataLen() uint16 { - return p.TotalLen() - p.HeaderLen() -} - -func (p IPv4Packet) Payload() []byte { - return p[p.HeaderLen():p.TotalLen()] -} - -func (p IPv4Packet) Protocol() IPProtocol { - return p[9] -} - -func (p IPv4Packet) SetProtocol(protocol IPProtocol) { - p[9] = protocol -} - -func (p IPv4Packet) Flags() byte { - return p[6] >> 5 -} - -func (p IPv4Packet) SetFlags(flags byte) { - p[6] &= 0x1F - p[6] |= flags << 5 -} - -func (p IPv4Packet) SourceIP() netip.Addr { - return netip.AddrFrom4([4]byte{p[12], p[13], p[14], p[15]}) -} - -func (p IPv4Packet) SetSourceIP(ip netip.Addr) { - if ip.Is4() { - copy(p[12:16], ip.AsSlice()) - } -} - -func (p IPv4Packet) DestinationIP() netip.Addr { - return netip.AddrFrom4([4]byte{p[16], p[17], p[18], p[19]}) -} - -func (p IPv4Packet) SetDestinationIP(ip netip.Addr) { - if ip.Is4() { - copy(p[16:20], ip.AsSlice()) - } -} - -func (p IPv4Packet) Checksum() uint16 { - return binary.BigEndian.Uint16(p[10:]) -} - -func (p IPv4Packet) SetChecksum(sum [2]byte) { - p[10] = sum[0] - p[11] = sum[1] -} - -func (p IPv4Packet) TimeToLive() uint8 { - return p[8] -} - -func (p IPv4Packet) SetTimeToLive(ttl uint8) { - p[8] = ttl -} - -func (p IPv4Packet) DecTimeToLive() { - p[8] = p[8] - uint8(1) -} - -func (p IPv4Packet) ResetChecksum() { - p.SetChecksum(zeroChecksum) - p.SetChecksum(Checksum(0, p[:p.HeaderLen()])) -} - -// PseudoSum for tcp checksum -func (p IPv4Packet) PseudoSum() uint32 { - sum := Sum(p[12:20]) - sum += uint32(p.Protocol()) - sum += uint32(p.DataLen()) - return sum -} - -func (p IPv4Packet) Valid() bool { - return len(p) >= IPv4HeaderSize && uint16(len(p)) >= p.TotalLen() -} - -func (p IPv4Packet) Verify() error { - if len(p) < IPv4PacketMinLength { - return ErrInvalidLength - } - - checksum := []byte{p[10], p[11]} - headerLength := uint16(p[0]&0xF) * 4 - packetLength := binary.BigEndian.Uint16(p[2:]) - - if p[0]>>4 != 4 { - return ErrInvalidIPVersion - } - - if uint16(len(p)) < packetLength || packetLength < headerLength { - return ErrInvalidLength - } - - p[10] = 0 - p[11] = 0 - defer copy(p[10:12], checksum) - - answer := Checksum(0, p[:headerLength]) - - if answer[0] != checksum[0] || answer[1] != checksum[1] { - return ErrInvalidChecksum - } - - return nil -} - -var _ IP = (*IPv4Packet)(nil) diff --git a/listener/tun/ipstack/system/mars/tcpip/ipv6.go b/listener/tun/ipstack/system/mars/tcpip/ipv6.go deleted file mode 100644 index fd7502d6..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/ipv6.go +++ /dev/null @@ -1,141 +0,0 @@ -package tcpip - -import ( - "encoding/binary" - "net/netip" -) - -const ( - versTCFL = 0 - - IPv6PayloadLenOffset = 4 - - IPv6NextHeaderOffset = 6 - hopLimit = 7 - v6SrcAddr = 8 - v6DstAddr = v6SrcAddr + IPv6AddressSize - - IPv6FixedHeaderSize = v6DstAddr + IPv6AddressSize -) - -const ( - versIHL = 0 - tos = 1 - ipVersionShift = 4 - ipIHLMask = 0x0f - IPv4IHLStride = 4 -) - -type IPv6Packet []byte - -const ( - IPv6MinimumSize = IPv6FixedHeaderSize - - IPv6AddressSize = 16 - - IPv6Version = 6 - - IPv6MinimumMTU = 1280 -) - -func (b IPv6Packet) PayloadLength() uint16 { - return binary.BigEndian.Uint16(b[IPv6PayloadLenOffset:]) -} - -func (b IPv6Packet) HopLimit() uint8 { - return b[hopLimit] -} - -func (b IPv6Packet) NextHeader() byte { - return b[IPv6NextHeaderOffset] -} - -func (b IPv6Packet) Protocol() IPProtocol { - return b.NextHeader() -} - -func (b IPv6Packet) Payload() []byte { - return b[IPv6MinimumSize:][:b.PayloadLength()] -} - -func (b IPv6Packet) SourceIP() netip.Addr { - addr, _ := netip.AddrFromSlice(b[v6SrcAddr:][:IPv6AddressSize]) - return addr -} - -func (b IPv6Packet) DestinationIP() netip.Addr { - addr, _ := netip.AddrFromSlice(b[v6DstAddr:][:IPv6AddressSize]) - return addr -} - -func (IPv6Packet) Checksum() uint16 { - return 0 -} - -func (b IPv6Packet) TOS() (uint8, uint32) { - v := binary.BigEndian.Uint32(b[versTCFL:]) - return uint8(v >> 20), v & 0xfffff -} - -func (b IPv6Packet) SetTOS(t uint8, l uint32) { - vtf := (6 << 28) | (uint32(t) << 20) | (l & 0xfffff) - binary.BigEndian.PutUint32(b[versTCFL:], vtf) -} - -func (b IPv6Packet) SetPayloadLength(payloadLength uint16) { - binary.BigEndian.PutUint16(b[IPv6PayloadLenOffset:], payloadLength) -} - -func (b IPv6Packet) SetSourceIP(addr netip.Addr) { - if addr.Is6() { - copy(b[v6SrcAddr:][:IPv6AddressSize], addr.AsSlice()) - } -} - -func (b IPv6Packet) SetDestinationIP(addr netip.Addr) { - if addr.Is6() { - copy(b[v6DstAddr:][:IPv6AddressSize], addr.AsSlice()) - } -} - -func (b IPv6Packet) SetHopLimit(v uint8) { - b[hopLimit] = v -} - -func (b IPv6Packet) SetNextHeader(v byte) { - b[IPv6NextHeaderOffset] = v -} - -func (b IPv6Packet) SetProtocol(p IPProtocol) { - b.SetNextHeader(p) -} - -func (b IPv6Packet) DecTimeToLive() { - b[hopLimit] = b[hopLimit] - uint8(1) -} - -func (IPv6Packet) SetChecksum(uint16) { -} - -func (IPv6Packet) ResetChecksum() { -} - -func (b IPv6Packet) PseudoSum() uint32 { - sum := Sum(b[v6SrcAddr:IPv6FixedHeaderSize]) - sum += uint32(b.Protocol()) - sum += uint32(b.PayloadLength()) - return sum -} - -func (b IPv6Packet) Valid() bool { - return len(b) >= IPv6MinimumSize && len(b) >= int(b.PayloadLength())+IPv6MinimumSize -} - -func IPVersion(b []byte) int { - if len(b) < versIHL+1 { - return -1 - } - return int(b[versIHL] >> ipVersionShift) -} - -var _ IP = (*IPv6Packet)(nil) diff --git a/listener/tun/ipstack/system/mars/tcpip/tcp.go b/listener/tun/ipstack/system/mars/tcpip/tcp.go deleted file mode 100644 index d3d08878..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcp.go +++ /dev/null @@ -1,90 +0,0 @@ -package tcpip - -import ( - "encoding/binary" - "net" -) - -const ( - TCPFin uint16 = 1 << 0 - TCPSyn uint16 = 1 << 1 - TCPRst uint16 = 1 << 2 - TCPPuh uint16 = 1 << 3 - TCPAck uint16 = 1 << 4 - TCPUrg uint16 = 1 << 5 - TCPEce uint16 = 1 << 6 - TCPEwr uint16 = 1 << 7 - TCPNs uint16 = 1 << 8 -) - -const TCPHeaderSize = 20 - -type TCPPacket []byte - -func (p TCPPacket) SourcePort() uint16 { - return binary.BigEndian.Uint16(p) -} - -func (p TCPPacket) SetSourcePort(port uint16) { - binary.BigEndian.PutUint16(p, port) -} - -func (p TCPPacket) DestinationPort() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -func (p TCPPacket) SetDestinationPort(port uint16) { - binary.BigEndian.PutUint16(p[2:], port) -} - -func (p TCPPacket) Flags() uint16 { - return uint16(p[13] | (p[12] & 0x1)) -} - -func (p TCPPacket) Checksum() uint16 { - return binary.BigEndian.Uint16(p[16:]) -} - -func (p TCPPacket) SetChecksum(sum [2]byte) { - p[16] = sum[0] - p[17] = sum[1] -} - -func (p TCPPacket) ResetChecksum(psum uint32) { - p.SetChecksum(zeroChecksum) - p.SetChecksum(Checksum(psum, p)) -} - -func (p TCPPacket) Valid() bool { - return len(p) >= TCPHeaderSize -} - -func (p TCPPacket) Verify(sourceAddress net.IP, targetAddress net.IP) error { - var checksum [2]byte - checksum[0] = p[16] - checksum[1] = p[17] - - // reset checksum - p[16] = 0 - p[17] = 0 - - // restore checksum - defer func() { - p[16] = checksum[0] - p[17] = checksum[1] - }() - - // check checksum - s := uint32(0) - s += Sum(sourceAddress) - s += Sum(targetAddress) - s += uint32(TCP) - s += uint32(len(p)) - - check := Checksum(s, p) - if checksum[0] != check[0] || checksum[1] != check[1] { - return ErrInvalidChecksum - } - - return nil -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip.go b/listener/tun/ipstack/system/mars/tcpip/tcpip.go deleted file mode 100644 index 87811ea2..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip.go +++ /dev/null @@ -1,24 +0,0 @@ -package tcpip - -var zeroChecksum = [2]byte{0x00, 0x00} - -var SumFnc = SumCompat - -func Sum(b []byte) uint32 { - return SumFnc(b) -} - -// Checksum for Internet Protocol family headers -func Checksum(sum uint32, b []byte) (answer [2]byte) { - sum += Sum(b) - sum = (sum >> 16) + (sum & 0xffff) - sum += sum >> 16 - sum = ^sum - answer[0] = byte(sum >> 8) - answer[1] = byte(sum) - return -} - -func SetIPv4(packet []byte) { - packet[0] = (packet[0] & 0x0f) | (4 << 4) -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64.go b/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64.go deleted file mode 100644 index c7d5dcaa..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build !noasm - -package tcpip - -import ( - "unsafe" - - "golang.org/x/sys/cpu" -) - -//go:noescape -func sumAsmAvx2(data unsafe.Pointer, length uintptr) uintptr - -func SumAVX2(data []byte) uint32 { - if len(data) == 0 { - return 0 - } - - return uint32(sumAsmAvx2(unsafe.Pointer(&data[0]), uintptr(len(data)))) -} - -func init() { - if cpu.X86.HasAVX2 { - SumFnc = SumAVX2 - } -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64.s b/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64.s deleted file mode 100644 index 100820ba..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64.s +++ /dev/null @@ -1,140 +0,0 @@ -#include "textflag.h" - -DATA endian_swap_mask<>+0(SB)/8, $0x607040502030001 -DATA endian_swap_mask<>+8(SB)/8, $0xE0F0C0D0A0B0809 -DATA endian_swap_mask<>+16(SB)/8, $0x607040502030001 -DATA endian_swap_mask<>+24(SB)/8, $0xE0F0C0D0A0B0809 -GLOBL endian_swap_mask<>(SB), RODATA, $32 - -// func sumAsmAvx2(data unsafe.Pointer, length uintptr) uintptr -// -// args (8 bytes aligned): -// data unsafe.Pointer - 8 bytes - 0 offset -// length uintptr - 8 bytes - 8 offset -// result uintptr - 8 bytes - 16 offset -#define PDATA AX -#define LENGTH CX -#define RESULT BX -TEXT ·sumAsmAvx2(SB),NOSPLIT,$0-24 - MOVQ data+0(FP), PDATA - MOVQ length+8(FP), LENGTH - XORQ RESULT, RESULT - -#define VSUM Y0 -#define ENDIAN_SWAP_MASK Y1 -BEGIN: - VMOVDQU endian_swap_mask<>(SB), ENDIAN_SWAP_MASK - VPXOR VSUM, VSUM, VSUM - -#define LOADED_0 Y2 -#define LOADED_1 Y3 -#define LOADED_2 Y4 -#define LOADED_3 Y5 -BATCH_64: - CMPQ LENGTH, $64 - JB BATCH_32 - VPMOVZXWD (PDATA), LOADED_0 - VPMOVZXWD 16(PDATA), LOADED_1 - VPMOVZXWD 32(PDATA), LOADED_2 - VPMOVZXWD 48(PDATA), LOADED_3 - VPSHUFB ENDIAN_SWAP_MASK, LOADED_0, LOADED_0 - VPSHUFB ENDIAN_SWAP_MASK, LOADED_1, LOADED_1 - VPSHUFB ENDIAN_SWAP_MASK, LOADED_2, LOADED_2 - VPSHUFB ENDIAN_SWAP_MASK, LOADED_3, LOADED_3 - VPADDD LOADED_0, VSUM, VSUM - VPADDD LOADED_1, VSUM, VSUM - VPADDD LOADED_2, VSUM, VSUM - VPADDD LOADED_3, VSUM, VSUM - ADDQ $-64, LENGTH - ADDQ $64, PDATA - JMP BATCH_64 -#undef LOADED_0 -#undef LOADED_1 -#undef LOADED_2 -#undef LOADED_3 - -#define LOADED_0 Y2 -#define LOADED_1 Y3 -BATCH_32: - CMPQ LENGTH, $32 - JB BATCH_16 - VPMOVZXWD (PDATA), LOADED_0 - VPMOVZXWD 16(PDATA), LOADED_1 - VPSHUFB ENDIAN_SWAP_MASK, LOADED_0, LOADED_0 - VPSHUFB ENDIAN_SWAP_MASK, LOADED_1, LOADED_1 - VPADDD LOADED_0, VSUM, VSUM - VPADDD LOADED_1, VSUM, VSUM - ADDQ $-32, LENGTH - ADDQ $32, PDATA - JMP BATCH_32 -#undef LOADED_0 -#undef LOADED_1 - -#define LOADED Y2 -BATCH_16: - CMPQ LENGTH, $16 - JB COLLECT - VPMOVZXWD (PDATA), LOADED - VPSHUFB ENDIAN_SWAP_MASK, LOADED, LOADED - VPADDD LOADED, VSUM, VSUM - ADDQ $-16, LENGTH - ADDQ $16, PDATA - JMP BATCH_16 -#undef LOADED - -#define EXTRACTED Y2 -#define EXTRACTED_128 X2 -#define TEMP_64 DX -COLLECT: - VEXTRACTI128 $0, VSUM, EXTRACTED_128 - VPEXTRD $0, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VPEXTRD $1, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VPEXTRD $2, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VPEXTRD $3, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VEXTRACTI128 $1, VSUM, EXTRACTED_128 - VPEXTRD $0, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VPEXTRD $1, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VPEXTRD $2, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT - VPEXTRD $3, EXTRACTED_128, TEMP_64 - ADDL TEMP_64, RESULT -#undef EXTRACTED -#undef EXTRACTED_128 -#undef TEMP_64 - -#define TEMP DX -#define TEMP2 SI -BATCH_2: - CMPQ LENGTH, $2 - JB BATCH_1 - XORQ TEMP, TEMP - MOVW (PDATA), TEMP - MOVQ TEMP, TEMP2 - SHRW $8, TEMP2 - SHLW $8, TEMP - ORW TEMP2, TEMP - ADDL TEMP, RESULT - ADDQ $-2, LENGTH - ADDQ $2, PDATA - JMP BATCH_2 -#undef TEMP - -#define TEMP DX -BATCH_1: - CMPQ LENGTH, $0 - JZ RETURN - XORQ TEMP, TEMP - MOVB (PDATA), TEMP - SHLW $8, TEMP - ADDL TEMP, RESULT -#undef TEMP - -RETURN: - MOVQ RESULT, result+16(FP) - RET diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64_test.go b/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64_test.go deleted file mode 100644 index 7133845a..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_amd64_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package tcpip - -import ( - "crypto/rand" - "testing" - - "golang.org/x/sys/cpu" -) - -func Test_SumAVX2(t *testing.T) { - if !cpu.X86.HasAVX2 { - t.Skipf("AVX2 unavailable") - } - - bytes := make([]byte, chunkSize) - - for size := 0; size <= chunkSize; size++ { - for count := 0; count < chunkCount; count++ { - _, err := rand.Reader.Read(bytes[:size]) - if err != nil { - t.Skipf("Rand read failed: %v", err) - } - - compat := SumCompat(bytes[:size]) - avx := SumAVX2(bytes[:size]) - - if compat != avx { - t.Errorf("Sum of length=%d mismatched", size) - } - } - } -} - -func Benchmark_SumAVX2(b *testing.B) { - if !cpu.X86.HasAVX2 { - b.Skipf("AVX2 unavailable") - } - - bytes := make([]byte, chunkSize) - - _, err := rand.Reader.Read(bytes) - if err != nil { - b.Skipf("Rand read failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - SumAVX2(bytes) - } -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64.go b/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64.go deleted file mode 100644 index c16c8fac..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64.go +++ /dev/null @@ -1,24 +0,0 @@ -package tcpip - -import ( - "unsafe" - - "golang.org/x/sys/cpu" -) - -//go:noescape -func sumAsmNeon(data unsafe.Pointer, length uintptr) uintptr - -func SumNeon(data []byte) uint32 { - if len(data) == 0 { - return 0 - } - - return uint32(sumAsmNeon(unsafe.Pointer(&data[0]), uintptr(len(data)))) -} - -func init() { - if cpu.ARM64.HasASIMD { - SumFnc = SumNeon - } -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64.s b/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64.s deleted file mode 100644 index f6d57cf0..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64.s +++ /dev/null @@ -1,118 +0,0 @@ -#include "textflag.h" - -// func sumAsmNeon(data unsafe.Pointer, length uintptr) uintptr -// -// args (8 bytes aligned): -// data unsafe.Pointer - 8 bytes - 0 offset -// length uintptr - 8 bytes - 8 offset -// result uintptr - 8 bytes - 16 offset -#define PDATA R0 -#define LENGTH R1 -#define RESULT R2 -#define VSUM V0 -TEXT ·sumAsmNeon(SB),NOSPLIT,$0-24 - MOVD data+0(FP), PDATA - MOVD length+8(FP), LENGTH - MOVD $0, RESULT - VMOVQ $0, $0, VSUM - -#define LOADED_0 V1 -#define LOADED_1 V2 -#define LOADED_2 V3 -#define LOADED_3 V4 -BATCH_32: - CMP $32, LENGTH - BLO BATCH_16 - VLD1 (PDATA), [LOADED_0.B8, LOADED_1.B8, LOADED_2.B8, LOADED_3.B8] - VREV16 LOADED_0.B8, LOADED_0.B8 - VREV16 LOADED_1.B8, LOADED_1.B8 - VREV16 LOADED_2.B8, LOADED_2.B8 - VREV16 LOADED_3.B8, LOADED_3.B8 - VUSHLL $0, LOADED_0.H4, LOADED_0.S4 - VUSHLL $0, LOADED_1.H4, LOADED_1.S4 - VUSHLL $0, LOADED_2.H4, LOADED_2.S4 - VUSHLL $0, LOADED_3.H4, LOADED_3.S4 - VADD LOADED_0.S4, VSUM.S4, VSUM.S4 - VADD LOADED_1.S4, VSUM.S4, VSUM.S4 - VADD LOADED_2.S4, VSUM.S4, VSUM.S4 - VADD LOADED_3.S4, VSUM.S4, VSUM.S4 - ADD $-32, LENGTH - ADD $32, PDATA - B BATCH_32 -#undef LOADED_0 -#undef LOADED_1 -#undef LOADED_2 -#undef LOADED_3 - -#define LOADED_0 V1 -#define LOADED_1 V2 -BATCH_16: - CMP $16, LENGTH - BLO BATCH_8 - VLD1 (PDATA), [LOADED_0.B8, LOADED_1.B8] - VREV16 LOADED_0.B8, LOADED_0.B8 - VREV16 LOADED_1.B8, LOADED_1.B8 - VUSHLL $0, LOADED_0.H4, LOADED_0.S4 - VUSHLL $0, LOADED_1.H4, LOADED_1.S4 - VADD LOADED_0.S4, VSUM.S4, VSUM.S4 - VADD LOADED_1.S4, VSUM.S4, VSUM.S4 - ADD $-16, LENGTH - ADD $16, PDATA - B BATCH_16 -#undef LOADED_0 -#undef LOADED_1 - -#define LOADED_0 V1 -BATCH_8: - CMP $8, LENGTH - BLO BATCH_2 - VLD1 (PDATA), [LOADED_0.B8] - VREV16 LOADED_0.B8, LOADED_0.B8 - VUSHLL $0, LOADED_0.H4, LOADED_0.S4 - VADD LOADED_0.S4, VSUM.S4, VSUM.S4 - ADD $-8, LENGTH - ADD $8, PDATA - B BATCH_8 -#undef LOADED_0 - -#define LOADED_L R3 -#define LOADED_H R4 -BATCH_2: - CMP $2, LENGTH - BLO BATCH_1 - MOVBU (PDATA), LOADED_H - MOVBU 1(PDATA), LOADED_L - LSL $8, LOADED_H - ORR LOADED_H, LOADED_L, LOADED_L - ADD LOADED_L, RESULT, RESULT - ADD $2, PDATA - ADD $-2, LENGTH - B BATCH_2 -#undef LOADED_H -#undef LOADED_L - -#define LOADED R3 -BATCH_1: - CMP $1, LENGTH - BLO COLLECT - MOVBU (PDATA), LOADED - LSL $8, LOADED - ADD LOADED, RESULT, RESULT - -#define EXTRACTED R3 -COLLECT: - VMOV VSUM.S[0], EXTRACTED - ADD EXTRACTED, RESULT - VMOV VSUM.S[1], EXTRACTED - ADD EXTRACTED, RESULT - VMOV VSUM.S[2], EXTRACTED - ADD EXTRACTED, RESULT - VMOV VSUM.S[3], EXTRACTED - ADD EXTRACTED, RESULT -#undef VSUM -#undef PDATA -#undef LENGTH - -RETURN: - MOVD RESULT, result+16(FP) - RET diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64_test.go b/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64_test.go deleted file mode 100644 index 20a89095..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_arm64_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package tcpip - -import ( - "crypto/rand" - "testing" - - "golang.org/x/sys/cpu" -) - -func Test_SumNeon(t *testing.T) { - if !cpu.ARM64.HasASIMD { - t.Skipf("Neon unavailable") - } - - bytes := make([]byte, chunkSize) - - for size := 0; size <= chunkSize; size++ { - for count := 0; count < chunkCount; count++ { - _, err := rand.Reader.Read(bytes[:size]) - if err != nil { - t.Skipf("Rand read failed: %v", err) - } - - compat := SumCompat(bytes[:size]) - neon := SumNeon(bytes[:size]) - - if compat != neon { - t.Errorf("Sum of length=%d mismatched", size) - } - } - } -} - -func Benchmark_SumNeon(b *testing.B) { - if !cpu.ARM64.HasASIMD { - b.Skipf("Neon unavailable") - } - - bytes := make([]byte, chunkSize) - - _, err := rand.Reader.Read(bytes) - if err != nil { - b.Skipf("Rand read failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - SumNeon(bytes) - } -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_compat.go b/listener/tun/ipstack/system/mars/tcpip/tcpip_compat.go deleted file mode 100644 index 0b9dbb01..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_compat.go +++ /dev/null @@ -1,14 +0,0 @@ -package tcpip - -func SumCompat(b []byte) (sum uint32) { - n := len(b) - if n&1 != 0 { - n-- - sum += uint32(b[n]) << 8 - } - - for i := 0; i < n; i += 2 { - sum += (uint32(b[i]) << 8) | uint32(b[i+1]) - } - return -} diff --git a/listener/tun/ipstack/system/mars/tcpip/tcpip_compat_test.go b/listener/tun/ipstack/system/mars/tcpip/tcpip_compat_test.go deleted file mode 100644 index f8595aa5..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/tcpip_compat_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package tcpip - -import ( - "crypto/rand" - "testing" -) - -const ( - chunkSize = 9000 - chunkCount = 10 -) - -func Benchmark_SumCompat(b *testing.B) { - bytes := make([]byte, chunkSize) - - _, err := rand.Reader.Read(bytes) - if err != nil { - b.Skipf("Rand read failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - SumCompat(bytes) - } -} diff --git a/listener/tun/ipstack/system/mars/tcpip/udp.go b/listener/tun/ipstack/system/mars/tcpip/udp.go deleted file mode 100644 index d2cb7f46..00000000 --- a/listener/tun/ipstack/system/mars/tcpip/udp.go +++ /dev/null @@ -1,55 +0,0 @@ -package tcpip - -import ( - "encoding/binary" -) - -const UDPHeaderSize = 8 - -type UDPPacket []byte - -func (p UDPPacket) Length() uint16 { - return binary.BigEndian.Uint16(p[4:]) -} - -func (p UDPPacket) SetLength(length uint16) { - binary.BigEndian.PutUint16(p[4:], length) -} - -func (p UDPPacket) SourcePort() uint16 { - return binary.BigEndian.Uint16(p) -} - -func (p UDPPacket) SetSourcePort(port uint16) { - binary.BigEndian.PutUint16(p, port) -} - -func (p UDPPacket) DestinationPort() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -func (p UDPPacket) SetDestinationPort(port uint16) { - binary.BigEndian.PutUint16(p[2:], port) -} - -func (p UDPPacket) Payload() []byte { - return p[UDPHeaderSize:p.Length()] -} - -func (p UDPPacket) Checksum() uint16 { - return binary.BigEndian.Uint16(p[6:]) -} - -func (p UDPPacket) SetChecksum(sum [2]byte) { - p[6] = sum[0] - p[7] = sum[1] -} - -func (p UDPPacket) ResetChecksum(psum uint32) { - p.SetChecksum(zeroChecksum) - p.SetChecksum(Checksum(psum, p)) -} - -func (p UDPPacket) Valid() bool { - return len(p) >= UDPHeaderSize && uint16(len(p)) >= p.Length() -} diff --git a/listener/tun/ipstack/system/stack.go b/listener/tun/ipstack/system/stack.go deleted file mode 100644 index b4668293..00000000 --- a/listener/tun/ipstack/system/stack.go +++ /dev/null @@ -1,227 +0,0 @@ -package system - -import ( - "encoding/binary" - "io" - "net" - "net/netip" - "runtime" - "strconv" - "sync" - "time" - - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/Dreamacro/clash/listener/tun/ipstack" - D "github.com/Dreamacro/clash/listener/tun/ipstack/commons" - "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars" - "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/nat" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/socks5" -) - -type sysStack struct { - stack io.Closer - device device.Device - - closed bool - once sync.Once - wg sync.WaitGroup -} - -func (s *sysStack) Close() error { - defer func() { - if s.device != nil { - _ = s.device.Close() - } - }() - - s.closed = true - - err := s.stack.Close() - - s.wg.Wait() - - return err -} - -func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) { - var ( - gateway = tunAddress.Masked().Addr().Next() - portal = gateway.Next() - broadcast = nnip.UnMasked(tunAddress) - ) - - stack, err := mars.StartListener(device, gateway, portal, broadcast) - if err != nil { - _ = device.Close() - - return nil, err - } - - ipStack := &sysStack{stack: stack, device: device} - - dnsAddr := dnsHijack - - tcp := func() { - defer func(tcp *nat.TCP) { - _ = tcp.Close() - }(stack.TCP()) - - for !ipStack.closed { - conn, err := stack.TCP().Accept() - if err != nil { - log.Debugln("[STACK] accept connection error: %v", err) - continue - } - - lAddr := conn.LocalAddr().(*net.TCPAddr) - rAddr := conn.RemoteAddr().(*net.TCPAddr) - - lAddrPort := netip.AddrPortFrom(nnip.IpToAddr(lAddr.IP), uint16(lAddr.Port)) - rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port)) - - if rAddrPort.Addr().IsLoopback() { - _ = conn.Close() - - continue - } - - if D.ShouldHijackDns(dnsAddr, rAddrPort) { - go func() { - buf := pool.Get(pool.UDPBufferSize) - defer func() { - _ = pool.Put(buf) - _ = conn.Close() - }() - - for { - if conn.SetReadDeadline(time.Now().Add(C.DefaultTCPTimeout)) != nil { - break - } - - length := uint16(0) - if err := binary.Read(conn, binary.BigEndian, &length); err != nil { - break - } - - if int(length) > len(buf) { - break - } - - n, err := conn.Read(buf[:length]) - if err != nil { - break - } - - msg, err := D.RelayDnsPacket(buf[:n]) - if err != nil { - break - } - - _, _ = conn.Write(msg) - } - }() - - continue - } - - metadata := &C.Metadata{ - NetWork: C.TCP, - Type: C.TUN, - SrcIP: lAddrPort.Addr(), - DstIP: rAddrPort.Addr(), - SrcPort: strconv.Itoa(lAddr.Port), - DstPort: strconv.Itoa(rAddr.Port), - AddrType: C.AtypIPv4, - Host: "", - } - - tcpIn <- context.NewConnContext(conn, metadata) - } - - ipStack.wg.Done() - } - - udp := func() { - defer func(udp *nat.UDP) { - _ = udp.Close() - }(stack.UDP()) - - for !ipStack.closed { - buf := pool.Get(pool.UDPBufferSize) - - n, lRAddr, rRAddr, err := stack.UDP().ReadFrom(buf) - if err != nil { - _ = pool.Put(buf) - break - } - - raw := buf[:n] - lAddr := lRAddr.(*net.UDPAddr) - rAddr := rRAddr.(*net.UDPAddr) - - rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port)) - - if rAddrPort.Addr().IsLoopback() || rAddrPort.Addr() == gateway { - _ = pool.Put(buf) - - continue - } - - if D.ShouldHijackDns(dnsAddr, rAddrPort) { - go func() { - msg, err := D.RelayDnsPacket(raw) - if err != nil { - _ = pool.Put(buf) - return - } - - _, _ = stack.UDP().WriteTo(msg, rAddr, lAddr) - - _ = pool.Put(buf) - }() - - continue - } - - pkt := &packet{ - local: lAddr, - data: raw, - writeBack: func(b []byte, addr net.Addr) (int, error) { - return stack.UDP().WriteTo(b, rAddr, lAddr) - }, - drop: func() { - _ = pool.Put(buf) - }, - } - - select { - case udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN): - default: - } - } - - ipStack.wg.Done() - } - - ipStack.once.Do(func() { - ipStack.wg.Add(1) - go tcp() - - numUDPWorkers := 4 - if num := runtime.GOMAXPROCS(0); num > numUDPWorkers { - numUDPWorkers = num - } - for i := 0; i < numUDPWorkers; i++ { - ipStack.wg.Add(1) - go udp() - } - }) - - return ipStack, nil -} diff --git a/listener/tun/ipstack/system/udp.go b/listener/tun/ipstack/system/udp.go deleted file mode 100644 index cb2761e8..00000000 --- a/listener/tun/ipstack/system/udp.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import "net" - -type packet struct { - local *net.UDPAddr - data []byte - writeBack func(b []byte, addr net.Addr) (int, error) - drop func() -} - -func (pkt *packet) Data() []byte { - return pkt.data -} - -func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { - return pkt.writeBack(b, addr) -} - -func (pkt *packet) Drop() { - pkt.drop() -} - -func (pkt *packet) LocalAddr() net.Addr { - return pkt.local -} diff --git a/listener/tun/tun_adapter.go b/listener/tun/tun_adapter.go deleted file mode 100644 index 24d61949..00000000 --- a/listener/tun/tun_adapter.go +++ /dev/null @@ -1,162 +0,0 @@ -package tun - -import ( - "fmt" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/cmd" - "github.com/Dreamacro/clash/config" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/tun/device" - "github.com/Dreamacro/clash/listener/tun/device/fdbased" - "github.com/Dreamacro/clash/listener/tun/device/tun" - "github.com/Dreamacro/clash/listener/tun/ipstack" - "github.com/Dreamacro/clash/listener/tun/ipstack/commons" - "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor" - "github.com/Dreamacro/clash/listener/tun/ipstack/system" - "github.com/Dreamacro/clash/log" - "net/netip" - "net/url" - "runtime" - "strings" -) - -// New TunAdapter -func New(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) { - - var ( - tunAddress = tunConf.TunAddressPrefix - devName = tunConf.Device - stackType = tunConf.Stack - autoRoute = tunConf.AutoRoute - mtu = 9000 - - tunDevice device.Device - tunStack ipstack.Stack - - err error - ) - - if devName == "" { - devName = generateDeviceName() - } - - if !tunAddress.IsValid() || !tunAddress.Addr().Is4() { - tunAddress = netip.MustParsePrefix("198.18.0.1/16") - } - - // open tun device - tunDevice, err = parseDevice(devName, uint32(mtu)) - if err != nil { - return nil, fmt.Errorf("can't open tun: %w", err) - } - - // new ip stack - switch stackType { - case C.TunGvisor: - err = tunDevice.UseEndpoint() - if err != nil { - _ = tunDevice.Close() - return nil, fmt.Errorf("can't attach endpoint to tun: %w", err) - } - - tunStack, err = gvisor.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn) - - if err != nil { - _ = tunDevice.Close() - return nil, fmt.Errorf("can't New gvisor stack: %w", err) - } - case C.TunSystem: - err = tunDevice.UseIOBased() - if err != nil { - _ = tunDevice.Close() - return nil, fmt.Errorf("can't New system stack: %w", err) - } - - tunStack, err = system.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn) - if err != nil { - _ = tunDevice.Close() - return nil, fmt.Errorf("can't New system stack: %w", err) - } - default: - // never happen - } - - // setting address and routing - err = commons.ConfigInterfaceAddress(tunDevice, tunAddress, mtu, autoRoute) - if err != nil { - _ = tunDevice.Close() - return nil, fmt.Errorf("setting interface address and routing failed: %w", err) - } - - if tunConf.AutoDetectInterface { - commons.StartDefaultInterfaceChangeMonitor() - } - - setAtLatest(stackType, devName) - - log.Infoln("TUN stack listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", tunDevice.Name(), tunAddress.Masked().Addr().Next().String(), mtu, autoRoute, stackType) - return tunStack, nil -} - -func generateDeviceName() string { - switch runtime.GOOS { - case "darwin": - return tun.Driver + "://utun" - case "windows": - return tun.Driver + "://Meta" - default: - return tun.Driver + "://Meta" - } -} - -func parseDevice(s string, mtu uint32) (device.Device, error) { - if !strings.Contains(s, "://") { - s = fmt.Sprintf("%s://%s", tun.Driver /* default driver */, s) - } - - u, err := url.Parse(s) - if err != nil { - return nil, err - } - - name := u.Host - driver := strings.ToLower(u.Scheme) - - switch driver { - case fdbased.Driver: - return fdbased.Open(name, mtu) - case tun.Driver: - return tun.Open(name, mtu) - default: - return nil, fmt.Errorf("unsupported driver: %s", driver) - } -} - -func setAtLatest(stackType C.TUNStack, devName string) { - if stackType != C.TunSystem { - return - } - - switch runtime.GOOS { - case "darwin": - // _, _ = cmd.ExecCmd("sysctl -w net.inet.ip.forwarding=1") - // _, _ = cmd.ExecCmd("sysctl -w net.inet6.ip6.forwarding=1") - case "windows": - _, _ = cmd.ExecCmd("ipconfig /renew") - case "linux": - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.forwarding = 1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_local = 1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_redirects = 1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.rp_filter = 2") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.forwarding = 1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.accept_local = 1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.accept_redirects = 1") - // _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.rp_filter = 2") - // _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.forwarding = 1", devName)) - // _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_local = 1", devName)) - // _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_redirects = 1", devName)) - // _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.rp_filter = 2", devName)) - // _, _ = cmd.ExecCmd("iptables -t filter -P FORWARD ACCEPT") - } -} diff --git a/rules/common/base.go b/rules/common/base.go index 42b4d770..abc2b434 100644 --- a/rules/common/base.go +++ b/rules/common/base.go @@ -5,7 +5,7 @@ import ( ) var ( - errPayload = errors.New("payload error") + errPayload = errors.New("payloadRule error") initFlag bool noResolve = "no-resolve" ) diff --git a/rules/common/domain.go b/rules/common/domain.go index 4a8a7d27..2f0e4f2f 100644 --- a/rules/common/domain.go +++ b/rules/common/domain.go @@ -18,11 +18,11 @@ func (d *Domain) RuleType() C.RuleType { return C.Domain } -func (d *Domain) Match(metadata *C.Metadata) bool { +func (d *Domain) Match(metadata *C.Metadata) (bool, string) { if metadata.AddrType != C.AtypDomainName { - return false + return false, "" } - return metadata.Host == d.domain + return metadata.Host == d.domain, d.adapter } func (d *Domain) Adapter() string { @@ -47,4 +47,4 @@ func NewDomain(domain string, adapter string) *Domain { } } -var _ C.Rule = (*Domain)(nil) +//var _ C.Rule = (*Domain)(nil) diff --git a/rules/common/domain_keyword.go b/rules/common/domain_keyword.go index 667b2861..58257544 100644 --- a/rules/common/domain_keyword.go +++ b/rules/common/domain_keyword.go @@ -18,12 +18,12 @@ func (dk *DomainKeyword) RuleType() C.RuleType { return C.DomainKeyword } -func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { +func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) { if metadata.AddrType != C.AtypDomainName { - return false + return false, "" } domain := metadata.Host - return strings.Contains(domain, dk.keyword) + return strings.Contains(domain, dk.keyword), dk.adapter } func (dk *DomainKeyword) Adapter() string { @@ -48,4 +48,4 @@ func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { } } -var _ C.Rule = (*DomainKeyword)(nil) +//var _ C.Rule = (*DomainKeyword)(nil) diff --git a/rules/common/domain_suffix.go b/rules/common/domain_suffix.go index c2edcd16..c9016744 100644 --- a/rules/common/domain_suffix.go +++ b/rules/common/domain_suffix.go @@ -18,12 +18,12 @@ func (ds *DomainSuffix) RuleType() C.RuleType { return C.DomainSuffix } -func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { +func (ds *DomainSuffix) Match(metadata *C.Metadata) (bool, string) { if metadata.AddrType != C.AtypDomainName { - return false + return false, "" } domain := metadata.Host - return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix + return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter } func (ds *DomainSuffix) Adapter() string { @@ -48,4 +48,4 @@ func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { } } -var _ C.Rule = (*DomainSuffix)(nil) +//var _ C.Rule = (*DomainSuffix)(nil) diff --git a/rules/common/final.go b/rules/common/final.go index e42baf92..8aa5ed7b 100644 --- a/rules/common/final.go +++ b/rules/common/final.go @@ -13,8 +13,8 @@ func (f *Match) RuleType() C.RuleType { return C.MATCH } -func (f *Match) Match(metadata *C.Metadata) bool { - return true +func (f *Match) Match(metadata *C.Metadata) (bool, string) { + return true, f.adapter } func (f *Match) Adapter() string { @@ -32,4 +32,4 @@ func NewMatch(adapter string) *Match { } } -var _ C.Rule = (*Match)(nil) +//var _ C.Rule = (*Match)(nil) diff --git a/rules/common/geoip.go b/rules/common/geoip.go index 0a7670e1..ada862d2 100644 --- a/rules/common/geoip.go +++ b/rules/common/geoip.go @@ -25,10 +25,10 @@ func (g *GEOIP) RuleType() C.RuleType { return C.GEOIP } -func (g *GEOIP) Match(metadata *C.Metadata) bool { +func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP if !ip.IsValid() { - return false + return false, "" } if strings.EqualFold(g.country, "LAN") { @@ -37,13 +37,13 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool { ip.IsLoopback() || ip.IsMulticast() || ip.IsLinkLocalUnicast() || - resolver.IsFakeBroadcastIP(ip) + resolver.IsFakeBroadcastIP(ip), g.adapter } if !C.GeodataMode { record, _ := mmdb.Instance().Country(ip.AsSlice()) - return strings.EqualFold(record.Country.IsoCode, g.country) + return strings.EqualFold(record.Country.IsoCode, g.country), g.adapter } - return g.geoIPMatcher.Match(ip.AsSlice()) + return g.geoIPMatcher.Match(ip.AsSlice()), g.adapter } func (g *GEOIP) Adapter() string { @@ -98,4 +98,4 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) return geoip, nil } -var _ C.Rule = (*GEOIP)(nil) +//var _ C.Rule = (*GEOIP)(nil) diff --git a/rules/common/geosite.go b/rules/common/geosite.go index c88fd78d..9897f349 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -23,13 +23,13 @@ func (gs *GEOSITE) RuleType() C.RuleType { return C.GEOSITE } -func (gs *GEOSITE) Match(metadata *C.Metadata) bool { +func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) { if metadata.AddrType != C.AtypDomainName { - return false + return false, "" } domain := metadata.Host - return gs.matcher.ApplyDomain(domain) + return gs.matcher.ApplyDomain(domain), gs.adapter } func (gs *GEOSITE) Adapter() string { @@ -75,4 +75,4 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { return geoSite, nil } -var _ C.Rule = (*GEOSITE)(nil) +//var _ C.Rule = (*GEOSITE)(nil) diff --git a/rules/common/in_type.go b/rules/common/in_type.go index c577c843..520c9594 100644 --- a/rules/common/in_type.go +++ b/rules/common/in_type.go @@ -13,13 +13,13 @@ type InType struct { payload string } -func (u *InType) Match(metadata *C.Metadata) bool { +func (u *InType) Match(metadata *C.Metadata) (bool, string) { for _, tp := range u.types { if metadata.Type == tp { - return true + return true, u.adapter } } - return false + return false, "" } func (u *InType) RuleType() C.RuleType { diff --git a/rules/common/ipcidr.go b/rules/common/ipcidr.go index 5ac17cf4..8ab6cf5a 100644 --- a/rules/common/ipcidr.go +++ b/rules/common/ipcidr.go @@ -35,12 +35,12 @@ func (i *IPCIDR) RuleType() C.RuleType { return C.IPCIDR } -func (i *IPCIDR) Match(metadata *C.Metadata) bool { +func (i *IPCIDR) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP if i.isSourceIP { ip = metadata.SrcIP } - return ip.IsValid() && i.ipnet.Contains(ip) + return ip.IsValid() && i.ipnet.Contains(ip), i.adapter } func (i *IPCIDR) Adapter() string { @@ -74,4 +74,4 @@ func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) return ipcidr, nil } -var _ C.Rule = (*IPCIDR)(nil) +//var _ C.Rule = (*IPCIDR)(nil) diff --git a/rules/common/ipsuffix.go b/rules/common/ipsuffix.go index 18271244..b01557dc 100644 --- a/rules/common/ipsuffix.go +++ b/rules/common/ipsuffix.go @@ -22,7 +22,7 @@ func (is *IPSuffix) RuleType() C.RuleType { return C.IPSuffix } -func (is *IPSuffix) Match(metadata *C.Metadata) bool { +func (is *IPSuffix) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP if is.isSourceIP { ip = metadata.SrcIP @@ -30,7 +30,7 @@ func (is *IPSuffix) Match(metadata *C.Metadata) bool { mIPBytes := ip.AsSlice() if len(is.ipBytes) != len(mIPBytes) { - return false + return false, "" } size := len(mIPBytes) @@ -38,15 +38,15 @@ func (is *IPSuffix) Match(metadata *C.Metadata) bool { for i := bits / 8; i > 0; i-- { if is.ipBytes[size-i] != mIPBytes[size-i] { - return false + return false, "" } } if (is.ipBytes[size-bits/8-1] << (8 - bits%8)) != (mIPBytes[size-bits/8-1] << (8 - bits%8)) { - return false + return false, "" } - return true + return true, is.adapter } func (is *IPSuffix) Adapter() string { diff --git a/rules/common/network_type.go b/rules/common/network_type.go index 107df91e..fb6b5077 100644 --- a/rules/common/network_type.go +++ b/rules/common/network_type.go @@ -36,8 +36,8 @@ 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) Match(metadata *C.Metadata) (bool, string) { + return n.network == metadata.NetWork, n.adapter } func (n *NetworkType) Adapter() string { diff --git a/rules/common/port.go b/rules/common/port.go index 06fde6c2..270e5b20 100644 --- a/rules/common/port.go +++ b/rules/common/port.go @@ -24,11 +24,11 @@ func (p *Port) RuleType() C.RuleType { return C.DstPort } -func (p *Port) Match(metadata *C.Metadata) bool { +func (p *Port) Match(metadata *C.Metadata) (bool, string) { if p.isSource { - return p.matchPortReal(metadata.SrcPort) + return p.matchPortReal(metadata.SrcPort), p.adapter } - return p.matchPortReal(metadata.DstPort) + return p.matchPortReal(metadata.DstPort), p.adapter } func (p *Port) Adapter() string { diff --git a/rules/common/process.go b/rules/common/process.go index 69d83632..9263c32d 100644 --- a/rules/common/process.go +++ b/rules/common/process.go @@ -17,12 +17,12 @@ func (ps *Process) RuleType() C.RuleType { return C.Process } -func (ps *Process) Match(metadata *C.Metadata) bool { +func (ps *Process) Match(metadata *C.Metadata) (bool, string) { if ps.nameOnly { - return strings.EqualFold(metadata.Process, ps.process) + return strings.EqualFold(metadata.Process, ps.process), ps.adapter } - return strings.EqualFold(metadata.ProcessPath, ps.process) + return strings.EqualFold(metadata.ProcessPath, ps.process), ps.adapter } func (ps *Process) Adapter() string { diff --git a/rules/common/uid.go b/rules/common/uid.go index f7ea4875..5a989f67 100644 --- a/rules/common/uid.go +++ b/rules/common/uid.go @@ -71,10 +71,10 @@ func (u *Uid) RuleType() C.RuleType { return C.Uid } -func (u *Uid) Match(metadata *C.Metadata) bool { +func (u *Uid) Match(metadata *C.Metadata) (bool, string) { srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16) if err != nil { - return false + return false, "" } var uid int32 if metadata.Uid != nil { @@ -83,15 +83,15 @@ func (u *Uid) Match(metadata *C.Metadata) bool { metadata.Uid = &uid } else { log.Warnln("[UID] could not get uid from %s", metadata.String()) - return false + return false, "" } for _, _uid := range u.uids { if _uid.Contains(uid) { - return true + return true, u.adapter } } - return false + return false, "" } func (u *Uid) Adapter() string { diff --git a/rules/logic/and.go b/rules/logic/and.go index 5a9b4d0f..a8fc1bad 100644 --- a/rules/logic/and.go +++ b/rules/logic/and.go @@ -20,9 +20,9 @@ func (A *AND) ShouldFindProcess() bool { } func NewAND(payload string, adapter string, - parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*AND, error) { + parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*AND, error) { and := &AND{Base: &common.Base{}, payload: payload, adapter: adapter} - rules, err := parseRuleByPayload(payload, parse) + rules, err := ParseRuleByPayload(payload, parse) if err != nil { return nil, err } @@ -45,14 +45,14 @@ func (A *AND) RuleType() C.RuleType { return C.AND } -func (A *AND) Match(metadata *C.Metadata) bool { +func (A *AND) Match(metadata *C.Metadata) (bool, string) { for _, rule := range A.rules { - if !rule.Match(metadata) { - return false + if m, _ := rule.Match(metadata); !m { + return false, "" } } - return true + return true, A.adapter } func (A *AND) Adapter() string { diff --git a/rules/logic/common.go b/rules/logic/common.go index 736ead43..080771ba 100644 --- a/rules/logic/common.go +++ b/rules/logic/common.go @@ -9,7 +9,7 @@ import ( _ "unsafe" ) -func parseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ([]C.Rule, error) { +func ParseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) ([]C.Rule, error) { regex, err := regexp.Compile("\\(.*\\)") if err != nil { return nil, err @@ -59,13 +59,13 @@ func payloadToRule(subPayload string, parseRule func(tp, payload, target string, return parseRule(tp, param[0], "", param[1:]) } -func parseLogicSubRule(parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { +func parseLogicSubRule(parseRule func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { return func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { - case "MATCH": - return nil, fmt.Errorf("unsupported rule type on logic rule") + case "MATCH", "SUB-RULE": + return nil, fmt.Errorf("unsupported rule type [%s] on logic rule", tp) default: - return parseRule(tp, payload, target, params) + return parseRule(tp, payload, target, params, nil) } } } diff --git a/rules/logic/logic_test.go b/rules/logic/logic_test.go index b7ea9ebe..4ec72cc4 100644 --- a/rules/logic/logic_test.go +++ b/rules/logic/logic_test.go @@ -2,14 +2,14 @@ package logic import ( "fmt" - "github.com/Dreamacro/clash/constant" + C "github.com/Dreamacro/clash/constant" RC "github.com/Dreamacro/clash/rules/common" RP "github.com/Dreamacro/clash/rules/provider" "github.com/stretchr/testify/assert" "testing" ) -func ParseRule(tp, payload, target string, params []string) (parsed constant.Rule, parseErr error) { +func ParseRule(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error) { switch tp { case "DOMAIN": parsed = RC.NewDomain(payload, target) @@ -46,6 +46,8 @@ func ParseRule(tp, payload, target string, params []string) (parsed constant.Rul parsed, parseErr = RC.NewUid(payload, target) case "IN-TYPE": parsed, parseErr = RC.NewInType(payload, target) + case "SUB-RULE": + parsed, parseErr = NewSubRule(payload, target, subRules, ParseRule) case "AND": parsed, parseErr = NewAND(payload, target, ParseRule) case "OR": @@ -54,7 +56,7 @@ func ParseRule(tp, payload, target string, params []string) (parsed constant.Rul parsed, parseErr = NewNOT(payload, target, ParseRule) case "RULE-SET": noResolve := RC.HasNoResolve(params) - parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule) + parsed, parseErr = RP.NewRuleSet(payload, target, noResolve) case "MATCH": parsed = RC.NewMatch(target) parseErr = nil @@ -70,12 +72,13 @@ func TestAND(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, "DIRECT", and.adapter) assert.Equal(t, false, and.ShouldResolveIP()) - assert.Equal(t, true, and.Match(&constant.Metadata{ + m, _ := and.Match(&C.Metadata{ Host: "baidu.com", - AddrType: constant.AtypDomainName, - NetWork: constant.TCP, + AddrType: C.AtypDomainName, + NetWork: C.TCP, DstPort: "20000", - })) + }) + assert.Equal(t, true, m) and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) assert.NotEqual(t, nil, err) @@ -87,9 +90,10 @@ func TestAND(t *testing.T) { func TestNOT(t *testing.T) { not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT", ParseRule) assert.Equal(t, nil, err) - assert.Equal(t, false, not.Match(&constant.Metadata{ + m, _ := not.Match(&C.Metadata{ DstPort: "6100", - })) + }) + assert.Equal(t, false, m) _, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT", ParseRule) assert.NotEqual(t, nil, err) @@ -101,8 +105,9 @@ func TestNOT(t *testing.T) { func TestOR(t *testing.T) { or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) assert.Equal(t, nil, err) - assert.Equal(t, true, or.Match(&constant.Metadata{ - NetWork: constant.TCP, - })) + m, _ := or.Match(&C.Metadata{ + NetWork: C.TCP, + }) + assert.Equal(t, true, m) assert.Equal(t, false, or.ShouldResolveIP()) } diff --git a/rules/logic/not.go b/rules/logic/not.go index dc14e1d1..6a5b34d8 100644 --- a/rules/logic/not.go +++ b/rules/logic/not.go @@ -17,9 +17,9 @@ func (not *NOT) ShouldFindProcess() bool { return false } -func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*NOT, error) { +func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*NOT, error) { not := &NOT{Base: &common.Base{}, adapter: adapter} - rule, err := parseRuleByPayload(payload, parse) + rule, err := ParseRuleByPayload(payload, parse) if err != nil { return nil, err } @@ -38,8 +38,16 @@ func (not *NOT) RuleType() C.RuleType { return C.NOT } -func (not *NOT) Match(metadata *C.Metadata) bool { - return not.rule == nil || !not.rule.Match(metadata) +func (not *NOT) Match(metadata *C.Metadata) (bool, string) { + if not.rule == nil { + return true, not.adapter + } + + if m, _ := not.rule.Match(metadata); !m { + return true, not.adapter + } + + return false, "" } func (not *NOT) Adapter() string { diff --git a/rules/logic/or.go b/rules/logic/or.go index 08698b02..d1aae9ac 100644 --- a/rules/logic/or.go +++ b/rules/logic/or.go @@ -23,14 +23,14 @@ func (or *OR) RuleType() C.RuleType { return C.OR } -func (or *OR) Match(metadata *C.Metadata) bool { +func (or *OR) Match(metadata *C.Metadata) (bool, string) { for _, rule := range or.rules { - if rule.Match(metadata) { - return true + if m, _ := rule.Match(metadata); m { + return true, or.adapter } } - return false + return false, "" } func (or *OR) Adapter() string { @@ -45,9 +45,9 @@ func (or *OR) ShouldResolveIP() bool { return or.needIP } -func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*OR, error) { +func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*OR, error) { or := &OR{Base: &common.Base{}, payload: payload, adapter: adapter} - rules, err := parseRuleByPayload(payload, parse) + rules, err := ParseRuleByPayload(payload, parse) if err != nil { return nil, err } diff --git a/rules/logic/sub_rules.go b/rules/logic/sub_rules.go new file mode 100644 index 00000000..b4ad7613 --- /dev/null +++ b/rules/logic/sub_rules.go @@ -0,0 +1,91 @@ +package logic + +import ( + "fmt" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/rules/common" +) + +type SubRule struct { + *common.Base + payload string + payloadRule C.Rule + subName string + subRules *map[string][]C.Rule + shouldFindProcess *bool + shouldResolveIP *bool +} + +func NewSubRule(payload, subName string, sub *map[string][]C.Rule, + parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*SubRule, error) { + payloadRule, err := ParseRuleByPayload(fmt.Sprintf("(%s)", payload), parse) + if err != nil { + return nil, err + } + if len(payloadRule) != 1 { + return nil, fmt.Errorf("Sub-Rule rule must contain one rule") + } + + return &SubRule{ + Base: &common.Base{}, + payload: payload, + payloadRule: payloadRule[0], + subName: subName, + subRules: sub, + }, nil +} + +func (r *SubRule) RuleType() C.RuleType { + return C.SubRules +} + +func (r *SubRule) Match(metadata *C.Metadata) (bool, string) { + + return match(metadata, r.subName, r.subRules) +} + +func match(metadata *C.Metadata, name string, subRules *map[string][]C.Rule) (bool, string) { + for _, rule := range (*subRules)[name] { + if m, a := rule.Match(metadata); m { + if rule.RuleType() == C.SubRules { + match(metadata, rule.Adapter(), subRules) + } else { + return m, a + } + } + } + return false, "" +} + +func (r *SubRule) ShouldResolveIP() bool { + if r.shouldResolveIP == nil { + s := false + for _, rule := range (*r.subRules)[r.subName] { + s = s || rule.ShouldResolveIP() + } + r.shouldResolveIP = &s + } + + return *r.shouldResolveIP +} + +func (r *SubRule) ShouldFindProcess() bool { + if r.shouldFindProcess == nil { + s := false + for _, rule := range (*r.subRules)[r.subName] { + s = s || rule.ShouldFindProcess() + } + r.shouldFindProcess = &s + } + + return *r.shouldFindProcess +} + +func (r *SubRule) Adapter() string { + return r.subName +} + +func (r *SubRule) Payload() string { + return r.payload +} diff --git a/rules/parser.go b/rules/parser.go index c6ca8847..6f9e229f 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -8,7 +8,7 @@ import ( RP "github.com/Dreamacro/clash/rules/provider" ) -func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { +func ParseRule(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error) { switch tp { case "DOMAIN": parsed = RC.NewDomain(payload, target) @@ -45,6 +45,8 @@ func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, pars parsed, parseErr = RC.NewUid(payload, target) case "IN-TYPE": parsed, parseErr = RC.NewInType(payload, target) + case "SUB-RULE": + parsed, parseErr = logic.NewSubRule(payload, target, subRules, ParseRule) case "AND": parsed, parseErr = logic.NewAND(payload, target, ParseRule) case "OR": @@ -53,7 +55,7 @@ func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, pars parsed, parseErr = logic.NewNOT(payload, target, ParseRule) case "RULE-SET": noResolve := RC.HasNoResolve(params) - parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule) + parsed, parseErr = RP.NewRuleSet(payload, target, noResolve) case "MATCH": parsed = RC.NewMatch(target) parseErr = nil diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 430acead..727688fc 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -16,7 +16,7 @@ type classicalStrategy struct { func (c *classicalStrategy) Match(metadata *C.Metadata) bool { for _, rule := range c.rules { - if rule.Match(metadata) { + if m, _ := rule.Match(metadata); m { return true } } @@ -66,13 +66,13 @@ func ruleParse(ruleRaw string) (string, string, []string) { return "", "", nil } -func NewClassicalStrategy(parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) *classicalStrategy { +func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy { return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { - case "MATCH": + case "MATCH", "SUB-RULE": return nil, fmt.Errorf("unsupported rule type on rule-set") default: - return parse(tp, payload, target, params) + return parse(tp, payload, target, params, nil) } }} } diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 80311af0..86e21a30 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -17,7 +17,7 @@ type ruleProviderSchema struct { Interval int `provider:"interval,omitempty"` } -func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) { +func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) { schema := &ruleProviderSchema{} decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) if err := decoder.Decode(mapping, schema); err != nil { diff --git a/rules/provider/provider.go b/rules/provider/provider.go index ce96c04f..9ae125fb 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -103,7 +103,7 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) { } func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle, - parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) P.RuleProvider { + parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider { rp := &ruleSetProvider{ behavior: behavior, } @@ -126,7 +126,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration return wrapper } -func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ruleStrategy { +func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy { switch behavior { case P.Domain: strategy := NewDomainStrategy() diff --git a/rules/provider/rule_set.go b/rules/provider/rule_set.go index 84aaf0fb..326e3b0d 100644 --- a/rules/provider/rule_set.go +++ b/rules/provider/rule_set.go @@ -23,8 +23,8 @@ func (rs *RuleSet) RuleType() C.RuleType { return C.RuleSet } -func (rs *RuleSet) Match(metadata *C.Metadata) bool { - return rs.getProviders().Match(metadata) +func (rs *RuleSet) Match(metadata *C.Metadata) (bool, string) { + return rs.getProviders().Match(metadata), rs.adapter } func (rs *RuleSet) Adapter() string { @@ -47,7 +47,7 @@ func (rs *RuleSet) getProviders() P.RuleProvider { return rs.ruleProvider } -func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*RuleSet, error) { +func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool) (*RuleSet, error) { rp, ok := RuleProviders()[ruleProviderName] if !ok { return nil, fmt.Errorf("rule set %s not found", ruleProviderName) diff --git a/transport/hysteria/obfs/dummy.go b/transport/hysteria/obfs/dummy.go new file mode 100644 index 00000000..3edff101 --- /dev/null +++ b/transport/hysteria/obfs/dummy.go @@ -0,0 +1,18 @@ +package obfs + +type DummyObfuscator struct{} + +func NewDummyObfuscator() *DummyObfuscator { + return &DummyObfuscator{} +} + +func (x *DummyObfuscator) Deobfuscate(in []byte, out []byte) int { + if len(out) < len(in) { + return 0 + } + return copy(out, in) +} + +func (x *DummyObfuscator) Obfuscate(in []byte, out []byte) int { + return copy(out, in) +} diff --git a/transport/hysteria/transport/client.go b/transport/hysteria/transport/client.go index d350b4ad..43330cd3 100644 --- a/transport/hysteria/transport/client.go +++ b/transport/hysteria/transport/client.go @@ -7,7 +7,7 @@ import ( "github.com/Dreamacro/clash/transport/hysteria/conns/faketcp" "github.com/Dreamacro/clash/transport/hysteria/conns/udp" "github.com/Dreamacro/clash/transport/hysteria/conns/wechat" - "github.com/Dreamacro/clash/transport/hysteria/obfs" + obfsPkg "github.com/Dreamacro/clash/transport/hysteria/obfs" "github.com/lucas-clemente/quic-go" "net" ) @@ -16,7 +16,7 @@ type ClientTransport struct { Dialer *net.Dialer } -func (ct *ClientTransport) quicPacketConn(proto string, server string, obfs obfs.Obfuscator, dialer PacketDialer) (net.PacketConn, error) { +func (ct *ClientTransport) quicPacketConn(proto string, server string, obfs obfsPkg.Obfuscator, dialer PacketDialer) (net.PacketConn, error) { if len(proto) == 0 || proto == "udp" { conn, err := dialer.ListenPacket() if err != nil { @@ -33,12 +33,10 @@ func (ct *ClientTransport) quicPacketConn(proto string, server string, obfs obfs if err != nil { return nil, err } - if obfs != nil { - oc := wechat.NewObfsWeChatUDPConn(conn, obfs) - return oc, nil - } else { - return conn, nil + if obfs == nil { + obfs = obfsPkg.NewDummyObfuscator() } + return wechat.NewObfsWeChatUDPConn(conn, obfs), nil } else if proto == "faketcp" { var conn *faketcp.TCPConn conn, err := faketcp.Dial("tcp", server) @@ -62,7 +60,7 @@ type PacketDialer interface { RemoteAddr(host string) (net.Addr, error) } -func (ct *ClientTransport) QUICDial(proto string, server string, tlsConfig *tls.Config, quicConfig *quic.Config, obfs obfs.Obfuscator, dialer PacketDialer) (quic.Connection, error) { +func (ct *ClientTransport) QUICDial(proto string, server string, tlsConfig *tls.Config, quicConfig *quic.Config, obfs obfsPkg.Obfuscator, dialer PacketDialer) (quic.Connection, error) { serverUDPAddr, err := dialer.RemoteAddr(server) if err != nil { return nil, err diff --git a/transport/snell/snell.go b/transport/snell/snell.go index c6b7a569..e2bd2820 100644 --- a/transport/snell/snell.go +++ b/transport/snell/snell.go @@ -241,6 +241,9 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, error) { return nil, 0, errors.New("remote address invalid") } uAddr := addr.UDPAddr() + if uAddr == nil { + return nil, 0, errors.New("parse addr error") + } length := len(payload) if n-headLen < length { diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 4ebb431b..937b5f91 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -250,6 +250,9 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) { return nil, 0, 0, errors.New("read addr error") } uAddr := addr.UDPAddr() + if uAddr == nil { + return nil, 0, 0, errors.New("parse addr error") + } if _, err = io.ReadFull(r, payload[:2]); err != nil { return nil, 0, 0, errors.New("read length error") diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 3c74e14d..ddf771b9 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -107,7 +107,7 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid func UpdateSniffer(dispatcher *sniffer.SnifferDispatcher) { configMux.Lock() - sniffer.Dispatcher = *dispatcher + sniffer.Dispatcher = dispatcher sniffingEnable = true configMux.Unlock() } @@ -269,7 +269,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) defer cancel() - rawPc, err := proxy.ListenPacketContext(ctx, metadata) + rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure()) if err != nil { if rule == nil { log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -331,9 +331,18 @@ func handleTCPConn(connCtx C.ConnContext) { return } + dialMetadata := metadata + if len(metadata.Host) > 0 { + if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { + dialMetadata.DstIP = node.Data + dialMetadata.DNSMode = C.DNSHosts + dialMetadata = dialMetadata.Pure() + } + } + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) defer cancel() - remoteConn, err := proxy.DialContext(ctx, metadata) + remoteConn, err := proxy.DialContext(ctx, dialMetadata) if err != nil { if rule == nil { log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -410,13 +419,21 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { } } - if rule.Match(metadata) { - adapter, ok := proxies[rule.Adapter()] + if matched, ada := rule.Match(metadata); matched { + adapter, ok := proxies[ada] if !ok { continue } - if adapter.Type() == C.Pass || (adapter.Unwrap(metadata) != nil && adapter.Unwrap(metadata).Type() == C.Pass) { + // parse multi-layer nesting + passed := false + for adapter := adapter; adapter != nil; adapter = adapter.Unwrap(metadata, false) { + if adapter.Type() == C.Pass { + passed = true + break + } + } + if passed { log.Debugln("%s match Pass rule", adapter.Name()) continue }