diff --git a/adapter/inbound/packet.go b/adapter/inbound/packet.go index 0e3f6c48..80683458 100644 --- a/adapter/inbound/packet.go +++ b/adapter/inbound/packet.go @@ -12,7 +12,7 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions metadata.Type = source ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr())) if p, ok := packet.(C.UDPPacketInAddr); ok { - ApplyAdditions(metadata, WithInAddr(p.InAddr())) + ApplyAdditions(metadata, WithInAddr(p.InAddr()), WithDstAddr(metadata.RawDstAddr)) } ApplyAdditions(metadata, additions...) diff --git a/adapter/outbound/patch.go b/adapter/outbound/patch.go new file mode 100644 index 00000000..1a52f191 --- /dev/null +++ b/adapter/outbound/patch.go @@ -0,0 +1,7 @@ +package outbound + +import "net" + +func (c *conn) RawConn() (net.Conn, bool) { + return c.ExtendedConn, true +} diff --git a/adapter/outboundgroup/patch.go b/adapter/outboundgroup/patch.go new file mode 100644 index 00000000..9b16fe25 --- /dev/null +++ b/adapter/outboundgroup/patch.go @@ -0,0 +1,62 @@ +package outboundgroup + +import ( + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" +) + +type ProxyGroup interface { + C.ProxyAdapter + + Providers() []provider.ProxyProvider + Proxies() []C.Proxy + Now() string +} + +func (f *Fallback) Providers() []provider.ProxyProvider { + return f.providers +} + +func (lb *LoadBalance) Providers() []provider.ProxyProvider { + return lb.providers +} + +func (f *Fallback) Proxies() []C.Proxy { + return f.GetProxies(false) +} + +func (lb *LoadBalance) Proxies() []C.Proxy { + return lb.GetProxies(false) +} + +func (lb *LoadBalance) Now() string { + return "" +} + +func (r *Relay) Providers() []provider.ProxyProvider { + return r.providers +} + +func (r *Relay) Proxies() []C.Proxy { + return r.GetProxies(false) +} + +func (r *Relay) Now() string { + return "" +} + +func (s *Selector) Providers() []provider.ProxyProvider { + return s.providers +} + +func (s *Selector) Proxies() []C.Proxy { + return s.GetProxies(false) +} + +func (u *URLTest) Providers() []provider.ProxyProvider { + return u.providers +} + +func (u *URLTest) Proxies() []C.Proxy { + return u.GetProxies(false) +} diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index feb972ab..36221049 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -18,6 +18,7 @@ import ( const ( defaultURLTestTimeout = time.Second * 5 + defaultURLTestURL = "https://www.gstatic.com/generate_204" ) type HealthCheckOption struct { @@ -148,6 +149,11 @@ func (hc *HealthCheck) stop() { } func (hc *HealthCheck) check() { + + if len(hc.proxies) == 0 { + return + } + _, _, _ = hc.singleDo.Do(func() (struct{}, error) { id := utils.NewUUIDV4().String() log.Debugln("Start New Health Checking {%s}", id) @@ -223,6 +229,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp if len(url) == 0 { interval = 0 expectedStatus = nil + url = defaultURLTestURL } return &HealthCheck{ diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index d885a546..1ea7b365 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -27,7 +27,7 @@ type healthCheckSchema struct { type proxyProviderSchema struct { Type string `provider:"type"` - Path string `provider:"path,omitempty"` + Path string `provider:"path"` URL string `provider:"url,omitempty"` Interval int `provider:"interval,omitempty"` Filter string `provider:"filter,omitempty"` @@ -59,23 +59,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide hcInterval = uint(schema.HealthCheck.Interval) } hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus) + path := C.Path.Resolve(schema.Path) var vehicle types.Vehicle switch schema.Type { case "file": - path := C.Path.Resolve(schema.Path) vehicle = resource.NewFileVehicle(path) case "http": - if schema.Path != "" { - path := C.Path.Resolve(schema.Path) - if !C.Path.IsSafePath(path) { - return nil, fmt.Errorf("%w: %s", errSubPath, path) - } - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } else { - path := C.Path.GetPathByHash("proxies", schema.URL) - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } + vehicle = resource.NewHTTPVehicle(schema.URL, path) default: return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) } diff --git a/adapter/provider/patch.go b/adapter/provider/patch.go new file mode 100644 index 00000000..0fe5e878 --- /dev/null +++ b/adapter/provider/patch.go @@ -0,0 +1,34 @@ +package provider + +import ( + "time" +) + +var ( + suspended bool +) + +type UpdatableProvider interface { + UpdatedAt() time.Time +} + +func (pp *proxySetProvider) UpdatedAt() time.Time { + return pp.Fetcher.UpdatedAt +} + +func (pp *proxySetProvider) Close() error { + pp.healthCheck.close() + pp.Fetcher.Destroy() + + return nil +} + +func (cp *compatibleProvider) Close() error { + cp.healthCheck.close() + + return nil +} + +func Suspend(s bool) { + suspended = s +} diff --git a/common/net/patch.go b/common/net/patch.go new file mode 100644 index 00000000..512ec1bd --- /dev/null +++ b/common/net/patch.go @@ -0,0 +1,11 @@ +package net + +import "net" + +func (c *BufferedConn) RawConn() (net.Conn, bool) { + if c.r.Buffered() == 0 { + return c.ExtendedConn, true + } + + return nil, false +} diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 0cfa1b6c..d2e86f36 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -70,6 +70,9 @@ func DialContext(ctx context.Context, network, address string, options ...Option } func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { + if DefaultSocketHook != nil { + return listenPacketHooked(ctx, network, address) + } cfg := applyOptions(options...) lc := &net.ListenConfig{} @@ -110,6 +113,9 @@ func GetTcpConcurrent() bool { } func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { + if DefaultSocketHook != nil { + return dialContextHooked(ctx, network, destination, port) + } address := net.JoinHostPort(destination.String(), port) netDialer := opt.netDialer diff --git a/component/dialer/patch.go b/component/dialer/patch.go new file mode 100644 index 00000000..02fab327 --- /dev/null +++ b/component/dialer/patch.go @@ -0,0 +1,47 @@ +package dialer + +import ( + "context" + "net" + "net/netip" + "syscall" +) + +type TunnelDialer func(context context.Context, network, address string) (net.Conn, error) +type SocketControl func(network, address string, conn syscall.RawConn) error + +var DefaultTunnelDialer TunnelDialer +var DefaultSocketHook SocketControl + +func DialTunnelContext(ctx context.Context, network, address string) (net.Conn, error) { + if dialer := DefaultTunnelDialer; dialer != nil { + return dialer(ctx, network, address) + } + + return DialContext(ctx, network, address) +} + +func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) { + dialer := &net.Dialer{ + Control: DefaultSocketHook, + } + + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) + if err != nil { + return nil, err + } + + if t, ok := conn.(*net.TCPConn); ok { + t.SetKeepAlive(false) + } + + return conn, nil +} + +func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) { + lc := &net.ListenConfig{ + Control: DefaultSocketHook, + } + + return lc.ListenPacket(ctx, network, address) +} diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 5db8bee9..081a31b2 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -54,7 +54,7 @@ func Verify() bool { return err == nil } -func Instance() Reader { +func DefaultInstance() Reader { once.Do(func() { mmdbPath := C.Path.MMDB() log.Debugln("Load MMDB file: %s", mmdbPath) diff --git a/component/mmdb/patch.go b/component/mmdb/patch.go new file mode 100644 index 00000000..db5cb6a4 --- /dev/null +++ b/component/mmdb/patch.go @@ -0,0 +1,31 @@ +package mmdb + +import ( + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/oschwald/geoip2-golang" + "github.com/oschwald/maxminddb-golang" +) + +var overrideMmdb *geoip2.Reader + +func InstallOverride(override *geoip2.Reader) { + overrideMmdb = overrideMmdb +} + +func Instance() Reader { + once.Do(func() { + mmdb, err := maxminddb.Open(C.Path.MMDB()) + if err != nil { + log.Fatalln("Can't load mmdb: %s", err.Error()) + } + reader = Reader{Reader: mmdb} + if mmdb.Metadata.DatabaseType == "sing-geoip" { + reader.databaseType = typeSing + } else { + reader.databaseType = typeMaxmind + } + }) + + return reader +} diff --git a/component/process/patch.go b/component/process/patch.go new file mode 100644 index 00000000..c7b9ccf3 --- /dev/null +++ b/component/process/patch.go @@ -0,0 +1,15 @@ +package process + +import "github.com/Dreamacro/clash/constant" + +type PackageNameResolver func(metadata *constant.Metadata) (string, error) + +var DefaultPackageNameResolver PackageNameResolver + +func FindPackageName(metadata *constant.Metadata) (string, error) { + if resolver := DefaultPackageNameResolver; resolver != nil { + return resolver(metadata) + } + + return "", ErrPlatformNotSupport +} diff --git a/component/resource/fetcher.go b/component/resource/fetcher.go index c92687b1..d9e70018 100644 --- a/component/resource/fetcher.go +++ b/component/resource/fetcher.go @@ -13,6 +13,10 @@ import ( "github.com/samber/lo" ) +const ( + minInterval = time.Minute * 5 +) + var ( fileMode os.FileMode = 0o666 dirMode os.FileMode = 0o755 @@ -24,8 +28,7 @@ type Fetcher[V any] struct { resourceType string name string vehicle types.Vehicle - UpdatedAt *time.Time - ticker *time.Ticker + UpdatedAt time.Time done chan struct{} hash [16]byte parser Parser[V] @@ -56,7 +59,7 @@ func (f *Fetcher[V]) Initial() (V, error) { if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { buf, err = os.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() - f.UpdatedAt = &modTime + f.UpdatedAt = modTime isLocal = true if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) { log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name()) @@ -64,6 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) { } } else { buf, err = f.vehicle.Read() + f.UpdatedAt = time.Now() } if err != nil { @@ -113,7 +117,7 @@ func (f *Fetcher[V]) Initial() (V, error) { f.hash = md5.Sum(buf) // pull contents automatically - if f.ticker != nil { + if f.interval > 0 { go f.pullLoop() } @@ -129,7 +133,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) { now := time.Now() hash := md5.Sum(buf) if bytes.Equal(f.hash[:], hash[:]) { - f.UpdatedAt = &now + f.UpdatedAt = now _ = os.Chtimes(f.vehicle.Path(), now, now) return lo.Empty[V](), true, nil } @@ -145,23 +149,31 @@ func (f *Fetcher[V]) Update() (V, bool, error) { } } - f.UpdatedAt = &now + f.UpdatedAt = now f.hash = hash return contents, false, nil } func (f *Fetcher[V]) Destroy() error { - if f.ticker != nil { + if f.interval > 0 { f.done <- struct{}{} } return nil } func (f *Fetcher[V]) pullLoop() { + initialInterval := f.interval - time.Since(f.UpdatedAt) + if initialInterval < minInterval { + initialInterval = minInterval + } + + timer := time.NewTimer(initialInterval) + defer timer.Stop() for { select { - case <-f.ticker.C: + case <-timer.C: + timer.Reset(f.interval) elm, same, err := f.Update() if err != nil { log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) @@ -178,7 +190,6 @@ func (f *Fetcher[V]) pullLoop() { f.OnUpdate(elm) } case <-f.done: - f.ticker.Stop() return } } @@ -197,17 +208,12 @@ func safeWrite(path string, buf []byte) error { } func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] { - var ticker *time.Ticker - if interval != 0 { - ticker = time.NewTicker(interval) - } return &Fetcher[V]{ name: name, - ticker: ticker, vehicle: vehicle, parser: parser, - done: make(chan struct{}, 1), + done: make(chan struct{}, 8), OnUpdate: onUpdate, interval: interval, } diff --git a/config/config.go b/config/config.go index 97a193a1..7e0884cb 100644 --- a/config/config.go +++ b/config/config.go @@ -59,7 +59,6 @@ type General struct { TCPConcurrent bool `json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `json:"find-process-mode"` Sniffing bool `json:"sniffing"` - EBpf EBpf `json:"-"` GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalUA string `json:"global-ua"` } @@ -212,11 +211,16 @@ type RawDNS struct { } type RawFallbackFilter struct { - GeoIP bool `yaml:"geoip"` - GeoIPCode string `yaml:"geoip-code"` - IPCIDR []string `yaml:"ipcidr"` - Domain []string `yaml:"domain"` - GeoSite []string `yaml:"geosite"` + GeoIP bool `yaml:"geoip" json:"geoip"` + GeoIPCode string `yaml:"geoip-code" json:"geoip-code"` + IPCIDR []string `yaml:"ipcidr" json:"ipcidr"` + Domain []string `yaml:"domain" json:"domain"` + GeoSite []string `yaml:"geosite" json:"geosite"` +} + +type RawClashForAndroid struct { + AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"` + UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"` } type RawTun struct { @@ -482,7 +486,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { startTime := time.Now() config.Experimental = &rawCfg.Experimental config.Profile = &rawCfg.Profile - config.IPTables = &rawCfg.IPTables config.TLS = &rawCfg.RawTLS general, err := parseGeneral(rawCfg) @@ -543,11 +546,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.DNS = dnsCfg - err = parseTun(rawCfg.Tun, config.General) - if err != nil { - return nil, err - } - err = parseTuicServer(rawCfg.TuicServer, config.General) if err != nil { return nil, err @@ -644,7 +642,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) { GeodataLoader: cfg.GeodataLoader, TCPConcurrent: cfg.TCPConcurrent, FindProcessMode: cfg.FindProcessMode, - EBpf: cfg.EBpf, GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalUA: cfg.GlobalUA, }, nil diff --git a/constant/geodata.go b/constant/geodata.go index 72452270..2ed0f6e6 100644 --- a/constant/geodata.go +++ b/constant/geodata.go @@ -2,7 +2,7 @@ package constant var ( GeodataMode bool - GeoIpUrl string - MmdbUrl string - GeoSiteUrl string + GeoIpUrl = "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat" + MmdbUrl = "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb" + GeoSiteUrl = "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat" ) diff --git a/constant/metadata.go b/constant/metadata.go index 5f472205..9c97899c 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -147,6 +147,9 @@ type Metadata struct { SpecialProxy string `json:"specialProxy"` SpecialRules string `json:"specialRules"` RemoteDst string `json:"remoteDestination"` + + RawSrcAddr net.Addr `json:"-"` + RawDstAddr net.Addr `json:"-"` // Only domain rule SniffHost string `json:"sniffHost"` } diff --git a/constant/patch.go b/constant/patch.go new file mode 100644 index 00000000..3b5daa4c --- /dev/null +++ b/constant/patch.go @@ -0,0 +1,11 @@ +package constant + +import "net" + +type WrappedConn interface { + RawConn() (net.Conn, bool) +} + +type WrappedPacketConn interface { + RawPacketConn() (net.PacketConn, bool) +} diff --git a/context/patch.go b/context/patch.go new file mode 100644 index 00000000..39471a2b --- /dev/null +++ b/context/patch.go @@ -0,0 +1,7 @@ +package context + +import "net" + +func (c *ConnContext) RawConn() (net.Conn, bool) { + return c.conn, true +} diff --git a/dns/dhcp.go b/dns/dhcp.go index 7d420d89..e8c9ee0c 100644 --- a/dns/dhcp.go +++ b/dns/dhcp.go @@ -1,3 +1,6 @@ +//go:build disabled +// +build disabled + package dns import ( diff --git a/dns/patch.go b/dns/patch.go new file mode 100644 index 00000000..311a57fb --- /dev/null +++ b/dns/patch.go @@ -0,0 +1,79 @@ +package dns + +import ( + "context" + + D "github.com/miekg/dns" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/dhcp" + "github.com/Dreamacro/clash/component/resolver" +) + +const SystemDNSPlaceholder = "system" + +var systemResolver *Resolver +var isolateHandler handler + +var _ dnsClient = (*dhcpClient)(nil) + +type dhcpClient struct { + enable bool +} + +func (d *dhcpClient) Address() string { + return SystemDNSPlaceholder +} + +func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { + return d.ExchangeContext(context.Background(), m) +} + +func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + if s := systemResolver; s != nil { + return s.ExchangeContext(ctx, m) + } + + return nil, dhcp.ErrNotFound +} + +func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) { + if h := isolateHandler; h != nil { + return handlerWithContext(context.Background(), h, msg) + } + + return nil, D.ErrTime +} + +func FlushCacheWithDefaultResolver() { + if r := resolver.DefaultResolver; r != nil { + r.(*Resolver).lruCache = cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)) + } +} + +func UpdateSystemDNS(addr []string) { + if len(addr) == 0 { + systemResolver = nil + } + + ns := make([]NameServer, 0, len(addr)) + for _, d := range addr { + ns = append(ns, NameServer{Addr: d}) + } + + systemResolver = NewResolver(Config{Main: ns}) +} + +func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) { + if resolver == nil { + isolateHandler = nil + + return + } + + isolateHandler = NewHandler(resolver, mapper) +} + +func newDHCPClient(ifaceName string) *dhcpClient { + return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder} +} diff --git a/dns/server.go b/dns/server.go index 5c5970db..314536e2 100644 --- a/dns/server.go +++ b/dns/server.go @@ -49,6 +49,7 @@ func (s *Server) SetHandler(handler handler) { } func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) { + UpdateIsolateHandler(resolver, mapper) if addr == address && resolver != nil { handler := NewHandler(resolver, mapper) server.SetHandler(handler) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 87e0e0b1..5ab4b1cf 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -172,7 +172,7 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList listener.ReCreateHTTP(general.Port, tunnel.Tunnel) listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel) listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel) - listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel) + // listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel) listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel) listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel) listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel) diff --git a/listener/http/patch.go b/listener/http/patch.go new file mode 100644 index 00000000..bba87301 --- /dev/null +++ b/listener/http/patch.go @@ -0,0 +1,7 @@ +package http + +import "net" + +func (l *Listener) Listener() net.Listener { + return l.listener +} diff --git a/listener/http/server.go b/listener/http/server.go index 0377d3b6..85b5cbed 100644 --- a/listener/http/server.go +++ b/listener/http/server.go @@ -65,6 +65,9 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi } continue } + if t, ok := conn.(*net.TCPConn); ok { + t.SetKeepAlive(false) + } go HandleConn(conn, tunnel, c, additions...) } }() diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 0fbfb2cc..548dacf9 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -18,7 +18,7 @@ var ( type ruleProviderSchema struct { Type string `provider:"type"` Behavior string `provider:"behavior"` - Path string `provider:"path,omitempty"` + Path string `provider:"path"` URL string `provider:"url,omitempty"` Format string `provider:"format,omitempty"` Interval int `provider:"interval,omitempty"` @@ -54,23 +54,13 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t return nil, fmt.Errorf("unsupported format type: %s", schema.Format) } + path := C.Path.Resolve(schema.Path) var vehicle P.Vehicle switch schema.Type { case "file": - path := C.Path.Resolve(schema.Path) vehicle = resource.NewFileVehicle(path) case "http": - if schema.Path != "" { - path := C.Path.Resolve(schema.Path) - if !C.Path.IsSafePath(path) { - return nil, fmt.Errorf("%w: %s", errSubPath, path) - } - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } else { - path := C.Path.GetPathByHash("rules", schema.URL) - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } - + vehicle = resource.NewHTTPVehicle(schema.URL, path) default: return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) } diff --git a/rules/provider/patch.go b/rules/provider/patch.go new file mode 100644 index 00000000..f44ebf00 --- /dev/null +++ b/rules/provider/patch.go @@ -0,0 +1,25 @@ +package provider + +import "time" + +var ( + suspended bool +) + +type UpdatableProvider interface { + UpdatedAt() time.Time +} + +func (f *ruleSetProvider) UpdatedAt() time.Time { + return f.Fetcher.UpdatedAt +} + +func (rp *ruleSetProvider) Close() error { + rp.Fetcher.Destroy() + + return nil +} + +func Suspend(s bool) { + suspended = s +} diff --git a/tunnel/connection.go b/tunnel/connection.go index 9fc4f405..475e05fd 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -83,5 +83,11 @@ func closeAllLocalCoon(lAddr string) { } func handleSocket(ctx C.ConnContext, outbound net.Conn) { + left := unwrap(ctx.Conn()) + right := unwrap(outbound) + + if relayHijack(left, right) { + return + } N.Relay(ctx.Conn(), outbound) } diff --git a/tunnel/patch.go b/tunnel/patch.go new file mode 100644 index 00000000..f4021544 --- /dev/null +++ b/tunnel/patch.go @@ -0,0 +1,73 @@ +package tunnel + +import ( + "net" + + C "github.com/Dreamacro/clash/constant" +) + +func relayHijack(left net.Conn, right net.Conn) bool { + var l *net.TCPConn + var r *net.TCPConn + var ok bool + + if l, ok = left.(*net.TCPConn); !ok { + return false + } + + if r, ok = right.(*net.TCPConn); !ok { + return false + } + + closed := make(chan struct{}) + + go func() { + defer close(closed) + + r.ReadFrom(l) + r.Close() + }() + + l.ReadFrom(r) + l.Close() + + <-closed + + return true +} + +func unwrap(conn net.Conn) net.Conn { + r := conn + + for { + w, ok := r.(C.WrappedConn) + if !ok { + break + } + rc, ok := w.RawConn() + if !ok { + break + } + r = rc + } + + return r +} + +func unwrapPacket(conn net.PacketConn) net.PacketConn { + r := conn + + for { + w, ok := r.(C.WrappedPacketConn) + if !ok { + break + } + rc, ok := w.RawPacketConn() + if !ok { + break + } + r = rc + } + + return r +} diff --git a/tunnel/statistic/patch.go b/tunnel/statistic/patch.go new file mode 100644 index 00000000..9768bde4 --- /dev/null +++ b/tunnel/statistic/patch.go @@ -0,0 +1,25 @@ +package statistic + +import ( + "net" +) + +func (m *Manager) Total() (up, down int64) { + return m.uploadTotal.Load(), m.downloadTotal.Load() +} + +func (tt *tcpTracker) RawConn() (net.Conn, bool) { + if tt.Chain.Last() == "DIRECT" { + return tt.Conn, true + } + + return nil, false +} + +func (ut *udpTracker) RawPacketConn() (net.PacketConn, bool) { + if ut.Chain.Last() == "DIRECT" { + return ut.PacketConn, true + } + + return nil, false +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index fe37d75e..7508bd82 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -47,6 +47,8 @@ var ( findProcessMode P.FindProcessMode fakeIPRange netip.Prefix + + procesCache string ) type tunnel struct{} @@ -627,6 +629,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { metadata.Process = filepath.Base(path) metadata.ProcessPath = path metadata.Uid = uid + if procesCache != metadata.Process { + log.Debugln("[Process] %s from process %s", metadata.String(), path) + } + procesCache = metadata.Process } }