diff --git a/adapter/adapter.go b/adapter/adapter.go index 8136827a..6de7fb92 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -10,6 +10,7 @@ import ( "net/netip" "net/url" "strconv" + "strings" "time" "github.com/metacubex/mihomo/common/atomic" @@ -18,6 +19,7 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/puzpuzpuz/xsync/v3" ) @@ -39,6 +41,11 @@ type Proxy struct { extra *xsync.MapOf[string, *internalProxyState] } +// Adapter implements C.Proxy +func (p *Proxy) Adapter() C.ProxyAdapter { + return p.ProxyAdapter +} + // AliveForTestUrl implements C.Proxy func (p *Proxy) AliveForTestUrl(url string) bool { if state, ok := p.extra.Load(url); ok { @@ -255,10 +262,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In if unifiedDelay { second := time.Now() - resp, err = client.Do(req) - if err == nil { + var ignoredErr error + var secondResp *http.Response + secondResp, ignoredErr = client.Do(req) + if ignoredErr == nil { + resp = secondResp _ = resp.Body.Close() start = second + } else { + if strings.HasPrefix(url, "http://") { + log.Errorln("%s failed to get the second response from %s: %v", p.Name(), url, ignoredErr) + log.Warnln("It is recommended to use HTTPS for provider.health-check.url and group.url to ensure better reliability. Due to some proxy providers hijacking test addresses and not being compatible with repeated HEAD requests, using HTTP may result in failed tests.") + } } } diff --git a/adapter/inbound/listen.go b/adapter/inbound/listen.go index 1b86c811..318c9675 100644 --- a/adapter/inbound/listen.go +++ b/adapter/inbound/listen.go @@ -3,6 +3,9 @@ package inbound import ( "context" "net" + "sync" + + "github.com/metacubex/mihomo/component/keepalive" "github.com/metacubex/tfo-go" ) @@ -11,28 +14,47 @@ var ( lc = tfo.ListenConfig{ DisableTFO: true, } + mutex sync.RWMutex ) func SetTfo(open bool) { + mutex.Lock() + defer mutex.Unlock() lc.DisableTFO = !open } func Tfo() bool { + mutex.RLock() + defer mutex.RUnlock() return !lc.DisableTFO } func SetMPTCP(open bool) { + mutex.Lock() + defer mutex.Unlock() setMultiPathTCP(&lc.ListenConfig, open) } func MPTCP() bool { + mutex.RLock() + defer mutex.RUnlock() return getMultiPathTCP(&lc.ListenConfig) } func ListenContext(ctx context.Context, network, address string) (net.Listener, error) { + mutex.RLock() + defer mutex.RUnlock() return lc.Listen(ctx, network, address) } func Listen(network, address string) (net.Listener, error) { return ListenContext(context.Background(), network, address) } + +func init() { + keepalive.SetDisableKeepAliveCallback.Register(func(b bool) { + mutex.Lock() + defer mutex.Unlock() + keepalive.SetNetListenConfig(&lc.ListenConfig) + }) +} diff --git a/adapter/inbound/listen_notwindows.go b/adapter/inbound/listen_notwindows.go new file mode 100644 index 00000000..8fdfb7b8 --- /dev/null +++ b/adapter/inbound/listen_notwindows.go @@ -0,0 +1,14 @@ +//go:build !windows + +package inbound + +import ( + "net" + "os" +) + +const SupportNamedPipe = false + +func ListenNamedPipe(path string) (net.Listener, error) { + return nil, os.ErrInvalid +} diff --git a/adapter/inbound/listen_windows.go b/adapter/inbound/listen_windows.go new file mode 100644 index 00000000..d19239da --- /dev/null +++ b/adapter/inbound/listen_windows.go @@ -0,0 +1,32 @@ +package inbound + +import ( + "net" + "os" + + "github.com/metacubex/wireguard-go/ipc/namedpipe" + "golang.org/x/sys/windows" +) + +const SupportNamedPipe = true + +// windowsSDDL is the Security Descriptor set on the namedpipe. +// It provides read/write access to all users and the local system. +const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)" + +func ListenNamedPipe(path string) (net.Listener, error) { + sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL") + if sddl == "" { + sddl = windowsSDDL + } + securityDescriptor, err := windows.SecurityDescriptorFromString(sddl) + if err != nil { + return nil, err + } + namedpipeLC := namedpipe.ListenConfig{ + SecurityDescriptor: securityDescriptor, + InputBufferSize: 256 * 1024, + OutputBufferSize: 256 * 1024, + } + return namedpipeLC.Listen(path) +} diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 7114045d..dbde5593 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,19 +3,12 @@ package outbound import ( "context" "errors" - "os" - "strconv" - - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" - "github.com/metacubex/mihomo/constant/features" ) -var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR")) - type Direct struct { *Base loopBack *loopback.Detector @@ -28,30 +21,25 @@ type DirectOption struct { // DialContext implements C.ProxyAdapter func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - if !features.CMFA && !DisableLoopBackDetector { - if err := d.loopBack.CheckConn(metadata); err != nil { - return nil, err - } + if err := d.loopBack.CheckConn(metadata); err != nil { + return nil, err } - opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) + opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver)) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) if err != nil { return nil, err } - N.TCPKeepAlive(c) return d.loopBack.NewConn(NewConn(c, d)), nil } // ListenPacketContext implements C.ProxyAdapter func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - if !features.CMFA && !DisableLoopBackDetector { - if err := d.loopBack.CheckPacketConn(metadata); err != nil { - return nil, err - } + if err := d.loopBack.CheckPacketConn(metadata); err != nil { + return nil, err } // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr if !metadata.Resolved() { - ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver) + ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver) if err != nil { return nil, errors.New("can't resolve ip") } diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index b837e49a..ebb1d67c 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -7,13 +7,11 @@ import ( "encoding/base64" "errors" "fmt" - "io" "net" "net/http" "strconv" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" @@ -76,7 +74,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad if err != nil { return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index ccab16c1..55c66c45 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -69,7 +69,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer { return &hyDialerWithContext{ ctx: context.Background(), - hyDialer: func(network string) (net.PacketConn, error) { + hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) { var err error var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...) if len(h.option.DialerProxy) > 0 { @@ -78,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack return nil, err } } - rAddrPort, _ := netip.ParseAddrPort(h.Addr()) + rAddrPort, _ := netip.ParseAddrPort(rAddr.String()) return cDialer.ListenPacket(ctx, network, "", rAddrPort) }, remoteAddr: func(addr string) (net.Addr, error) { @@ -131,11 +131,7 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) { } func NewHysteria(option HysteriaOption) (*Hysteria, error) { - clientTransport := &transport.ClientTransport{ - Dialer: &net.Dialer{ - Timeout: 8 * time.Second, - }, - } + clientTransport := &transport.ClientTransport{} addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) ports := option.Ports @@ -284,7 +280,7 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { } type hyDialerWithContext struct { - hyDialer func(network string) (net.PacketConn, error) + hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error) ctx context.Context remoteAddr func(host string) (net.Addr, error) } @@ -294,7 +290,7 @@ func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, erro if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil { network = dialer.ParseNetwork(network, addrPort.Addr()) } - return h.hyDialer(network) + return h.hyDialer(network, rAddr) } func (h *hyDialerWithContext) Context() context.Context { diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 88fb8456..021fbc0a 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -149,7 +149,6 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index 07d78047..437695b4 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -80,7 +80,6 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia if err != nil { return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index 76ed4be9..f6a4b4f9 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -6,7 +6,6 @@ import ( "net" "strconv" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" @@ -94,7 +93,6 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.addr, err) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -122,7 +120,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, err } - N.TCPKeepAlive(c) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) err = snell.WriteUDPHeader(c, s.version) @@ -207,8 +204,7 @@ func NewSnell(option SnellOption) (*Snell, error) { if err != nil { return nil, err } - - N.TCPKeepAlive(c) + return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil }) } diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index c17ee6a7..1908167a 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -10,7 +10,6 @@ import ( "net/netip" "strconv" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" @@ -82,7 +81,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -128,7 +126,6 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, safeConnClose(c, err) }(c) - N.TCPKeepAlive(c) var user *socks5.User if ss.user != "" { user = &socks5.User{ diff --git a/adapter/outbound/ssh.go b/adapter/outbound/ssh.go index 8b2776a6..9e23b463 100644 --- a/adapter/outbound/ssh.go +++ b/adapter/outbound/ssh.go @@ -77,7 +77,6 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string) if err != nil { return nil, err } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 938a8858..b3a611af 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -9,7 +9,6 @@ import ( "net/http" "strconv" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" @@ -154,7 +153,6 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -212,7 +210,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me defer func(c net.Conn) { safeConnClose(c, err) }(c) - N.TCPKeepAlive(c) c, err = t.plainStream(ctx, c) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) @@ -314,7 +311,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) } - N.TCPKeepAlive(c) return c, nil } diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index 2c85c7c8..9f0636a6 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -55,7 +55,7 @@ func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr, return nil, err } - ip, err := resolver.ResolveProxyServerHost(ctx, host) + ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) if err != nil { return nil, err } @@ -71,12 +71,12 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref var fallback netip.Addr switch prefer { case C.IPv4Only: - ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host) + ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver) case C.IPv6Only: - ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host) + ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver) case C.IPv6Prefer: var ips []netip.Addr - ips, err = resolver.LookupIPProxyServerHost(ctx, host) + ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) if err == nil { for _, addr := range ips { if addr.Is6() { @@ -92,7 +92,7 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref default: // C.IPv4Prefer, C.DualStack and other var ips []netip.Addr - ips, err = resolver.LookupIPProxyServerHost(ctx, host) + ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) if err == nil { for _, addr := range ips { if addr.Is4() { diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index b18bf4da..79058874 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -262,7 +262,6 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -327,7 +326,6 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -574,7 +572,6 @@ func NewVless(option VlessOption) (*Vless, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - N.TCPKeepAlive(c) return c, nil } diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 7d5a7224..8797374d 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -312,7 +312,6 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -373,7 +372,6 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -473,7 +471,6 @@ func NewVmess(option VmessOption) (*Vmess, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - N.TCPKeepAlive(c) return c, nil } diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index 43046384..03145c37 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -44,7 +44,7 @@ type WireGuard struct { device wireguardGoDevice tunDevice wireguard.Device dialer proxydialer.SingDialer - resolver *dns.Resolver + resolver resolver.Resolver refP *refProxyAdapter initOk atomic.Bool @@ -272,6 +272,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { }, } if option.AmneziaWGOption != nil { + outbound.bind.SetParseReserved(false) // AmneziaWG don't need parse reserved outbound.device = amnezia.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers) } else { outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers) diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 738ed154..048cc34c 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -204,7 +204,7 @@ func strategyStickySessions(url string) strategyFn { for i := 1; i < maxRetry; i++ { proxy := proxies[nowIdx] if proxy.AliveForTestUrl(url) { - if nowIdx != idx { + if !has || nowIdx != idx { lruCache.Set(key, nowIdx) } diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go index 84216377..66b2510c 100644 --- a/adapter/outboundgroup/util.go +++ b/adapter/outboundgroup/util.go @@ -4,3 +4,7 @@ type SelectAble interface { Set(string) error ForceSet(name string) } + +var _ SelectAble = (*Fallback)(nil) +var _ SelectAble = (*URLTest)(nil) +var _ SelectAble = (*Selector)(nil) diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 79a752a6..2fe9633c 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -1,11 +1,9 @@ package provider import ( - "context" "encoding/json" "errors" "fmt" - "net/http" "reflect" "runtime" "strings" @@ -14,7 +12,7 @@ import ( "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/common/convert" "github.com/metacubex/mihomo/common/utils" - mihomoHttp "github.com/metacubex/mihomo/component/http" + "github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/resource" C "github.com/metacubex/mihomo/constant" types "github.com/metacubex/mihomo/constant/provider" @@ -80,7 +78,9 @@ func (pp *proxySetProvider) Initial() error { if err != nil { return err } - pp.getSubscriptionInfo() + if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" { + pp.SetSubscriptionInfo(subscriptionInfo) + } pp.closeAllConnections() return nil } @@ -117,35 +117,14 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { } } -func (pp *proxySetProvider) getSubscriptionInfo() { - if pp.VehicleType() != types.HTTP { - return - } - go func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) - defer cancel() - resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(), - http.MethodGet, nil, nil, pp.Vehicle().Proxy()) - if err != nil { - return - } - defer resp.Body.Close() +func (pp *proxySetProvider) SetSubscriptionInfo(userInfo string) { + pp.subscriptionInfo = NewSubscriptionInfo(userInfo) +} - userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) - if userInfoStr == "" { - resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(), - http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy()) - if err != nil { - return - } - defer resp2.Body.Close() - userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo")) - if userInfoStr == "" { - return - } - } - pp.subscriptionInfo = NewSubscriptionInfo(userInfoStr) - }() +func (pp *proxySetProvider) SetProvider(provider types.ProxyProvider) { + if httpVehicle, ok := pp.Vehicle().(*resource.HTTPVehicle); ok { + httpVehicle.SetProvider(provider) + } } func (pp *proxySetProvider) closeAllConnections() { @@ -196,6 +175,9 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd)) pd.Fetcher = fetcher wrapper := &ProxySetProvider{pd} + if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok { + httpVehicle.SetProvider(wrapper) + } runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close) return wrapper, nil } @@ -205,16 +187,21 @@ func (pp *ProxySetProvider) Close() error { return pp.proxySetProvider.Close() } +func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) { + pp.proxySetProvider.SetProvider(provider) +} + // CompatibleProvider for auto gc type CompatibleProvider struct { *compatibleProvider } type compatibleProvider struct { - name string - healthCheck *HealthCheck - proxies []C.Proxy - version uint32 + name string + healthCheck *HealthCheck + subscriptionInfo *SubscriptionInfo + proxies []C.Proxy + version uint32 } func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { @@ -284,6 +271,10 @@ func (cp *compatibleProvider) Close() error { return nil } +func (cp *compatibleProvider) SetSubscriptionInfo(userInfo string) { + cp.subscriptionInfo = NewSubscriptionInfo(userInfo) +} + func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { if len(proxies) == 0 { return nil, errors.New("provider need one proxy at least") @@ -313,7 +304,6 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { return func(elm []C.Proxy) { pd.setProxies(elm) pd.version += 1 - pd.getSubscriptionInfo() } } diff --git a/adapter/provider/subscription_info.go b/adapter/provider/subscription_info.go index b72c7b61..412b4342 100644 --- a/adapter/provider/subscription_info.go +++ b/adapter/provider/subscription_info.go @@ -16,8 +16,7 @@ type SubscriptionInfo struct { } func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) { - userinfo = strings.ToLower(userinfo) - userinfo = strings.ReplaceAll(userinfo, " ", "") + userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "") si = new(SubscriptionInfo) for _, field := range strings.Split(userinfo, ";") { diff --git a/common/net/tcp_keepalive.go b/common/net/tcp_keepalive.go deleted file mode 100644 index 047a1c05..00000000 --- a/common/net/tcp_keepalive.go +++ /dev/null @@ -1,23 +0,0 @@ -package net - -import ( - "net" - "runtime" - "time" -) - -var ( - KeepAliveIdle = 0 * time.Second - KeepAliveInterval = 0 * time.Second - DisableKeepAlive = false -) - -func TCPKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - if runtime.GOOS == "android" || DisableKeepAlive { - _ = tcp.SetKeepAlive(false) - } else { - tcpKeepAlive(tcp) - } - } -} diff --git a/common/net/tcp_keepalive_go122.go b/common/net/tcp_keepalive_go122.go deleted file mode 100644 index 12873168..00000000 --- a/common/net/tcp_keepalive_go122.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !go1.23 - -package net - -import "net" - -func tcpKeepAlive(tcp *net.TCPConn) { - _ = tcp.SetKeepAlive(true) - _ = tcp.SetKeepAlivePeriod(KeepAliveInterval) -} diff --git a/common/net/tcp_keepalive_go123.go b/common/net/tcp_keepalive_go123.go deleted file mode 100644 index 2dd4754b..00000000 --- a/common/net/tcp_keepalive_go123.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build go1.23 - -package net - -import "net" - -func tcpKeepAlive(tcp *net.TCPConn) { - config := net.KeepAliveConfig{ - Enable: true, - Idle: KeepAliveIdle, - Interval: KeepAliveInterval, - } - if !SupportTCPKeepAliveCount() { - // it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1 - // for Count on those old Windows if you intend to customize the TCP keep-alive settings. - config.Count = -1 - } - _ = tcp.SetKeepAliveConfig(config) -} diff --git a/common/utils/hash.go b/common/utils/hash.go new file mode 100644 index 00000000..6957e2c3 --- /dev/null +++ b/common/utils/hash.go @@ -0,0 +1,62 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" + "errors" +) + +// HashType warps hash array inside struct +// someday can change to other hash algorithm simply +type HashType struct { + md5 [md5.Size]byte // MD5 +} + +func MakeHash(data []byte) HashType { + return HashType{md5.Sum(data)} +} + +func (h HashType) Equal(hash HashType) bool { + return h.md5 == hash.md5 +} + +func (h HashType) Bytes() []byte { + return h.md5[:] +} + +func (h HashType) String() string { + return hex.EncodeToString(h.Bytes()) +} + +func (h HashType) MarshalText() ([]byte, error) { + return []byte(h.String()), nil +} + +func (h *HashType) UnmarshalText(data []byte) error { + if hex.DecodedLen(len(data)) != md5.Size { + return errors.New("invalid hash length") + } + _, err := hex.Decode(h.md5[:], data) + return err +} + +func (h HashType) MarshalBinary() ([]byte, error) { + return h.md5[:], nil +} + +func (h *HashType) UnmarshalBinary(data []byte) error { + if len(data) != md5.Size { + return errors.New("invalid hash length") + } + copy(h.md5[:], data) + return nil +} + +func (h HashType) Len() int { + return len(h.md5) +} + +func (h HashType) IsValid() bool { + var zero HashType + return h != zero +} diff --git a/component/auth/auth.go b/component/auth/auth.go index b52fa135..176b21d7 100644 --- a/component/auth/auth.go +++ b/component/auth/auth.go @@ -5,6 +5,11 @@ type Authenticator interface { Users() []string } +type AuthStore interface { + Authenticator() Authenticator + SetAuthenticator(Authenticator) +} + type AuthUser struct { User string Pass string diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 41f79b8e..4fd051ef 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/metacubex/mihomo/component/keepalive" "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/log" ) @@ -144,6 +145,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po } dialer := netDialer.(*net.Dialer) + keepalive.SetNetDialer(dialer) if opt.mpTcp { setMultiPathTCP(dialer) } @@ -338,26 +340,18 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso return nil, "-1", err } + if preferResolver == nil { + preferResolver = resolver.ProxyServerHostResolver + } + var ips []netip.Addr switch network { case "tcp4", "udp4": - if preferResolver == nil { - ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host) - } else { - ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver) - } + ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver) case "tcp6", "udp6": - if preferResolver == nil { - ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host) - } else { - ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver) - } + ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver) default: - if preferResolver == nil { - ips, err = resolver.LookupIPProxyServerHost(ctx, host) - } else { - ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver) - } + ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver) } if err != nil { return nil, "-1", fmt.Errorf("dns resolve failed: %w", err) diff --git a/component/dialer/resolver.go b/component/dialer/resolver.go deleted file mode 100644 index ea38a90e..00000000 --- a/component/dialer/resolver.go +++ /dev/null @@ -1,29 +0,0 @@ -package dialer - -import ( - "context" - "net" - "net/netip" -) - -func init() { - // We must use this DialContext to query DNS - // when using net default resolver. - net.DefaultResolver.PreferGo = true - net.DefaultResolver.Dial = resolverDialContext -} - -func resolverDialContext(ctx context.Context, network, address string) (net.Conn, error) { - d := &net.Dialer{} - - interfaceName := DefaultInterface.Load() - - if interfaceName != "" { - dstIP, err := netip.ParseAddr(address) - if err == nil { - _ = bindIfaceToDialer(interfaceName, d, network, dstIP) - } - } - - return d.DialContext(ctx, network, address) -} diff --git a/component/fakeip/cachefile.go b/component/fakeip/cachefile.go index 6f0cc48b..92d09721 100644 --- a/component/fakeip/cachefile.go +++ b/component/fakeip/cachefile.go @@ -7,46 +7,32 @@ import ( ) type cachefileStore struct { - cache *cachefile.CacheFile + cache *cachefile.FakeIpStore } // GetByHost implements store.GetByHost func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) { - elm := c.cache.GetFakeip([]byte(host)) - if elm == nil { - return netip.Addr{}, false - } - - if len(elm) == 4 { - return netip.AddrFrom4(*(*[4]byte)(elm)), true - } else { - return netip.AddrFrom16(*(*[16]byte)(elm)), true - } + return c.cache.GetByHost(host) } // PutByHost implements store.PutByHost func (c *cachefileStore) PutByHost(host string, ip netip.Addr) { - c.cache.PutFakeip([]byte(host), ip.AsSlice()) + c.cache.PutByHost(host, ip) } // GetByIP implements store.GetByIP func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) { - elm := c.cache.GetFakeip(ip.AsSlice()) - if elm == nil { - return "", false - } - return string(elm), true + return c.cache.GetByIP(ip) } // PutByIP implements store.PutByIP func (c *cachefileStore) PutByIP(ip netip.Addr, host string) { - c.cache.PutFakeip(ip.AsSlice(), []byte(host)) + c.cache.PutByIP(ip, host) } // DelByIP implements store.DelByIP func (c *cachefileStore) DelByIP(ip netip.Addr) { - addr := ip.AsSlice() - c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr)) + c.cache.DelByIP(ip) } // Exist implements store.Exist @@ -63,3 +49,7 @@ func (c *cachefileStore) CloneTo(store store) {} func (c *cachefileStore) FlushFakeIP() error { return c.cache.FlushFakeIP() } + +func newCachefileStore(cache *cachefile.CacheFile) *cachefileStore { + return &cachefileStore{cache.FakeIpStore()} +} diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 12c06332..41b848b3 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -201,9 +201,7 @@ func New(options Options) (*Pool, error) { ipnet: options.IPNet, } if options.Persistence { - pool.store = &cachefileStore{ - cache: cachefile.Cache(), - } + pool.store = newCachefileStore(cachefile.Cache()) } else { pool.store = newMemoryStore(options.Size) } diff --git a/component/fakeip/pool_test.go b/component/fakeip/pool_test.go index ee607b68..be78b87c 100644 --- a/component/fakeip/pool_test.go +++ b/component/fakeip/pool_test.go @@ -43,9 +43,7 @@ func createCachefileStore(options Options) (*Pool, string, error) { return nil, "", err } - pool.store = &cachefileStore{ - cache: &cachefile.CacheFile{DB: db}, - } + pool.store = newCachefileStore(&cachefile.CacheFile{DB: db}) return pool, f.Name(), nil } diff --git a/component/http/http.go b/component/http/http.go index 3fc06da3..63ea5be7 100644 --- a/component/http/http.go +++ b/component/http/http.go @@ -12,6 +12,7 @@ import ( "time" "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/listener/inner" ) @@ -71,8 +72,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st if conn, err := inner.HandleTcp(address, specialProxy); err == nil { return conn, nil } else { - d := net.Dialer{} - return d.DialContext(ctx, network, address) + return dialer.DialContext(ctx, network, address) } }, TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}), diff --git a/component/iface/iface.go b/component/iface/iface.go index 272ee737..a0fa4d5b 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -15,6 +15,7 @@ type Interface struct { Name string Addresses []netip.Prefix HardwareAddr net.HardwareAddr + Flags net.Flags } var ( @@ -66,6 +67,7 @@ func Interfaces() (map[string]*Interface, error) { Name: iface.Name, Addresses: ipNets, HardwareAddr: iface.HardwareAddr, + Flags: iface.Flags, } } diff --git a/component/keepalive/tcp_keepalive.go b/component/keepalive/tcp_keepalive.go new file mode 100644 index 00000000..9b24c45a --- /dev/null +++ b/component/keepalive/tcp_keepalive.go @@ -0,0 +1,65 @@ +package keepalive + +import ( + "net" + "runtime" + "time" + + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/utils" +) + +var ( + keepAliveIdle = atomic.NewTypedValue[time.Duration](0 * time.Second) + keepAliveInterval = atomic.NewTypedValue[time.Duration](0 * time.Second) + disableKeepAlive = atomic.NewBool(false) + + SetDisableKeepAliveCallback = utils.NewCallback[bool]() +) + +func SetKeepAliveIdle(t time.Duration) { + keepAliveIdle.Store(t) +} + +func SetKeepAliveInterval(t time.Duration) { + keepAliveInterval.Store(t) +} + +func KeepAliveIdle() time.Duration { + return keepAliveIdle.Load() +} + +func KeepAliveInterval() time.Duration { + return keepAliveInterval.Load() +} + +func SetDisableKeepAlive(disable bool) { + if runtime.GOOS == "android" { + setDisableKeepAlive(false) + } else { + setDisableKeepAlive(disable) + } +} + +func setDisableKeepAlive(disable bool) { + disableKeepAlive.Store(disable) + SetDisableKeepAliveCallback.Emit(disable) +} + +func DisableKeepAlive() bool { + return disableKeepAlive.Load() +} + +func SetNetDialer(dialer *net.Dialer) { + setNetDialer(dialer) +} + +func SetNetListenConfig(lc *net.ListenConfig) { + setNetListenConfig(lc) +} + +func TCPKeepAlive(c net.Conn) { + if tcp, ok := c.(*net.TCPConn); ok && tcp != nil { + tcpKeepAlive(tcp) + } +} diff --git a/component/keepalive/tcp_keepalive_go122.go b/component/keepalive/tcp_keepalive_go122.go new file mode 100644 index 00000000..5d88827d --- /dev/null +++ b/component/keepalive/tcp_keepalive_go122.go @@ -0,0 +1,30 @@ +//go:build !go1.23 + +package keepalive + +import "net" + +func tcpKeepAlive(tcp *net.TCPConn) { + if DisableKeepAlive() { + _ = tcp.SetKeepAlive(false) + } else { + _ = tcp.SetKeepAlive(true) + _ = tcp.SetKeepAlivePeriod(KeepAliveInterval()) + } +} + +func setNetDialer(dialer *net.Dialer) { + if DisableKeepAlive() { + dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled. + } else { + dialer.KeepAlive = KeepAliveInterval() + } +} + +func setNetListenConfig(lc *net.ListenConfig) { + if DisableKeepAlive() { + lc.KeepAlive = -1 // If negative, keep-alive probes are disabled. + } else { + lc.KeepAlive = KeepAliveInterval() + } +} diff --git a/component/keepalive/tcp_keepalive_go123.go b/component/keepalive/tcp_keepalive_go123.go new file mode 100644 index 00000000..4c08118b --- /dev/null +++ b/component/keepalive/tcp_keepalive_go123.go @@ -0,0 +1,45 @@ +//go:build go1.23 + +package keepalive + +import "net" + +func keepAliveConfig() net.KeepAliveConfig { + config := net.KeepAliveConfig{ + Enable: true, + Idle: KeepAliveIdle(), + Interval: KeepAliveInterval(), + } + if !SupportTCPKeepAliveCount() { + // it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1 + // for Count on those old Windows if you intend to customize the TCP keep-alive settings. + config.Count = -1 + } + return config +} + +func tcpKeepAlive(tcp *net.TCPConn) { + if DisableKeepAlive() { + _ = tcp.SetKeepAlive(false) + } else { + _ = tcp.SetKeepAliveConfig(keepAliveConfig()) + } +} + +func setNetDialer(dialer *net.Dialer) { + if DisableKeepAlive() { + dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled. + dialer.KeepAliveConfig.Enable = false + } else { + dialer.KeepAliveConfig = keepAliveConfig() + } +} + +func setNetListenConfig(lc *net.ListenConfig) { + if DisableKeepAlive() { + lc.KeepAlive = -1 // If negative, keep-alive probes are disabled. + lc.KeepAliveConfig.Enable = false + } else { + lc.KeepAliveConfig = keepAliveConfig() + } +} diff --git a/common/net/tcp_keepalive_go123_unix.go b/component/keepalive/tcp_keepalive_go123_unix.go similarity index 91% rename from common/net/tcp_keepalive_go123_unix.go rename to component/keepalive/tcp_keepalive_go123_unix.go index 0ead7ca4..8033cc6c 100644 --- a/common/net/tcp_keepalive_go123_unix.go +++ b/component/keepalive/tcp_keepalive_go123_unix.go @@ -1,6 +1,6 @@ //go:build go1.23 && unix -package net +package keepalive func SupportTCPKeepAliveIdle() bool { return true diff --git a/common/net/tcp_keepalive_go123_windows.go b/component/keepalive/tcp_keepalive_go123_windows.go similarity index 99% rename from common/net/tcp_keepalive_go123_windows.go rename to component/keepalive/tcp_keepalive_go123_windows.go index 8f1e61f9..2462e80c 100644 --- a/common/net/tcp_keepalive_go123_windows.go +++ b/component/keepalive/tcp_keepalive_go123_windows.go @@ -2,7 +2,7 @@ // copy and modify from golang1.23's internal/syscall/windows/version_windows.go -package net +package keepalive import ( "errors" diff --git a/component/loopback/detector.go b/component/loopback/detector.go index 8ec96a9d..c639ab22 100644 --- a/component/loopback/detector.go +++ b/component/loopback/detector.go @@ -4,14 +4,25 @@ import ( "errors" "fmt" "net/netip" + "os" + "strconv" "github.com/metacubex/mihomo/common/callback" "github.com/metacubex/mihomo/component/iface" C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" "github.com/puzpuzpuz/xsync/v3" ) +var disableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR")) + +func init() { + if features.CMFA { + disableLoopBackDetector = true + } +} + var ErrReject = errors.New("reject loopback connection") type Detector struct { @@ -20,6 +31,9 @@ type Detector struct { } func NewDetector() *Detector { + if disableLoopBackDetector { + return nil + } return &Detector{ connMap: xsync.NewMapOf[netip.AddrPort, struct{}](), packetConnMap: xsync.NewMapOf[uint16, struct{}](), @@ -27,6 +41,9 @@ func NewDetector() *Detector { } func (l *Detector) NewConn(conn C.Conn) C.Conn { + if l == nil || l.connMap == nil { + return conn + } metadata := C.Metadata{} if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { return conn @@ -42,6 +59,9 @@ func (l *Detector) NewConn(conn C.Conn) C.Conn { } func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn { + if l == nil || l.packetConnMap == nil { + return conn + } metadata := C.Metadata{} if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { return conn @@ -58,6 +78,9 @@ func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn { } func (l *Detector) CheckConn(metadata *C.Metadata) error { + if l == nil || l.connMap == nil { + return nil + } connAddr := metadata.SourceAddrPort() if !connAddr.IsValid() { return nil @@ -69,6 +92,9 @@ func (l *Detector) CheckConn(metadata *C.Metadata) error { } func (l *Detector) CheckPacketConn(metadata *C.Metadata) error { + if l == nil || l.packetConnMap == nil { + return nil + } connAddr := metadata.SourceAddrPort() if !connAddr.IsValid() { return nil diff --git a/component/mmdb/reader.go b/component/mmdb/reader.go index e76e9939..42e500c9 100644 --- a/component/mmdb/reader.go +++ b/component/mmdb/reader.go @@ -5,6 +5,7 @@ import ( "net" "strings" + "github.com/metacubex/mihomo/log" "github.com/oschwald/maxminddb-golang" ) @@ -23,11 +24,16 @@ type ASNReader struct { *maxminddb.Reader } -type ASNResult struct { +type GeoLite2 struct { AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"` AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` } +type IPInfo struct { + ASN string `maxminddb:"asn"` + Name string `maxminddb:"name"` +} + func (r IPReader) LookupCode(ipAddress net.IP) []string { switch r.databaseType { case typeMaxmind: @@ -66,8 +72,18 @@ func (r IPReader) LookupCode(ipAddress net.IP) []string { } } -func (r ASNReader) LookupASN(ip net.IP) ASNResult { - var result ASNResult - r.Lookup(ip, &result) - return result +func (r ASNReader) LookupASN(ip net.IP) (string, string) { + switch r.Metadata.DatabaseType { + case "GeoLite2-ASN", "DBIP-ASN-Lite (compat=GeoLite2-ASN)": + var result GeoLite2 + _ = r.Lookup(ip, &result) + return fmt.Sprint(result.AutonomousSystemNumber), result.AutonomousSystemOrganization + case "ipinfo generic_asn_free.mmdb": + var result IPInfo + _ = r.Lookup(ip, &result) + return result.ASN[2:], result.Name + default: + log.Warnln("Unsupported ASN type: %s", r.Metadata.DatabaseType) + } + return "", "" } diff --git a/component/nat/table.go b/component/nat/table.go index bb5ab755..66241fb4 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -10,47 +10,30 @@ import ( ) type Table struct { - mapping *xsync.MapOf[string, *Entry] - lockMap *xsync.MapOf[string, *sync.Cond] + mapping *xsync.MapOf[string, *entry] } -type Entry struct { - PacketConn C.PacketConn - WriteBackProxy C.WriteBackProxy +type entry struct { + PacketSender C.PacketSender LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn] LocalLockMap *xsync.MapOf[string, *sync.Cond] } -func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) { - t.mapping.Store(key, &Entry{ - PacketConn: e, - WriteBackProxy: w, - LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](), - LocalLockMap: xsync.NewMapOf[string, *sync.Cond](), +func (t *Table) GetOrCreate(key string, maker func() C.PacketSender) (C.PacketSender, bool) { + item, loaded := t.mapping.LoadOrCompute(key, func() *entry { + return &entry{ + PacketSender: maker(), + LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](), + LocalLockMap: xsync.NewMapOf[string, *sync.Cond](), + } }) -} - -func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) { - entry, exist := t.getEntry(key) - if !exist { - return nil, nil - } - return entry.PacketConn, entry.WriteBackProxy -} - -func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { - item, loaded := t.lockMap.LoadOrCompute(key, makeLock) - return item, loaded + return item.PacketSender, loaded } func (t *Table) Delete(key string) { t.mapping.Delete(key) } -func (t *Table) DeleteLock(lockKey string) { - t.lockMap.Delete(lockKey) -} - func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn { entry, exist := t.getEntry(lAddr) if !exist { @@ -105,7 +88,7 @@ func (t *Table) DeleteLockForLocalConn(lAddr, key string) { entry.LocalLockMap.Delete(key) } -func (t *Table) getEntry(key string) (*Entry, bool) { +func (t *Table) getEntry(key string) (*entry, bool) { return t.mapping.Load(key) } @@ -116,7 +99,6 @@ func makeLock() *sync.Cond { // New return *Cache func New() *Table { return &Table{ - mapping: xsync.NewMapOf[string, *Entry](), - lockMap: xsync.NewMapOf[string, *sync.Cond](), + mapping: xsync.NewMapOf[string, *entry](), } } diff --git a/component/process/process_windows.go b/component/process/process_windows.go index f8cd00d8..73ac0255 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -180,7 +180,7 @@ func newSearcher(isV4, isTCP bool) *searcher { func getTransportTable(fn uintptr, family int, class int) ([]byte, error) { for size, buf := uint32(8), make([]byte, 8); ; { ptr := unsafe.Pointer(&buf[0]) - err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) + err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) switch err { case 0: @@ -215,13 +215,13 @@ func getExecPathFromPID(pid uint32) (string, error) { buf := make([]uint16, syscall.MAX_LONG_PATH) size := uint32(len(buf)) - r1, _, err := syscall.SyscallN( - queryProcName, + r1, _, err := syscall.Syscall6( + queryProcName, 4, uintptr(h), uintptr(0), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), - ) + 0, 0) if r1 == 0 { return "", err } diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index 0591c92b..7b4cdfc2 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -1,7 +1,6 @@ package cachefile import ( - "math" "os" "sync" "time" @@ -18,9 +17,10 @@ var ( fileMode os.FileMode = 0o666 defaultCache *CacheFile - bucketSelected = []byte("selected") - bucketFakeip = []byte("fakeip") - bucketETag = []byte("etag") + bucketSelected = []byte("selected") + bucketFakeip = []byte("fakeip") + bucketETag = []byte("etag") + bucketSubscriptionInfo = []byte("subscriptioninfo") ) // CacheFile store and update the cache file @@ -71,133 +71,6 @@ func (c *CacheFile) SelectedMap() map[string]string { return mapping } -func (c *CacheFile) PutFakeip(key, value []byte) error { - if c.DB == nil { - return nil - } - - err := c.DB.Batch(func(t *bbolt.Tx) error { - bucket, err := t.CreateBucketIfNotExists(bucketFakeip) - if err != nil { - return err - } - return bucket.Put(key, value) - }) - if err != nil { - log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) - } - - return err -} - -func (c *CacheFile) DelFakeipPair(ip, host []byte) error { - if c.DB == nil { - return nil - } - - err := c.DB.Batch(func(t *bbolt.Tx) error { - bucket, err := t.CreateBucketIfNotExists(bucketFakeip) - if err != nil { - return err - } - err = bucket.Delete(ip) - if len(host) > 0 { - if err := bucket.Delete(host); err != nil { - return err - } - } - return err - }) - if err != nil { - log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) - } - - return err -} - -func (c *CacheFile) GetFakeip(key []byte) []byte { - if c.DB == nil { - return nil - } - - tx, err := c.DB.Begin(false) - if err != nil { - return nil - } - defer tx.Rollback() - - bucket := tx.Bucket(bucketFakeip) - if bucket == nil { - return nil - } - - return bucket.Get(key) -} - -func (c *CacheFile) FlushFakeIP() error { - err := c.DB.Batch(func(t *bbolt.Tx) error { - bucket := t.Bucket(bucketFakeip) - if bucket == nil { - return nil - } - return t.DeleteBucket(bucketFakeip) - }) - return err -} - -func (c *CacheFile) SetETagWithHash(url string, hash []byte, etag string) { - if c.DB == nil { - return - } - - lenHash := len(hash) - if lenHash > math.MaxUint8 { - return // maybe panic is better - } - - data := make([]byte, 1, 1+lenHash+len(etag)) - data[0] = uint8(lenHash) - data = append(data, hash...) - data = append(data, etag...) - - err := c.DB.Batch(func(t *bbolt.Tx) error { - bucket, err := t.CreateBucketIfNotExists(bucketETag) - if err != nil { - return err - } - - return bucket.Put([]byte(url), data) - }) - if err != nil { - log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) - return - } -} -func (c *CacheFile) GetETagWithHash(key string) (hash []byte, etag string) { - if c.DB == nil { - return - } - var value []byte - c.DB.View(func(t *bbolt.Tx) error { - if bucket := t.Bucket(bucketETag); bucket != nil { - if v := bucket.Get([]byte(key)); v != nil { - value = v - } - } - return nil - }) - if len(value) == 0 { - return - } - lenHash := int(value[0]) - if len(value) < 1+lenHash { - return - } - hash = value[1 : 1+lenHash] - etag = string(value[1+lenHash:]) - return -} - func (c *CacheFile) Close() error { return c.DB.Close() } diff --git a/component/profile/cachefile/etag.go b/component/profile/cachefile/etag.go new file mode 100644 index 00000000..028fe504 --- /dev/null +++ b/component/profile/cachefile/etag.go @@ -0,0 +1,58 @@ +package cachefile + +import ( + "time" + + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/log" + + "github.com/metacubex/bbolt" + "github.com/vmihailenco/msgpack/v5" +) + +type EtagWithHash struct { + Hash utils.HashType + ETag string + Time time.Time +} + +func (c *CacheFile) SetETagWithHash(url string, etagWithHash EtagWithHash) { + if c.DB == nil { + return + } + + data, err := msgpack.Marshal(etagWithHash) + if err != nil { + return // maybe panic is better + } + + err = c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketETag) + if err != nil { + return err + } + + return bucket.Put([]byte(url), data) + }) + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) + return + } +} +func (c *CacheFile) GetETagWithHash(key string) (etagWithHash EtagWithHash) { + if c.DB == nil { + return + } + c.DB.View(func(t *bbolt.Tx) error { + if bucket := t.Bucket(bucketETag); bucket != nil { + if v := bucket.Get([]byte(key)); v != nil { + if err := msgpack.Unmarshal(v, &etagWithHash); err != nil { + return err + } + } + } + return nil + }) + + return +} diff --git a/component/profile/cachefile/fakeip.go b/component/profile/cachefile/fakeip.go new file mode 100644 index 00000000..20a09f9c --- /dev/null +++ b/component/profile/cachefile/fakeip.go @@ -0,0 +1,115 @@ +package cachefile + +import ( + "net/netip" + + "github.com/metacubex/mihomo/log" + + "github.com/metacubex/bbolt" +) + +type FakeIpStore struct { + *CacheFile +} + +func (c *CacheFile) FakeIpStore() *FakeIpStore { + return &FakeIpStore{c} +} + +func (c *FakeIpStore) GetByHost(host string) (ip netip.Addr, exist bool) { + if c.DB == nil { + return + } + c.DB.View(func(t *bbolt.Tx) error { + if bucket := t.Bucket(bucketFakeip); bucket != nil { + if v := bucket.Get([]byte(host)); v != nil { + ip, exist = netip.AddrFromSlice(v) + } + } + return nil + }) + return +} + +func (c *FakeIpStore) PutByHost(host string, ip netip.Addr) { + if c.DB == nil { + return + } + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketFakeip) + if err != nil { + return err + } + return bucket.Put([]byte(host), ip.AsSlice()) + }) + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) + } +} + +func (c *FakeIpStore) GetByIP(ip netip.Addr) (host string, exist bool) { + if c.DB == nil { + return + } + c.DB.View(func(t *bbolt.Tx) error { + if bucket := t.Bucket(bucketFakeip); bucket != nil { + if v := bucket.Get(ip.AsSlice()); v != nil { + host, exist = string(v), true + } + } + return nil + }) + return +} + +func (c *FakeIpStore) PutByIP(ip netip.Addr, host string) { + if c.DB == nil { + return + } + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketFakeip) + if err != nil { + return err + } + return bucket.Put(ip.AsSlice(), []byte(host)) + }) + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) + } +} + +func (c *FakeIpStore) DelByIP(ip netip.Addr) { + if c.DB == nil { + return + } + + addr := ip.AsSlice() + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketFakeip) + if err != nil { + return err + } + host := bucket.Get(addr) + err = bucket.Delete(addr) + if len(host) > 0 { + if err = bucket.Delete(host); err != nil { + return err + } + } + return err + }) + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) + } +} + +func (c *FakeIpStore) FlushFakeIP() error { + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket := t.Bucket(bucketFakeip) + if bucket == nil { + return nil + } + return t.DeleteBucket(bucketFakeip) + }) + return err +} diff --git a/component/profile/cachefile/subscriptioninfo.go b/component/profile/cachefile/subscriptioninfo.go new file mode 100644 index 00000000..c68f92eb --- /dev/null +++ b/component/profile/cachefile/subscriptioninfo.go @@ -0,0 +1,41 @@ +package cachefile + +import ( + "github.com/metacubex/mihomo/log" + + "github.com/metacubex/bbolt" +) + +func (c *CacheFile) SetSubscriptionInfo(name string, userInfo string) { + if c.DB == nil { + return + } + + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketSubscriptionInfo) + if err != nil { + return err + } + + return bucket.Put([]byte(name), []byte(userInfo)) + }) + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) + return + } +} +func (c *CacheFile) GetSubscriptionInfo(name string) (userInfo string) { + if c.DB == nil { + return + } + c.DB.View(func(t *bbolt.Tx) error { + if bucket := t.Bucket(bucketSubscriptionInfo); bucket != nil { + if v := bucket.Get([]byte(name)); v != nil { + userInfo = string(v) + } + } + return nil + }) + + return +} diff --git a/component/resolver/defaults.go b/component/resolver/defaults.go deleted file mode 100644 index 8a04bd17..00000000 --- a/component/resolver/defaults.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris - -package resolver - -import _ "unsafe" - -//go:linkname defaultNS net.defaultNS -var defaultNS []string - -func init() { - defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"} -} diff --git a/component/resolver/host_windows.go b/component/resolver/host_windows.go deleted file mode 100644 index 669f9547..00000000 --- a/component/resolver/host_windows.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build !go1.22 - -// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc - -package resolver - -import ( - "golang.org/x/sys/windows" - _ "unsafe" -) - -//go:linkname testHookHostsPath net.testHookHostsPath -var testHookHostsPath string - -func init() { - if dir, err := windows.GetSystemDirectory(); err == nil { - testHookHostsPath = dir + "/Drivers/etc/hosts" - } -} diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index feb3f98f..1eb3d642 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "net" "net/netip" "strings" "time" @@ -20,9 +19,15 @@ var ( // DefaultResolver aim to resolve ip DefaultResolver Resolver - // ProxyServerHostResolver resolve ip to proxies server host + // ProxyServerHostResolver resolve ip for proxies server host, only nil when DefaultResolver is nil ProxyServerHostResolver Resolver + // DirectHostResolver resolve ip for direct outbound host, only nil when DefaultResolver is nil + DirectHostResolver Resolver + + // SystemResolver always using system dns, and was init in dns module + SystemResolver Resolver + // DisableIPv6 means don't resolve ipv6 host // default value is true DisableIPv6 = true @@ -47,6 +52,7 @@ type Resolver interface { ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) Invalid() bool ClearCache() + ResetConnection() } // LookupIPv4WithResolver same as LookupIPv4, but with a resolver @@ -71,14 +77,7 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net return r.LookupIPv4(ctx, host) } - ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host) - if err != nil { - return nil, err - } else if len(ipAddrs) == 0 { - return nil, ErrIPNotFound - } - - return ipAddrs, nil + return SystemResolver.LookupIPv4(ctx, host) } // LookupIPv4 with a host, return ipv4 list @@ -127,14 +126,7 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net return r.LookupIPv6(ctx, host) } - ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host) - if err != nil { - return nil, err - } else if len(ipAddrs) == 0 { - return nil, ErrIPNotFound - } - - return ipAddrs, nil + return SystemResolver.LookupIPv6(ctx, host) } // LookupIPv6 with a host, return ipv6 list @@ -176,14 +168,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip return []netip.Addr{ip}, nil } - ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host) - if err != nil { - return nil, err - } else if len(ips) == 0 { - return nil, ErrIPNotFound - } - - return ips, nil + return SystemResolver.LookupIP(ctx, host) } // LookupIP with a host, return ip @@ -211,49 +196,10 @@ func ResolveIP(ctx context.Context, host string) (netip.Addr, error) { return ResolveIPWithResolver(ctx, host, DefaultResolver) } -// ResolveIPv4ProxyServerHost proxies server host only -func ResolveIPv4ProxyServerHost(ctx context.Context, host string) (netip.Addr, error) { - if ProxyServerHostResolver != nil { - return ResolveIPv4WithResolver(ctx, host, ProxyServerHostResolver) +func ResetConnection() { + if DefaultResolver != nil { + go DefaultResolver.ResetConnection() } - return ResolveIPv4(ctx, host) -} - -// ResolveIPv6ProxyServerHost proxies server host only -func ResolveIPv6ProxyServerHost(ctx context.Context, host string) (netip.Addr, error) { - if ProxyServerHostResolver != nil { - return ResolveIPv6WithResolver(ctx, host, ProxyServerHostResolver) - } - return ResolveIPv6(ctx, host) -} - -// ResolveProxyServerHost proxies server host only -func ResolveProxyServerHost(ctx context.Context, host string) (netip.Addr, error) { - if ProxyServerHostResolver != nil { - return ResolveIPWithResolver(ctx, host, ProxyServerHostResolver) - } - return ResolveIP(ctx, host) -} - -func LookupIPv6ProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) { - if ProxyServerHostResolver != nil { - return LookupIPv6WithResolver(ctx, host, ProxyServerHostResolver) - } - return LookupIPv6(ctx, host) -} - -func LookupIPv4ProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) { - if ProxyServerHostResolver != nil { - return LookupIPv4WithResolver(ctx, host, ProxyServerHostResolver) - } - return LookupIPv4(ctx, host) -} - -func LookupIPProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) { - if ProxyServerHostResolver != nil { - return LookupIPWithResolver(ctx, host, ProxyServerHostResolver) - } - return LookupIP(ctx, host) } func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) { diff --git a/component/resource/fetcher.go b/component/resource/fetcher.go index 3e2ec239..39beee85 100644 --- a/component/resource/fetcher.go +++ b/component/resource/fetcher.go @@ -5,6 +5,7 @@ import ( "os" "time" + "github.com/metacubex/mihomo/common/utils" types "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" @@ -21,7 +22,7 @@ type Fetcher[V any] struct { name string vehicle types.Vehicle updatedAt time.Time - hash types.HashType + hash utils.HashType parser Parser[V] interval time.Duration onUpdate func(V) @@ -55,7 +56,7 @@ func (f *Fetcher[V]) Initial() (V, error) { // local file exists, use it first buf, err = os.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() - contents, _, err = f.loadBuf(buf, types.MakeHash(buf), false) + contents, _, err = f.loadBuf(buf, utils.MakeHash(buf), false) f.updatedAt = modTime // reset updatedAt to file's modTime if err == nil { @@ -89,10 +90,10 @@ func (f *Fetcher[V]) Update() (V, bool, error) { } func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) { - return f.loadBuf(buf, types.MakeHash(buf), true) + return f.loadBuf(buf, utils.MakeHash(buf), true) } -func (f *Fetcher[V]) loadBuf(buf []byte, hash types.HashType, updateFile bool) (V, bool, error) { +func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) (V, bool, error) { now := time.Now() if f.hash.Equal(hash) { if updateFile { diff --git a/component/resource/vehicle.go b/component/resource/vehicle.go index f30e22d0..7c3cb1c2 100644 --- a/component/resource/vehicle.go +++ b/component/resource/vehicle.go @@ -9,6 +9,7 @@ import ( "path/filepath" "time" + "github.com/metacubex/mihomo/common/utils" mihomoHttp "github.com/metacubex/mihomo/component/http" "github.com/metacubex/mihomo/component/profile/cachefile" types "github.com/metacubex/mihomo/constant/provider" @@ -61,12 +62,12 @@ func (f *FileVehicle) Url() string { return "file://" + f.path } -func (f *FileVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []byte, hash types.HashType, err error) { +func (f *FileVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) { buf, err = os.ReadFile(f.path) if err != nil { return } - hash = types.MakeHash(buf) + hash = utils.MakeHash(buf) return } @@ -83,11 +84,12 @@ func NewFileVehicle(path string) *FileVehicle { } type HTTPVehicle struct { - url string - path string - proxy string - header http.Header - timeout time.Duration + url string + path string + proxy string + header http.Header + timeout time.Duration + provider types.ProxyProvider } func (h *HTTPVehicle) Url() string { @@ -110,20 +112,24 @@ func (h *HTTPVehicle) Write(buf []byte) error { return safeWrite(h.path, buf) } -func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []byte, hash types.HashType, err error) { +func (h *HTTPVehicle) SetProvider(provider types.ProxyProvider) { + h.provider = provider +} + +func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) { ctx, cancel := context.WithTimeout(ctx, h.timeout) defer cancel() header := h.header setIfNoneMatch := false if etag && oldHash.IsValid() { - hashBytes, etag := cachefile.Cache().GetETagWithHash(h.url) - if oldHash.EqualBytes(hashBytes) && etag != "" { + etagWithHash := cachefile.Cache().GetETagWithHash(h.url) + if oldHash.Equal(etagWithHash.Hash) && etagWithHash.ETag != "" { if header == nil { header = http.Header{} } else { header = header.Clone() } - header.Set("If-None-Match", etag) + header.Set("If-None-Match", etagWithHash.ETag) setIfNoneMatch = true } } @@ -132,6 +138,12 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []b return } defer resp.Body.Close() + + if subscriptionInfo := resp.Header.Get("subscription-userinfo"); h.provider != nil && subscriptionInfo != "" { + cachefile.Cache().SetSubscriptionInfo(h.provider.Name(), subscriptionInfo) + h.provider.SetSubscriptionInfo(subscriptionInfo) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { if setIfNoneMatch && resp.StatusCode == http.StatusNotModified { return nil, oldHash, nil @@ -143,9 +155,13 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []b if err != nil { return } - hash = types.MakeHash(buf) + hash = utils.MakeHash(buf) if etag { - cachefile.Cache().SetETagWithHash(h.url, hash.Bytes(), resp.Header.Get("ETag")) + cachefile.Cache().SetETagWithHash(h.url, cachefile.EtagWithHash{ + Hash: hash, + ETag: resp.Header.Get("ETag"), + Time: time.Now(), + }) } return } diff --git a/component/updater/update_geo.go b/component/updater/update_geo.go index 454cd84d..bba0dabd 100644 --- a/component/updater/update_geo.go +++ b/component/updater/update_geo.go @@ -10,12 +10,12 @@ import ( "github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/batch" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/geodata" _ "github.com/metacubex/mihomo/component/geodata/standard" "github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/resource" C "github.com/metacubex/mihomo/constant" - P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" "github.com/oschwald/maxminddb-golang" @@ -46,9 +46,9 @@ func SetGeoUpdateInterval(newGeoUpdateInterval int) { func UpdateMMDB() (err error) { vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout) - var oldHash P.HashType + var oldHash utils.HashType if buf, err := os.ReadFile(vehicle.Path()); err == nil { - oldHash = P.MakeHash(buf) + oldHash = utils.MakeHash(buf) } data, hash, err := vehicle.Read(context.Background(), oldHash) if err != nil { @@ -77,9 +77,9 @@ func UpdateMMDB() (err error) { func UpdateASN() (err error) { vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout) - var oldHash P.HashType + var oldHash utils.HashType if buf, err := os.ReadFile(vehicle.Path()); err == nil { - oldHash = P.MakeHash(buf) + oldHash = utils.MakeHash(buf) } data, hash, err := vehicle.Read(context.Background(), oldHash) if err != nil { @@ -110,9 +110,9 @@ func UpdateGeoIp() (err error) { geoLoader, err := geodata.GetGeoDataLoader("standard") vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout) - var oldHash P.HashType + var oldHash utils.HashType if buf, err := os.ReadFile(vehicle.Path()); err == nil { - oldHash = P.MakeHash(buf) + oldHash = utils.MakeHash(buf) } data, hash, err := vehicle.Read(context.Background(), oldHash) if err != nil { @@ -140,9 +140,9 @@ func UpdateGeoSite() (err error) { geoLoader, err := geodata.GetGeoDataLoader("standard") vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout) - var oldHash P.HashType + var oldHash utils.HashType if buf, err := os.ReadFile(vehicle.Path()); err == nil { - oldHash = P.MakeHash(buf) + oldHash = utils.MakeHash(buf) } data, hash, err := vehicle.Read(context.Background(), oldHash) if err != nil { @@ -229,20 +229,22 @@ func UpdateGeoDatabases() error { } func getUpdateTime() (err error, time time.Time) { - var fileInfo os.FileInfo - if geodata.GeodataMode() { - fileInfo, err = os.Stat(C.Path.GeoIP()) - if err != nil { - return err, time - } - } else { - fileInfo, err = os.Stat(C.Path.MMDB()) - if err != nil { - return err, time + filesToCheck := []string{ + C.Path.GeoIP(), + C.Path.MMDB(), + C.Path.ASN(), + C.Path.GeoSite(), + } + + for _, file := range filesToCheck { + var fileInfo os.FileInfo + fileInfo, err = os.Stat(file) + if err == nil { + return nil, fileInfo.ModTime() } } - return nil, fileInfo.ModTime() + return } func RegisterGeoUpdater() { diff --git a/component/updater/update_ui.go b/component/updater/update_ui.go index 29081761..bd2a5881 100644 --- a/component/updater/update_ui.go +++ b/component/updater/update_ui.go @@ -14,24 +14,69 @@ import ( "github.com/metacubex/mihomo/log" ) -var ( - ExternalUIURL string - ExternalUIPath string - AutoDownloadUI bool -) +type UIUpdater struct { + externalUIURL string + externalUIPath string + autoDownloadUI bool -var xdMutex sync.Mutex + mutex sync.Mutex +} -func DownloadUI() error { - xdMutex.Lock() - defer xdMutex.Unlock() +var DefaultUiUpdater = &UIUpdater{} - err := prepareUIPath() +func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater { + updater := &UIUpdater{} + // checkout externalUI exist + if externalUI != "" { + updater.autoDownloadUI = true + updater.externalUIPath = C.Path.Resolve(externalUI) + } else { + // default externalUI path + updater.externalUIPath = path.Join(C.Path.HomeDir(), "ui") + } + + // checkout UIpath/name exist + if externalUIName != "" { + updater.autoDownloadUI = true + updater.externalUIPath = path.Join(updater.externalUIPath, externalUIName) + } + + if externalUIURL != "" { + updater.externalUIURL = externalUIURL + } + return updater +} + +func (u *UIUpdater) AutoDownloadUI() { + u.mutex.Lock() + defer u.mutex.Unlock() + if u.autoDownloadUI { + dirEntries, _ := os.ReadDir(u.externalUIPath) + if len(dirEntries) > 0 { + log.Infoln("UI already exists, skip downloading") + } else { + log.Infoln("External UI downloading ...") + err := u.downloadUI() + if err != nil { + log.Errorln("Error downloading UI: %s", err) + } + } + } +} + +func (u *UIUpdater) DownloadUI() error { + u.mutex.Lock() + defer u.mutex.Unlock() + return u.downloadUI() +} + +func (u *UIUpdater) downloadUI() error { + err := u.prepareUIPath() if err != nil { return fmt.Errorf("prepare UI path failed: %w", err) } - data, err := downloadForBytes(ExternalUIURL) + data, err := downloadForBytes(u.externalUIURL) if err != nil { return fmt.Errorf("can't download file: %w", err) } @@ -42,7 +87,7 @@ func DownloadUI() error { } defer os.Remove(saved) - err = cleanup(ExternalUIPath) + err = cleanup(u.externalUIPath) if err != nil { if !os.IsNotExist(err) { return fmt.Errorf("cleanup exist file error: %w", err) @@ -54,18 +99,18 @@ func DownloadUI() error { return fmt.Errorf("can't extract zip file: %w", err) } - err = os.Rename(unzipFolder, ExternalUIPath) + err = os.Rename(unzipFolder, u.externalUIPath) if err != nil { return fmt.Errorf("rename UI folder failed: %w", err) } return nil } -func prepareUIPath() error { - if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { - log.Infoln("dir %s does not exist, creating", ExternalUIPath) - if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil { - log.Warnln("create dir %s error: %s", ExternalUIPath, err) +func (u *UIUpdater) prepareUIPath() error { + if _, err := os.Stat(u.externalUIPath); os.IsNotExist(err) { + log.Infoln("dir %s does not exist, creating", u.externalUIPath) + if err := os.MkdirAll(u.externalUIPath, os.ModePerm); err != nil { + log.Warnln("create dir %s error: %s", u.externalUIPath, err) } } return nil diff --git a/config/config.go b/config/config.go index 75616edc..ba6097bc 100644 --- a/config/config.go +++ b/config/config.go @@ -7,28 +7,23 @@ import ( "net" "net/netip" "net/url" - "path" "strings" "time" + _ "unsafe" "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/provider" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/cidr" "github.com/metacubex/mihomo/component/fakeip" "github.com/metacubex/mihomo/component/geodata" - mihomoHttp "github.com/metacubex/mihomo/component/http" P "github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/resolver" - "github.com/metacubex/mihomo/component/resource" "github.com/metacubex/mihomo/component/sniffer" - tlsC "github.com/metacubex/mihomo/component/tls" "github.com/metacubex/mihomo/component/trie" - "github.com/metacubex/mihomo/component/updater" C "github.com/metacubex/mihomo/constant" providerTypes "github.com/metacubex/mihomo/constant/provider" snifferTypes "github.com/metacubex/mihomo/constant/sniffer" @@ -67,6 +62,9 @@ type General struct { GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalUA string `json:"global-ua"` ETagSupport bool `json:"etag-support"` + KeepAliveIdle int `json:"keep-alive-idle"` + KeepAliveInterval int `json:"keep-alive-interval"` + DisableKeepAlive bool `json:"disable-keep-alive"` } // Inbound config @@ -103,9 +101,18 @@ type Controller struct { ExternalController string ExternalControllerTLS string ExternalControllerUnix string + ExternalControllerPipe string ExternalUI string + ExternalUIURL string + ExternalUIName string ExternalDohServer string Secret string + Cors Cors +} + +type Cors struct { + AllowOrigins []string + AllowPrivateNetwork bool } // Experimental config @@ -153,6 +160,8 @@ type DNS struct { Hosts *trie.DomainTrie[resolver.HostValue] NameServerPolicy []dns.Policy ProxyServerNameserver []dns.NameServer + DirectNameServer []dns.NameServer + DirectFollowPolicy bool } // Profile config @@ -190,26 +199,33 @@ type Config struct { TLS *TLS } +type RawCors struct { + AllowOrigins []string `yaml:"allow-origins" json:"allow-origins"` + AllowPrivateNetwork bool `yaml:"allow-private-network" json:"allow-private-network"` +} + type RawDNS struct { - Enable bool `yaml:"enable" json:"enable"` - PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` - IPv6 bool `yaml:"ipv6" json:"ipv6"` - IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` - UseHosts bool `yaml:"use-hosts" json:"use-hosts"` - UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"` - RespectRules bool `yaml:"respect-rules" json:"respect-rules"` - NameServer []string `yaml:"nameserver" json:"nameserver"` - Fallback []string `yaml:"fallback" json:"fallback"` - FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` - Listen string `yaml:"listen" json:"listen"` - EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"` - FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"` - FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` - FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"` - DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` - CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"` - NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"` - ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"` + Enable bool `yaml:"enable" json:"enable"` + PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` + IPv6 bool `yaml:"ipv6" json:"ipv6"` + IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` + UseHosts bool `yaml:"use-hosts" json:"use-hosts"` + UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"` + RespectRules bool `yaml:"respect-rules" json:"respect-rules"` + NameServer []string `yaml:"nameserver" json:"nameserver"` + Fallback []string `yaml:"fallback" json:"fallback"` + FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` + Listen string `yaml:"listen" json:"listen"` + EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"` + FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"` + FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` + FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"` + DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` + CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"` + NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"` + ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"` + DirectNameServer []string `yaml:"direct-nameserver" json:"direct-nameserver"` + DirectNameServerFollowPolicy bool `yaml:"direct-nameserver-follow-policy" json:"direct-nameserver-follow-policy"` } type RawFallbackFilter struct { @@ -364,8 +380,10 @@ type RawConfig struct { LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` IPv6 bool `yaml:"ipv6" json:"ipv6"` ExternalController string `yaml:"external-controller" json:"external-controller"` + ExternalControllerPipe string `yaml:"external-controller-pipe" json:"external-controller-pipe"` ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"` ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"` + ExternalControllerCors RawCors `yaml:"external-controller-cors" json:"external-controller-cors"` ExternalUI string `yaml:"external-ui" json:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` @@ -539,6 +557,10 @@ func DefaultRawConfig() *RawConfig { OverrideDest: true, }, ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", + ExternalControllerCors: RawCors{ + AllowOrigins: []string{"*"}, + AllowPrivateNetwork: true, + }, } } @@ -564,10 +586,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.General = general - if len(config.General.GlobalClientFingerprint) != 0 { - log.Debugln("GlobalClientFingerprint: %s", config.General.GlobalClientFingerprint) - tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint) - } + // We need to temporarily apply some configuration in general and roll back after parsing the complete configuration. + // The loading and downloading of geodata in the parseRules and parseRuleProviders rely on these. + // This implementation is very disgusting, but there is currently no better solution + rollback := temporaryUpdateGeneral(config.General) + defer rollback() controller, err := parseController(rawCfg) if err != nil { @@ -683,46 +706,10 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { return config, nil } +//go:linkname temporaryUpdateGeneral +func temporaryUpdateGeneral(general *General) func() + func parseGeneral(cfg *RawConfig) (*General, error) { - updater.SetGeoAutoUpdate(cfg.GeoAutoUpdate) - updater.SetGeoUpdateInterval(cfg.GeoUpdateInterval) - geodata.SetGeodataMode(cfg.GeodataMode) - geodata.SetLoader(cfg.GeodataLoader) - geodata.SetSiteMatcher(cfg.GeositeMatcher) - geodata.SetGeoIpUrl(cfg.GeoXUrl.GeoIp) - geodata.SetGeoSiteUrl(cfg.GeoXUrl.GeoSite) - geodata.SetMmdbUrl(cfg.GeoXUrl.Mmdb) - geodata.SetASNUrl(cfg.GeoXUrl.ASN) - mihomoHttp.SetUA(cfg.GlobalUA) - resource.SetETag(cfg.ETagSupport) - - if cfg.KeepAliveIdle != 0 { - N.KeepAliveIdle = time.Duration(cfg.KeepAliveIdle) * time.Second - } - if cfg.KeepAliveInterval != 0 { - N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second - } - N.DisableKeepAlive = cfg.DisableKeepAlive - - // checkout externalUI exist - if cfg.ExternalUI != "" { - updater.AutoDownloadUI = true - updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI) - } else { - // default externalUI path - updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui") - } - - // checkout UIpath/name exist - if cfg.ExternalUIName != "" { - updater.AutoDownloadUI = true - updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName) - } - - if cfg.ExternalUIURL != "" { - updater.ExternalUIURL = cfg.ExternalUIURL - } - return &General{ Inbound: Inbound{ Port: cfg.Port, @@ -756,11 +743,15 @@ func parseGeneral(cfg *RawConfig) (*General, error) { GeoUpdateInterval: cfg.GeoUpdateInterval, GeodataMode: cfg.GeodataMode, GeodataLoader: cfg.GeodataLoader, + GeositeMatcher: cfg.GeositeMatcher, TCPConcurrent: cfg.TCPConcurrent, FindProcessMode: cfg.FindProcessMode, GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalUA: cfg.GlobalUA, ETagSupport: cfg.ETagSupport, + KeepAliveIdle: cfg.KeepAliveIdle, + KeepAliveInterval: cfg.KeepAliveInterval, + DisableKeepAlive: cfg.DisableKeepAlive, }, nil } @@ -768,10 +759,17 @@ func parseController(cfg *RawConfig) (*Controller, error) { return &Controller{ ExternalController: cfg.ExternalController, ExternalUI: cfg.ExternalUI, + ExternalUIURL: cfg.ExternalUIURL, + ExternalUIName: cfg.ExternalUIName, Secret: cfg.Secret, + ExternalControllerPipe: cfg.ExternalControllerPipe, ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalDohServer: cfg.ExternalDohServer, + Cors: Cors{ + AllowOrigins: cfg.ExternalControllerCors.AllowOrigins, + AllowPrivateNetwork: cfg.ExternalControllerCors.AllowPrivateNetwork, + }, }, nil } @@ -1162,16 +1160,6 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns. var nameservers []dns.NameServer for idx, server := range servers { - if strings.HasPrefix(server, "dhcp://") { - nameservers = append( - nameservers, - dns.NameServer{ - Net: "dhcp", - Addr: server[len("dhcp://"):], - }, - ) - continue - } server = parsePureDNSServer(server) u, err := url.Parse(server) if err != nil { @@ -1222,6 +1210,13 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns. dnsNetType = "quic" // DNS over QUIC case "system": dnsNetType = "system" // System DNS + case "dhcp": + addr = server[len("dhcp://"):] // some special notation cannot be parsed by url + dnsNetType = "dhcp" // UDP from DHCP + if addr == "system" { // Compatible with old writing "dhcp://system" + dnsNetType = "system" + addr = "" + } case "rcode": dnsNetType = "rcode" addr = u.Host @@ -1247,16 +1242,18 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns. proxyName = dns.RespectRules } - nameservers = append( - nameservers, - dns.NameServer{ - Net: dnsNetType, - Addr: addr, - ProxyName: proxyName, - Params: params, - PreferH3: preferH3, - }, - ) + nameserver := dns.NameServer{ + Net: dnsNetType, + Addr: addr, + ProxyName: proxyName, + Params: params, + PreferH3: preferH3, + } + if slices.ContainsFunc(nameservers, nameserver.Equal) { + continue // skip duplicates nameserver + } + + nameservers = append(nameservers, nameserver) } return nameservers, nil } @@ -1401,6 +1398,11 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul return nil, err } + if dnsCfg.DirectNameServer, err = parseNameServer(cfg.DirectNameServer, false, cfg.PreferH3); err != nil { + return nil, err + } + dnsCfg.DirectFollowPolicy = cfg.DirectNameServerFollowPolicy + if len(cfg.DefaultNameserver) == 0 { return nil, errors.New("default nameserver should have at least one nameserver") } diff --git a/constant/adapters.go b/constant/adapters.go index cb213b3c..c7b73a06 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -158,6 +158,7 @@ type DelayHistoryStoreType int type Proxy interface { ProxyAdapter + Adapter() ProxyAdapter AliveForTestUrl(url string) bool DelayHistory() []DelayHistory ExtraDelayHistories() map[string]ProxyState @@ -212,6 +213,8 @@ func (at AdapterType) String() string { return "WireGuard" case Tuic: return "Tuic" + case Ssh: + return "Ssh" case Relay: return "Relay" @@ -223,8 +226,6 @@ func (at AdapterType) String() string { return "URLTest" case LoadBalance: return "LoadBalance" - case Ssh: - return "Ssh" default: return "Unknown" } @@ -255,12 +256,16 @@ type UDPPacketInAddr interface { // PacketAdapter is a UDP Packet adapter for socks/redir/tun type PacketAdapter interface { UDPPacket + // Metadata returns destination metadata Metadata() *Metadata + // Key is a SNAT key + Key() string } type packetAdapter struct { UDPPacket metadata *Metadata + key string } // Metadata returns destination metadata @@ -268,10 +273,16 @@ func (s *packetAdapter) Metadata() *Metadata { return s.metadata } +// Key is a SNAT key +func (s *packetAdapter) Key() string { + return s.key +} + func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter { return &packetAdapter{ packet, metadata, + packet.LocalAddr().String(), } } @@ -284,17 +295,23 @@ type WriteBackProxy interface { UpdateWriteBack(wb WriteBack) } +type PacketSender interface { + // Send will send PacketAdapter nonblocking + // the implement must call UDPPacket.Drop() inside Send + Send(PacketAdapter) + // Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy + Process(PacketConn, WriteBackProxy) + // ResolveUDP do a local resolve UDP dns blocking if metadata is not resolved + ResolveUDP(*Metadata) error + // Close stop the Process loop + Close() +} + type NatTable interface { - Set(key string, e PacketConn, w WriteBackProxy) - - Get(key string) (PacketConn, WriteBackProxy) - - GetOrCreateLock(key string) (*sync.Cond, bool) + GetOrCreate(key string, maker func() PacketSender) (PacketSender, bool) Delete(key string) - DeleteLock(key string) - GetForLocalConn(lAddr, rAddr string) *net.UDPConn AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool diff --git a/constant/path.go b/constant/path.go index 02279371..1594441c 100644 --- a/constant/path.go +++ b/constant/path.go @@ -1,14 +1,13 @@ package constant import ( - "crypto/md5" - "encoding/hex" "os" P "path" "path/filepath" "strconv" "strings" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/constant/features" ) @@ -89,8 +88,8 @@ func (p *path) IsSafePath(path string) bool { } func (p *path) GetPathByHash(prefix, name string) string { - hash := md5.Sum([]byte(name)) - filename := hex.EncodeToString(hash[:]) + hash := utils.MakeHash([]byte(name)) + filename := hash.String() return filepath.Join(p.HomeDir(), prefix, filename) } diff --git a/constant/provider/hash.go b/constant/provider/hash.go deleted file mode 100644 index b95ffe23..00000000 --- a/constant/provider/hash.go +++ /dev/null @@ -1,29 +0,0 @@ -package provider - -import ( - "bytes" - "crypto/md5" -) - -type HashType [md5.Size]byte // MD5 - -func MakeHash(data []byte) HashType { - return md5.Sum(data) -} - -func (h HashType) Equal(hash HashType) bool { - return h == hash -} - -func (h HashType) EqualBytes(hashBytes []byte) bool { - return bytes.Equal(hashBytes, h[:]) -} - -func (h HashType) Bytes() []byte { - return h[:] -} - -func (h HashType) IsValid() bool { - var zero HashType - return h != zero -} diff --git a/constant/provider/interface.go b/constant/provider/interface.go index 511e8f18..925c1734 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -32,7 +32,7 @@ func (v VehicleType) String() string { } type Vehicle interface { - Read(ctx context.Context, oldHash HashType) (buf []byte, hash HashType, err error) + Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) Write(buf []byte) error Path() string Url() string @@ -81,6 +81,7 @@ type ProxyProvider interface { Version() uint32 RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) HealthCheckURL() string + SetSubscriptionInfo(userInfo string) } // RuleProvider interface diff --git a/dns/client.go b/dns/client.go index 096b96a7..62fc12f9 100644 --- a/dns/client.go +++ b/dns/client.go @@ -103,3 +103,5 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) return ret.msg, ret.err } } + +func (c *client) ResetConnection() {} diff --git a/dns/dhcp.go b/dns/dhcp.go index dc1344f5..e3829b7c 100644 --- a/dns/dhcp.go +++ b/dns/dhcp.go @@ -53,6 +53,12 @@ func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, return } +func (d *dhcpClient) ResetConnection() { + for _, client := range d.clients { + client.ResetConnection() + } +} + func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) { d.lock.Lock() diff --git a/dns/doh.go b/dns/doh.go index ffb65fce..027afd58 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -203,11 +203,23 @@ func (doh *dnsOverHTTPS) Close() (err error) { return doh.closeClient(doh.client) } -// closeClient cleans up resources used by client if necessary. Note, that at -// this point it should only be done for HTTP/3 as it may leak due to keep-alive -// connections. +func (doh *dnsOverHTTPS) ResetConnection() { + doh.clientMu.Lock() + defer doh.clientMu.Unlock() + + if doh.client == nil { + return + } + + _ = doh.closeClient(doh.client) + doh.client = nil +} + +// closeClient cleans up resources used by client if necessary. func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) { - if isHTTP3(client) { + client.CloseIdleConnections() + + if isHTTP3(client) { // HTTP/3 may leak due to keep-alive connections. return client.Transport.(io.Closer).Close() } @@ -508,6 +520,13 @@ func (h *http3Transport) Close() (err error) { return h.baseTransport.Close() } +func (h *http3Transport) CloseIdleConnections() { + h.mu.RLock() + defer h.mu.RUnlock() + + h.baseTransport.CloseIdleConnections() +} + // createTransportH3 tries to create an HTTP/3 transport for this upstream. // We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or // if it is too slow. In order to do that, this method will run two probes diff --git a/dns/doq.go b/dns/doq.go index ad936f95..29fdd006 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -144,6 +144,10 @@ func (doq *dnsOverQUIC) Close() (err error) { return err } +func (doq *dnsOverQUIC) ResetConnection() { + doq.closeConnWithError(nil) +} + // exchangeQUIC attempts to open a QUIC connection, send the DNS message // through it and return the response it got from the server. func (doq *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) { diff --git a/dns/patch_android.go b/dns/patch_android.go index 6579ef07..8e744fcd 100644 --- a/dns/patch_android.go +++ b/dns/patch_android.go @@ -12,6 +12,10 @@ func FlushCacheWithDefaultResolver() { if r := resolver.DefaultResolver; r != nil { r.ClearCache() } + if r := resolver.SystemResolver; r != nil { + r.ClearCache() + } + resolver.ResetConnection() } func UpdateSystemDNS(addr []string) { @@ -30,3 +34,9 @@ func UpdateSystemDNS(addr []string) { func (c *systemClient) getDnsClients() ([]dnsClient, error) { return systemResolver, nil } + +func (c *systemClient) ResetConnection() { + for _, r := range systemResolver { + r.ResetConnection() + } +} diff --git a/dns/rcode.go b/dns/rcode.go index 9777d2e7..901d1019 100644 --- a/dns/rcode.go +++ b/dns/rcode.go @@ -48,3 +48,5 @@ func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, err func (r rcodeClient) Address() string { return r.addr } + +func (r rcodeClient) ResetConnection() {} diff --git a/dns/resolver.go b/dns/resolver.go index e03feef4..9f7e28f3 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -13,7 +13,6 @@ import ( "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/trie" C "github.com/metacubex/mihomo/constant" - "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" D "github.com/miekg/dns" @@ -24,6 +23,7 @@ import ( type dnsClient interface { ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) Address() string + ResetConnection() } type dnsCache interface { @@ -48,7 +48,7 @@ type Resolver struct { group singleflight.Group[*D.Msg] cache dnsCache policy []dnsPolicy - proxyServer []dnsClient + defaultResolver *Resolver } func (r *Resolver) LookupIPPrimaryIPv4(ctx context.Context, host string) (ips []netip.Addr, err error) { @@ -376,6 +376,20 @@ func (r *Resolver) ClearCache() { } } +func (r *Resolver) ResetConnection() { + if r != nil { + for _, c := range r.main { + c.ResetConnection() + } + for _, c := range r.fallback { + c.ResetConnection() + } + if dr := r.defaultResolver; dr != nil { + dr.ResetConnection() + } + } +} + type NameServer struct { Net string Addr string @@ -413,6 +427,8 @@ type Config struct { Main, Fallback []NameServer Default []NameServer ProxyServer []NameServer + DirectServer []NameServer + DirectFollowPolicy bool IPv6 bool IPv6Timeout uint EnhancedMode C.DNSMode @@ -421,20 +437,39 @@ type Config struct { Pool *fakeip.Pool Hosts *trie.DomainTrie[resolver.HostValue] Policy []Policy - Tunnel provider.Tunnel CacheAlgorithm string } -func NewResolver(config Config) *Resolver { - var cache dnsCache - if config.CacheAlgorithm == "lru" { - cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) +func (config Config) newCache() dnsCache { + if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" { + return lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) } else { - cache = arc.New(arc.WithSize[string, *D.Msg](4096)) + return arc.New(arc.WithSize[string, *D.Msg](4096)) } +} + +type Resolvers struct { + *Resolver + ProxyResolver *Resolver + DirectResolver *Resolver +} + +func (rs Resolvers) ClearCache() { + rs.Resolver.ClearCache() + rs.ProxyResolver.ClearCache() + rs.DirectResolver.ClearCache() +} + +func (rs Resolvers) ResetConnection() { + rs.Resolver.ResetConnection() + rs.ProxyResolver.ResetConnection() + rs.DirectResolver.ResetConnection() +} + +func NewResolver(config Config) (rs Resolvers) { defaultResolver := &Resolver{ main: transform(config.Default, nil), - cache: cache, + cache: config.newCache(), ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, } @@ -465,25 +500,40 @@ func NewResolver(config Config) *Resolver { return } - if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" { - cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) - } else { - cache = arc.New(arc.WithSize[string, *D.Msg](4096)) - } r := &Resolver{ ipv6: config.IPv6, main: cacheTransform(config.Main), - cache: cache, + cache: config.newCache(), hosts: config.Hosts, ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, } + r.defaultResolver = defaultResolver + rs.Resolver = r + + if len(config.ProxyServer) != 0 { + rs.ProxyResolver = &Resolver{ + ipv6: config.IPv6, + main: cacheTransform(config.ProxyServer), + cache: config.newCache(), + hosts: config.Hosts, + ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, + } + } + + if len(config.DirectServer) != 0 { + rs.DirectResolver = &Resolver{ + ipv6: config.IPv6, + main: cacheTransform(config.DirectServer), + cache: config.newCache(), + hosts: config.Hosts, + ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, + } + } if len(config.Fallback) != 0 { r.fallback = cacheTransform(config.Fallback) - } - - if len(config.ProxyServer) != 0 { - r.proxyServer = cacheTransform(config.ProxyServer) + r.fallbackIPFilters = config.FallbackIPFilter + r.fallbackDomainFilters = config.FallbackDomainFilter } if len(config.Policy) != 0 { @@ -512,22 +562,13 @@ func NewResolver(config Config) *Resolver { } } insertPolicy(nil) - } - r.fallbackIPFilters = config.FallbackIPFilter - r.fallbackDomainFilters = config.FallbackDomainFilter - return r -} - -func NewProxyServerHostResolver(old *Resolver) *Resolver { - r := &Resolver{ - ipv6: old.ipv6, - main: old.proxyServer, - cache: old.cache, - hosts: old.hosts, - ipv6Timeout: old.ipv6Timeout, + if rs.DirectResolver != nil && config.DirectFollowPolicy { + rs.DirectResolver.policy = r.policy + } } - return r + + return } var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go diff --git a/dns/system.go b/dns/system.go index 944f2824..ab6c0100 100644 --- a/dns/system.go +++ b/dns/system.go @@ -7,6 +7,8 @@ import ( "sync" "time" + "github.com/metacubex/mihomo/component/resolver" + D "github.com/miekg/dns" ) @@ -24,10 +26,15 @@ type systemClient struct { mu sync.Mutex dnsClients map[string]*systemDnsClient lastFlush time.Time + defaultNS []dnsClient } func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { dnsClients, err := c.getDnsClients() + if len(dnsClients) == 0 && len(c.defaultNS) > 0 { + dnsClients = c.defaultNS + err = nil + } if err != nil { return } @@ -38,11 +45,16 @@ func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Ms // Address implements dnsClient func (c *systemClient) Address() string { dnsClients, _ := c.getDnsClients() + isDefault := "" + if len(dnsClients) == 0 && len(c.defaultNS) > 0 { + dnsClients = c.defaultNS + isDefault = "[defaultNS]" + } addrs := make([]string, 0, len(dnsClients)) for _, c := range dnsClients { addrs = append(addrs, c.Address()) } - return fmt.Sprintf("system(%s)", strings.Join(addrs, ",")) + return fmt.Sprintf("system%s(%s)", isDefault, strings.Join(addrs, ",")) } var _ dnsClient = (*systemClient)(nil) @@ -52,3 +64,11 @@ func newSystemClient() *systemClient { dnsClients: map[string]*systemDnsClient{}, } } + +func init() { + r := NewResolver(Config{}) + c := newSystemClient() + c.defaultNS = transform([]NameServer{{Addr: "114.114.114.114:53"}, {Addr: "8.8.8.8:53"}}, nil) + r.main = []dnsClient{c} + resolver.SystemResolver = r +} diff --git a/dns/system_common.go b/dns/system_common.go index 06dc0b30..e6dabdcf 100644 --- a/dns/system_common.go +++ b/dns/system_common.go @@ -69,3 +69,5 @@ func (c *systemClient) getDnsClients() ([]dnsClient, error) { } return nil, err } + +func (c *systemClient) ResetConnection() {} diff --git a/dns/util.go b/dns/util.go index 92a86dbc..50459cc1 100644 --- a/dns/util.go +++ b/dns/util.go @@ -99,10 +99,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)) continue case "dhcp": - if s.Addr == "system" { // Compatible with old writing - ret = append(ret, newSystemClient()) - continue - } ret = append(ret, newDHCPClient(s.Addr)) continue case "system": diff --git a/docs/config.yaml b/docs/config.yaml index b3515a20..e75e5bd5 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -58,11 +58,21 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址 external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件 # secret: "123456" # `Authorization:Bearer ${secret}` +# RESTful API CORS标头配置 +external-controller-cors: + allow-origins: + - * + allow-private-network: true + # RESTful API Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 ) # !!!注意: 从Unix socket访问api接口不会验证secret, 如果开启请自行保证安全问题 !!! # 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ external-controller-unix: mihomo.sock +# RESTful API Windows namedpipe 监听地址 +# !!!注意: 从Windows namedpipe访问api接口不会验证secret, 如果开启请自行保证安全问题 !!! +external-controller-pipe: \\.\pipe\mihomo + # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 @@ -284,10 +294,15 @@ dns: # - tcp://1.1.1.1 # - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡 - # 专用于节点域名解析的 DNS 服务器,非必要配置项 + # 专用于节点域名解析的 DNS 服务器,非必要配置项,如果不填则遵循nameserver-policy、nameserver和fallback的配置 # proxy-server-nameserver: - # - https://dns.google/dns-query - # - tls://one.one.one.one + # - https://dns.google/dns-query + # - tls://one.one.one.one + + # 专用于direct出口域名解析的 DNS 服务器,非必要配置项,如果不填则遵循nameserver-policy、nameserver和fallback的配置 + # direct-nameserver: + # - system:// + # direct-nameserver-follow-policy: false # 是否遵循nameserver-policy,默认为不遵守,仅当direct-nameserver不为空时生效 # 配置 fallback 使用条件 # fallback-filter: diff --git a/go.mod b/go.mod index 6c5242f5..157aff1b 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,13 @@ go 1.20 require ( github.com/3andne/restls-client-go v0.1.6 github.com/bahlo/generic-list-go v0.2.0 - github.com/coreos/go-iptables v0.7.0 + github.com/coreos/go-iptables v0.8.0 github.com/dlclark/regexp2 v1.11.4 github.com/go-chi/chi/v5 v5.1.0 - github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 github.com/gofrs/uuid/v5 v5.3.0 - github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 + github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 github.com/klauspost/compress v1.17.9 github.com/klauspost/cpuid/v2 v2.2.8 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 @@ -21,15 +20,15 @@ require ( github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 github.com/metacubex/chacha v0.1.0 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 + github.com/metacubex/quic-go v0.48.2-0.20241105005628-a3e65bac65b2 github.com/metacubex/randv2 v0.2.0 github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 github.com/metacubex/sing-shadowsocks v0.2.8 github.com/metacubex/sing-shadowsocks2 v0.2.2 - github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 + github.com/metacubex/sing-tun v0.2.7-0.20241106120309-53606a70db98 github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 - github.com/metacubex/sing-wireguard v0.0.0-20240922131718-0f10c39a5531 - github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 + github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 + github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/metacubex/utls v1.6.6 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 github.com/miekg/dns v1.1.62 @@ -37,23 +36,25 @@ require ( github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 github.com/puzpuzpuz/xsync/v3 v3.4.0 + github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a - github.com/sagernet/sing v0.5.0-alpha.13 + github.com/sagernet/sing v0.5.0 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 github.com/sagernet/sing-shadowtls v0.1.4 github.com/samber/lo v1.47.0 github.com/shirou/gopsutil/v3 v3.24.5 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 + github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/wk8/go-ordered-map/v2 v2.1.8 gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 - go.uber.org/automaxprocs v1.5.3 + go.uber.org/automaxprocs v1.6.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.26.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa - golang.org/x/net v0.28.0 - golang.org/x/sys v0.24.0 + golang.org/x/crypto v0.28.0 + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20 + golang.org/x/net v0.30.0 + golang.org/x/sys v0.26.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.3.0 @@ -105,14 +106,15 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.4 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.24.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241105005934-13bf5e941908 diff --git a/go.sum b/go.sum index 2f5b7d0f..b7dc67c5 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= -github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= +github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -42,8 +42,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/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.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -73,8 +71,8 @@ github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 h1:GkMacU5ftc+IEg1449N3UEy2XLDz58W4fkrRu2fibb8= -github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= +github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas= +github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= @@ -106,26 +104,26 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= -github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 h1:CgdUBRxmNlxEGkp35HwvgQ10jwOOUJKWdOxpi8yWi8o= -github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4/go.mod h1:Y7yRGqFE6UQL/3aKPYmiYdjfVkeujJaStP4+jiZMcN8= +github.com/metacubex/quic-go v0.48.2-0.20241105005628-a3e65bac65b2 h1:1prpWzQnhN/LgGTMA6nz86MGcppDUOwfRkhxPOnrzAk= +github.com/metacubex/quic-go v0.48.2-0.20241105005628-a3e65bac65b2/go.mod h1:AiZ+UPgrkO1DTnmiAX4b+kRoV1Vfc65UkYD7RbFlIZA= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA= -github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/metacubex/sing v0.0.0-20241105005934-13bf5e941908 h1:cZYdGEQKfLsw//TI7dk9bdplz48zitpEDbDGusB9d40= +github.com/metacubex/sing v0.0.0-20241105005934-13bf5e941908/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM= github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4= github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo= github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= -github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP8p3Y4P/m74JYu7sQViesi3c8nbmT6cS0Y= -github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE= +github.com/metacubex/sing-tun v0.2.7-0.20241106120309-53606a70db98 h1:vW0QDrzUc4k1yi3A76lDW064zonPj880QFcpTD58u7A= +github.com/metacubex/sing-tun v0.2.7-0.20241106120309-53606a70db98/go.mod h1:GRcrj7VnhvYFsS34cv0J2qTVm5h9DvQuGwliVyVLVvE= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= -github.com/metacubex/sing-wireguard v0.0.0-20240922131718-0f10c39a5531 h1:BoIL2fZZTPzvSxuhng9kWwvUZ8fiMJyrWbgdHIX0CDs= -github.com/metacubex/sing-wireguard v0.0.0-20240922131718-0f10c39a5531/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw= -github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo= -github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= +github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns= +github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8= github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= @@ -162,6 +160,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= +github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= @@ -212,6 +212,10 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -220,26 +224,26 @@ gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiE gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -259,12 +263,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 66bbc89b..3fed360c 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -9,6 +9,7 @@ import ( "strconv" "sync" "time" + _ "unsafe" "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/inbound" @@ -16,9 +17,10 @@ import ( "github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" - G "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/geodata" mihomoHttp "github.com/metacubex/mihomo/component/http" "github.com/metacubex/mihomo/component/iface" + "github.com/metacubex/mihomo/component/keepalive" "github.com/metacubex/mihomo/component/profile" "github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/resolver" @@ -100,7 +102,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders) updateSniffer(cfg.Sniffer) updateHosts(cfg.Hosts) - updateGeneral(cfg.General) + updateGeneral(cfg.General, true) updateNTP(cfg.NTP) updateDNS(cfg.DNS, cfg.General.IPv6) updateListeners(cfg.General, cfg.Listeners, force) @@ -117,7 +119,9 @@ func ApplyConfig(cfg *config.Config, force bool) { runtime.GC() tunnel.OnRunning() hcCompatibleProvider(cfg.Providers) - initExternalUI() + updateUpdater(cfg) + + resolver.ResetConnection() } func initInnerTcp() { @@ -127,7 +131,7 @@ func initInnerTcp() { func GetGeneral() *config.General { ports := listener.GetPorts() var authenticator []string - if auth := authStore.Authenticator(); auth != nil { + if auth := authStore.Default.Authenticator(); auth != nil { authenticator = auth.Users() } @@ -158,22 +162,25 @@ func GetGeneral() *config.General { Interface: dialer.DefaultInterface.Load(), RoutingMark: int(dialer.DefaultRoutingMark.Load()), GeoXUrl: config.GeoXUrl{ - GeoIp: G.GeoIpUrl(), - Mmdb: G.MmdbUrl(), - ASN: G.ASNUrl(), - GeoSite: G.GeoSiteUrl(), + GeoIp: geodata.GeoIpUrl(), + Mmdb: geodata.MmdbUrl(), + ASN: geodata.ASNUrl(), + GeoSite: geodata.GeoSiteUrl(), }, GeoAutoUpdate: updater.GeoAutoUpdate(), GeoUpdateInterval: updater.GeoUpdateInterval(), - GeodataMode: G.GeodataMode(), - GeodataLoader: G.LoaderName(), - GeositeMatcher: G.SiteMatcherName(), + GeodataMode: geodata.GeodataMode(), + GeodataLoader: geodata.LoaderName(), + GeositeMatcher: geodata.SiteMatcherName(), TCPConcurrent: dialer.GetTcpConcurrent(), FindProcessMode: tunnel.FindProcessMode(), Sniffing: tunnel.IsSniffing(), GlobalClientFingerprint: tlsC.GetGlobalFingerprint(), GlobalUA: mihomoHttp.UA(), ETagSupport: resource.ETag(), + KeepAliveInterval: int(keepalive.KeepAliveInterval() / time.Second), + KeepAliveIdle: int(keepalive.KeepAliveIdle() / time.Second), + DisableKeepAlive: keepalive.DisableKeepAlive(), } return general @@ -233,6 +240,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) { resolver.DefaultResolver = nil resolver.DefaultHostMapper = nil resolver.DefaultLocalServer = nil + resolver.ProxyServerHostResolver = nil + resolver.DirectHostResolver = nil dns.ReCreateServer("", nil, nil) return } @@ -249,12 +258,12 @@ func updateDNS(c *config.DNS, generalIPv6 bool) { Default: c.DefaultNameserver, Policy: c.NameServerPolicy, ProxyServer: c.ProxyServerNameserver, - Tunnel: tunnel.Tunnel, + DirectServer: c.DirectNameServer, + DirectFollowPolicy: c.DirectFollowPolicy, CacheAlgorithm: c.CacheAlgorithm, } r := dns.NewResolver(cfg) - pr := dns.NewProxyServerHostResolver(r) m := dns.NewEnhancer(cfg) // reuse cache of old host mapper @@ -264,14 +273,22 @@ func updateDNS(c *config.DNS, generalIPv6 bool) { resolver.DefaultResolver = r resolver.DefaultHostMapper = m - resolver.DefaultLocalServer = dns.NewLocalServer(r, m) + resolver.DefaultLocalServer = dns.NewLocalServer(r.Resolver, m) resolver.UseSystemHosts = c.UseSystemHosts - if pr.Invalid() { - resolver.ProxyServerHostResolver = pr + if r.ProxyResolver.Invalid() { + resolver.ProxyServerHostResolver = r.ProxyResolver + } else { + resolver.ProxyServerHostResolver = r.Resolver } - dns.ReCreateServer(c.Listen, r, m) + if r.DirectResolver.Invalid() { + resolver.DirectHostResolver = r.DirectResolver + } else { + resolver.DirectHostResolver = r.Resolver + } + + dns.ReCreateServer(c.Listen, r.Resolver, m) } func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) { @@ -382,47 +399,68 @@ func updateTunnels(tunnels []LC.Tunnel) { listener.PatchTunnel(tunnels, tunnel.Tunnel) } -func initExternalUI() { - if updater.AutoDownloadUI { - dirEntries, _ := os.ReadDir(updater.ExternalUIPath) - if len(dirEntries) > 0 { - log.Infoln("UI already exists, skip downloading") - } else { - log.Infoln("External UI downloading ...") - updater.DownloadUI() - } +func updateUpdater(cfg *config.Config) { + general := cfg.General + updater.SetGeoAutoUpdate(general.GeoAutoUpdate) + updater.SetGeoUpdateInterval(general.GeoUpdateInterval) + + controller := cfg.Controller + updater.DefaultUiUpdater = updater.NewUiUpdater(controller.ExternalUI, controller.ExternalUIURL, controller.ExternalUIName) + updater.DefaultUiUpdater.AutoDownloadUI() +} + +//go:linkname temporaryUpdateGeneral github.com/metacubex/mihomo/config.temporaryUpdateGeneral +func temporaryUpdateGeneral(general *config.General) func() { + oldGeneral := GetGeneral() + updateGeneral(general, false) + return func() { + updateGeneral(oldGeneral, false) } } -func updateGeneral(general *config.General) { +func updateGeneral(general *config.General, logging bool) { tunnel.SetMode(general.Mode) tunnel.SetFindProcessMode(general.FindProcessMode) resolver.DisableIPv6 = !general.IPv6 - if general.TCPConcurrent { - dialer.SetTcpConcurrent(general.TCPConcurrent) + dialer.SetTcpConcurrent(general.TCPConcurrent) + if logging && general.TCPConcurrent { log.Infoln("Use tcp concurrent") } inbound.SetTfo(general.InboundTfo) inbound.SetMPTCP(general.InboundMPTCP) + keepalive.SetKeepAliveIdle(time.Duration(general.KeepAliveIdle) * time.Second) + keepalive.SetKeepAliveInterval(time.Duration(general.KeepAliveInterval) * time.Second) + keepalive.SetDisableKeepAlive(general.DisableKeepAlive) + adapter.UnifiedDelay.Store(general.UnifiedDelay) dialer.DefaultInterface.Store(general.Interface) dialer.DefaultRoutingMark.Store(int32(general.RoutingMark)) - if general.RoutingMark > 0 { + if logging && general.RoutingMark > 0 { log.Infoln("Use routing mark: %#x", general.RoutingMark) } iface.FlushCache() - G.SetLoader(general.GeodataLoader) - G.SetSiteMatcher(general.GeositeMatcher) + + geodata.SetGeodataMode(general.GeodataMode) + geodata.SetLoader(general.GeodataLoader) + geodata.SetSiteMatcher(general.GeositeMatcher) + geodata.SetGeoIpUrl(general.GeoXUrl.GeoIp) + geodata.SetGeoSiteUrl(general.GeoXUrl.GeoSite) + geodata.SetMmdbUrl(general.GeoXUrl.Mmdb) + geodata.SetASNUrl(general.GeoXUrl.ASN) + mihomoHttp.SetUA(general.GlobalUA) + resource.SetETag(general.ETagSupport) + + tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint) } func updateUsers(users []auth.AuthUser) { authenticator := auth.NewAuthenticator(users) - authStore.SetAuthenticator(authenticator) + authStore.Default.SetAuthenticator(authenticator) if authenticator != nil { log.Infoln("Authentication of local server updated") } @@ -444,12 +482,12 @@ func patchSelectGroup(proxies map[string]C.Proxy) { } for name, proxy := range proxies { - outbound, ok := proxy.(*adapter.Proxy) + outbound, ok := proxy.(C.Proxy) if !ok { continue } - selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble) + selector, ok := outbound.Adapter().(outboundgroup.SelectAble) if !ok { continue } diff --git a/hub/hub.go b/hub/hub.go index e22f7219..69f627ff 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -27,6 +27,12 @@ func WithExternalControllerUnix(externalControllerUnix string) Option { } } +func WithExternalControllerPipe(externalControllerPipe string) Option { + return func(cfg *config.Config) { + cfg.Controller.ExternalControllerPipe = externalControllerPipe + } +} + func WithSecret(secret string) Option { return func(cfg *config.Config) { cfg.Controller.Secret = secret @@ -47,11 +53,16 @@ func applyRoute(cfg *config.Config) { Addr: cfg.Controller.ExternalController, TLSAddr: cfg.Controller.ExternalControllerTLS, UnixAddr: cfg.Controller.ExternalControllerUnix, + PipeAddr: cfg.Controller.ExternalControllerPipe, Secret: cfg.Controller.Secret, Certificate: cfg.TLS.Certificate, PrivateKey: cfg.TLS.PrivateKey, DohServer: cfg.Controller.ExternalDohServer, IsDebug: cfg.General.LogLevel == log.DEBUG, + Cors: route.Cors{ + AllowOrigins: cfg.Controller.Cors.AllowOrigins, + AllowPrivateNetwork: cfg.Controller.Cors.AllowPrivateNetwork, + }, }) } diff --git a/hub/route/configs.go b/hub/route/configs.go index d4bda2bf..b23a35a5 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -24,9 +24,11 @@ import ( func configRouter() http.Handler { r := chi.NewRouter() r.Get("/", getConfigs) - r.Put("/", updateConfigs) - r.Post("/geo", updateGeoDatabases) - r.Patch("/", patchConfigs) + if !embedMode { // disallow update/patch configs in embed mode + r.Put("/", updateConfigs) + r.Post("/geo", updateGeoDatabases) + r.Patch("/", patchConfigs) + } return r } diff --git a/hub/route/groups.go b/hub/route/groups.go index c4e9501f..873a94df 100644 --- a/hub/route/groups.go +++ b/hub/route/groups.go @@ -9,7 +9,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" - "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/profile/cachefile" @@ -17,7 +16,7 @@ import ( "github.com/metacubex/mihomo/tunnel" ) -func GroupRouter() http.Handler { +func groupRouter() http.Handler { r := chi.NewRouter() r.Get("/", getGroups) @@ -32,7 +31,7 @@ func GroupRouter() http.Handler { func getGroups(w http.ResponseWriter, r *http.Request) { var gs []C.Proxy for _, p := range tunnel.Proxies() { - if _, ok := p.(*adapter.Proxy).ProxyAdapter.(C.Group); ok { + if _, ok := p.Adapter().(C.Group); ok { gs = append(gs, p) } } @@ -43,7 +42,7 @@ func getGroups(w http.ResponseWriter, r *http.Request) { func getGroup(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - if _, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group); ok { + if _, ok := proxy.Adapter().(C.Group); ok { render.JSON(w, r, proxy) return } @@ -53,25 +52,15 @@ func getGroup(w http.ResponseWriter, r *http.Request) { func getGroupDelay(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - group, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group) + group, ok := proxy.Adapter().(C.Group) if !ok { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) return } - switch proxy.(*adapter.Proxy).Type() { - case C.URLTest: - if urlTestGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest); ok { - urlTestGroup.ForceSet("") - } - case C.Fallback: - if fallbackGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.Fallback); ok { - fallbackGroup.ForceSet("") - } - } - - if proxy.(*adapter.Proxy).Type() != C.Selector { + if selectAble, ok := proxy.Adapter().(outboundgroup.SelectAble); ok && proxy.Type() != C.Selector { + selectAble.ForceSet("") cachefile.Cache().SetSelected(proxy.Name(), "") } diff --git a/hub/route/patch_android.go b/hub/route/patch_android.go new file mode 100644 index 00000000..5eba2796 --- /dev/null +++ b/hub/route/patch_android.go @@ -0,0 +1,7 @@ +//go:build android && cmfa + +package route + +func init() { + SetEmbedMode(true) // set embed mode default +} diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 69c8e446..ba4e03f9 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/profile/cachefile" @@ -31,6 +30,7 @@ func proxyRouter() http.Handler { r.Get("/", getProxy) r.Get("/delay", getProxyDelay) r.Put("/", updateProxy) + r.Delete("/", unfixedProxy) }) return r } @@ -81,8 +81,8 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { return } - proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy) - selector, ok := proxy.ProxyAdapter.(outboundgroup.SelectAble) + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + selector, ok := proxy.Adapter().(outboundgroup.SelectAble) if !ok { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("Must be a Selector")) @@ -146,3 +146,15 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { "delay": delay, }) } + +func unfixedProxy(w http.ResponseWriter, r *http.Request) { + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + if selectAble, ok := proxy.Adapter().(outboundgroup.SelectAble); ok && proxy.Type() != C.Selector { + selectAble.ForceSet("") + cachefile.Cache().SetSelected(proxy.Name(), "") + render.NoContent(w, r) + return + } + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) +} diff --git a/hub/route/server.go b/hub/route/server.go index b7077563..21102cc3 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -23,10 +23,10 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" "github.com/go-chi/render" "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" + "github.com/sagernet/cors" ) var ( @@ -35,8 +35,15 @@ var ( httpServer *http.Server tlsServer *http.Server unixServer *http.Server + pipeServer *http.Server + + embedMode = false ) +func SetEmbedMode(embed bool) { + embedMode = embed +} + type Traffic struct { Up int64 `json:"up"` Down int64 `json:"down"` @@ -51,33 +58,46 @@ type Config struct { Addr string TLSAddr string UnixAddr string + PipeAddr string Secret string Certificate string PrivateKey string DohServer string IsDebug bool + Cors Cors +} + +type Cors struct { + AllowOrigins []string + AllowPrivateNetwork bool +} + +func (c Cors) Apply(r chi.Router) { + r.Use(cors.New(cors.Options{ + AllowedOrigins: c.AllowOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, + AllowPrivateNetwork: c.AllowPrivateNetwork, + MaxAge: 300, + }).Handler) } func ReCreateServer(cfg *Config) { go start(cfg) go startTLS(cfg) go startUnix(cfg) + if inbound.SupportNamedPipe { + go startPipe(cfg) + } } func SetUIPath(path string) { uiPath = C.Path.Resolve(path) } -func router(isDebug bool, secret string, dohServer string) *chi.Mux { +func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux { r := chi.NewRouter() - corsM := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Content-Type", "Authorization"}, - MaxAge: 300, - }) - r.Use(setPrivateNetworkAccess) - r.Use(corsM.Handler) + cors.Apply(r) if isDebug { r.Mount("/debug", func() http.Handler { r := chi.NewRouter() @@ -100,15 +120,17 @@ func router(isDebug bool, secret string, dohServer string) *chi.Mux { r.Get("/version", version) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) - r.Mount("/group", GroupRouter()) + r.Mount("/group", groupRouter()) r.Mount("/rules", ruleRouter()) r.Mount("/connections", connectionRouter()) r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/cache", cacheRouter()) r.Mount("/dns", dnsRouter()) - r.Mount("/restart", restartRouter()) - r.Mount("/upgrade", upgradeRouter()) + if !embedMode { // disallow restart and upgrade in embed mode + r.Mount("/restart", restartRouter()) + r.Mount("/upgrade", upgradeRouter()) + } addExternalRouters(r) }) @@ -146,7 +168,7 @@ func start(cfg *Config) { log.Infoln("RESTful API listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer), + Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), } httpServer = server if err = server.Serve(l); err != nil { @@ -178,7 +200,7 @@ func startTLS(cfg *Config) { log.Infoln("RESTful API tls listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer), + Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{c}, }, @@ -223,26 +245,48 @@ func startUnix(cfg *Config) { log.Errorln("External controller unix listen error: %s", err) return } + _ = os.Chmod(addr, 0o666) log.Infoln("RESTful API unix listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, "", cfg.DohServer), + Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors), } unixServer = server if err = server.Serve(l); err != nil { log.Errorln("External controller unix serve error: %s", err) } } - } -func setPrivateNetworkAccess(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { - w.Header().Add("Access-Control-Allow-Private-Network", "true") +func startPipe(cfg *Config) { + // first stop existing server + if pipeServer != nil { + _ = pipeServer.Close() + pipeServer = nil + } + + // handle addr + if len(cfg.PipeAddr) > 0 { + if !strings.HasPrefix(cfg.PipeAddr, "\\\\.\\pipe\\") { // windows namedpipe must start with "\\.\pipe\" + log.Errorln("External controller pipe listen error: windows namedpipe must start with \"\\\\.\\pipe\\\"") + return } - next.ServeHTTP(w, r) - }) + + l, err := inbound.ListenNamedPipe(cfg.PipeAddr) + if err != nil { + log.Errorln("External controller pipe listen error: %s", err) + return + } + log.Infoln("RESTful API pipe listening at: %s", l.Addr().String()) + + server := &http.Server{ + Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors), + } + pipeServer = server + if err = server.Serve(l); err != nil { + log.Errorln("External controller pipe serve error: %s", err) + } + } } func safeEuqal(a, b string) bool { diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go index 25c326dd..2fed3f67 100644 --- a/hub/route/upgrade.go +++ b/hub/route/upgrade.go @@ -47,7 +47,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) { } func updateUI(w http.ResponseWriter, r *http.Request) { - err := updater.DownloadUI() + err := updater.DefaultUiUpdater.DownloadUI() if err != nil { log.Warnln("%s", err) render.Status(r, http.StatusInternalServerError) diff --git a/listener/auth/auth.go b/listener/auth/auth.go index 772be3bd..9e7632e8 100644 --- a/listener/auth/auth.go +++ b/listener/auth/auth.go @@ -4,14 +4,30 @@ import ( "github.com/metacubex/mihomo/component/auth" ) -var authenticator auth.Authenticator - -func Authenticator() auth.Authenticator { - return authenticator +type authStore struct { + authenticator auth.Authenticator } -func SetAuthenticator(au auth.Authenticator) { - authenticator = au +func (a *authStore) Authenticator() auth.Authenticator { + return a.authenticator } -func Nil() auth.Authenticator { return nil } +func (a *authStore) SetAuthenticator(authenticator auth.Authenticator) { + a.authenticator = authenticator +} + +func NewAuthStore(authenticator auth.Authenticator) auth.AuthStore { + return &authStore{authenticator} +} + +var Default auth.AuthStore = NewAuthStore(nil) + +type nilAuthStore struct{} + +func (a *nilAuthStore) Authenticator() auth.Authenticator { + return nil +} + +func (a *nilAuthStore) SetAuthenticator(authenticator auth.Authenticator) {} + +var Nil auth.AuthStore = (*nilAuthStore)(nil) // always return nil, even call SetAuthenticator() with a non-nil authenticator diff --git a/listener/http/proxy.go b/listener/http/proxy.go index 04ab98eb..5c08cd45 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -30,7 +30,7 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) { return n, err } -func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { +func HandleConn(c net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) { additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser inUserIdx := len(additions) - 1 client := newClient(c, tunnel, additions) @@ -41,7 +41,7 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, conn := N.NewBufferedConn(c) - authenticator := getAuth() + authenticator := store.Authenticator() keepAlive := true trusted := authenticator == nil // disable authenticate if lru is nil lastUser := "" diff --git a/listener/http/server.go b/listener/http/server.go index 48f12dc5..24f07e8b 100644 --- a/listener/http/server.go +++ b/listener/http/server.go @@ -4,7 +4,6 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/auth" C "github.com/metacubex/mihomo/constant" authStore "github.com/metacubex/mihomo/listener/auth" @@ -33,20 +32,20 @@ func (l *Listener) Close() error { } func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { - return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...) + return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...) } // NewWithAuthenticate // never change type traits because it's used in CMFA func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { - getAuth := authStore.Authenticator + store := authStore.Default if !authenticate { - getAuth = authStore.Nil + store = authStore.Default } - return NewWithAuthenticator(addr, tunnel, getAuth, additions...) + return NewWithAuthenticator(addr, tunnel, store, additions...) } -func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { +func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) { isDefault := false if len(additions) == 0 { isDefault = true @@ -55,8 +54,8 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth inbound.WithSpecialRules(""), } } - l, err := inbound.Listen("tcp", addr) + l, err := inbound.Listen("tcp", addr) if err != nil { return nil, err } @@ -74,19 +73,18 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth } continue } - N.TCPKeepAlive(conn) - getAuth := getAuth - if isDefault { // only apply on default listener + store := store + if isDefault || store == authStore.Default { // only apply on default listener if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) { _ = conn.Close() continue } if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) { - getAuth = authStore.Nil + store = authStore.Nil } } - go HandleConn(conn, tunnel, getAuth, additions...) + go HandleConn(conn, tunnel, store, additions...) } }() diff --git a/listener/inbound/auth.go b/listener/inbound/auth.go index 41f18fc0..85e72494 100644 --- a/listener/inbound/auth.go +++ b/listener/inbound/auth.go @@ -12,7 +12,7 @@ type AuthUser struct { type AuthUsers []AuthUser -func (a AuthUsers) GetAuth() func() auth.Authenticator { +func (a AuthUsers) GetAuthStore() auth.AuthStore { if a != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array if len(a) == 0 { return authStore.Nil @@ -25,7 +25,7 @@ func (a AuthUsers) GetAuth() func() auth.Authenticator { } } authenticator := auth.NewAuthenticator(users) - return func() auth.Authenticator { return authenticator } + return authStore.NewAuthStore(authenticator) } - return authStore.Authenticator + return authStore.Default } diff --git a/listener/inbound/http.go b/listener/inbound/http.go index c78abefd..e20a9a23 100644 --- a/listener/inbound/http.go +++ b/listener/inbound/http.go @@ -45,7 +45,7 @@ func (h *HTTP) Address() string { // Listen implements constant.InboundListener func (h *HTTP) Listen(tunnel C.Tunnel) error { var err error - h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuth(), h.Additions()...) + h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuthStore(), h.Additions()...) if err != nil { return err } diff --git a/listener/inbound/mixed.go b/listener/inbound/mixed.go index 443a2564..1d79929a 100644 --- a/listener/inbound/mixed.go +++ b/listener/inbound/mixed.go @@ -53,7 +53,7 @@ func (m *Mixed) Address() string { // Listen implements constant.InboundListener func (m *Mixed) Listen(tunnel C.Tunnel) error { var err error - m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuth(), m.Additions()...) + m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuthStore(), m.Additions()...) if err != nil { return err } diff --git a/listener/inbound/socks.go b/listener/inbound/socks.go index cf6d1ce4..119eec82 100644 --- a/listener/inbound/socks.go +++ b/listener/inbound/socks.go @@ -71,7 +71,7 @@ func (s *Socks) Address() string { // Listen implements constant.InboundListener func (s *Socks) Listen(tunnel C.Tunnel) error { var err error - if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuth(), s.Additions()...); err != nil { + if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuthStore(), s.Additions()...); err != nil { return err } if s.udp { diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index 12390061..5ac63011 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -37,10 +37,10 @@ func (l *Listener) Close() error { } func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { - return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...) + return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...) } -func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { +func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) { isDefault := false if len(additions) == 0 { isDefault = true @@ -49,6 +49,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth inbound.WithSpecialRules(""), } } + l, err := inbound.Listen("tcp", addr) if err != nil { return nil, err @@ -67,26 +68,24 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth } continue } - getAuth := getAuth - if isDefault { // only apply on default listener + store := store + if isDefault || store == authStore.Default { // only apply on default listener if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { _ = c.Close() continue } if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) { - getAuth = authStore.Nil + store = authStore.Nil } } - go handleConn(c, tunnel, getAuth, additions...) + go handleConn(c, tunnel, store, additions...) } }() return ml, nil } -func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { - N.TCPKeepAlive(conn) - +func handleConn(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) { bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) if err != nil { @@ -95,10 +94,10 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticato switch head[0] { case socks4.Version: - socks.HandleSocks4(bufConn, tunnel, getAuth, additions...) + socks.HandleSocks4(bufConn, tunnel, store, additions...) case socks5.Version: - socks.HandleSocks5(bufConn, tunnel, getAuth, additions...) + socks.HandleSocks5(bufConn, tunnel, store, additions...) default: - http.HandleConn(bufConn, tunnel, getAuth, additions...) + http.HandleConn(bufConn, tunnel, store, additions...) } } diff --git a/listener/redir/tcp.go b/listener/redir/tcp.go index 8474a8e2..47363182 100644 --- a/listener/redir/tcp.go +++ b/listener/redir/tcp.go @@ -4,7 +4,7 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/keepalive" C "github.com/metacubex/mihomo/constant" ) @@ -37,10 +37,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener inbound.WithSpecialRules(""), } } + l, err := net.Listen("tcp", addr) if err != nil { return nil, err } + rl := &Listener{ listener: l, addr: addr, @@ -68,6 +70,6 @@ func handleRedir(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) conn.Close() return } - N.TCPKeepAlive(conn) + keepalive.TCPKeepAlive(conn) tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, additions...)) } diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go index c3843814..b150e4cb 100644 --- a/listener/shadowsocks/tcp.go +++ b/listener/shadowsocks/tcp.go @@ -59,7 +59,6 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi } continue } - N.TCPKeepAlive(c) go sl.HandleConn(c, tunnel, additions...) } }() diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index 1cb798f7..5f2a4292 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/sockopt" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" @@ -153,7 +152,6 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi } continue } - N.TCPKeepAlive(c) go sl.HandleConn(c, tunnel) } diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index c2c668b3..ba337b01 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -279,7 +279,11 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis return } - defaultInterfaceMonitor, err = tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{OverrideAndroidVPN: true}) + overrideAndroidVPN := true + if disable, _ := strconv.ParseBool(os.Getenv("DISABLE_OVERRIDE_ANDROID_VPN")); disable { + overrideAndroidVPN = false + } + defaultInterfaceMonitor, err = tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{InterfaceFinder: interfaceFinder, OverrideAndroidVPN: overrideAndroidVPN}) if err != nil { err = E.Cause(err, "create DefaultInterfaceMonitor") return @@ -440,6 +444,10 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis //l.openAndroidHotspot(tunOptions) + if !l.options.AutoDetectInterface { + resolver.ResetConnection() + } + if options.FileDescriptor != 0 { tunName = fmt.Sprintf("%s(fd=%d)", tunName, options.FileDescriptor) } @@ -507,6 +515,7 @@ func (l *Listener) FlushDefaultInterface() { if old := dialer.DefaultInterface.Swap(autoDetectInterfaceName); old != autoDetectInterfaceName { log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, autoDetectInterfaceName) iface.FlushCache() + resolver.ResetConnection() // reset resolver's connection after default interface changed } return } diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index ce422b16..7a0afa0b 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -121,7 +121,6 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition) } continue } - N.TCPKeepAlive(c) go sl.HandleConn(c, tunnel) } diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index 3e98a602..cc66613e 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -36,10 +36,10 @@ func (l *Listener) Close() error { } func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { - return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...) + return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...) } -func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { +func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) { isDefault := false if len(additions) == 0 { isDefault = true @@ -48,6 +48,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth inbound.WithSpecialRules(""), } } + l, err := inbound.Listen("tcp", addr) if err != nil { return nil, err @@ -66,25 +67,24 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth } continue } - getAuth := getAuth - if isDefault { // only apply on default listener + store := store + if isDefault || store == authStore.Default { // only apply on default listener if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { _ = c.Close() continue } if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) { - getAuth = authStore.Nil + store = authStore.Nil } } - go handleSocks(c, tunnel, getAuth, additions...) + go handleSocks(c, tunnel, store, additions...) } }() return sl, nil } -func handleSocks(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { - N.TCPKeepAlive(conn) +func handleSocks(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) { bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) if err != nil { @@ -94,16 +94,16 @@ func handleSocks(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticat switch head[0] { case socks4.Version: - HandleSocks4(bufConn, tunnel, getAuth, additions...) + HandleSocks4(bufConn, tunnel, store, additions...) case socks5.Version: - HandleSocks5(bufConn, tunnel, getAuth, additions...) + HandleSocks5(bufConn, tunnel, store, additions...) default: conn.Close() } } -func HandleSocks4(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { - authenticator := getAuth() +func HandleSocks4(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) { + authenticator := store.Authenticator() addr, _, user, err := socks4.ServerHandshake(conn, authenticator) if err != nil { conn.Close() @@ -113,8 +113,8 @@ func HandleSocks4(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authentica tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...)) } -func HandleSocks5(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { - authenticator := getAuth() +func HandleSocks5(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) { + authenticator := store.Authenticator() target, command, user, err := socks5.ServerHandshake(conn, authenticator) if err != nil { conn.Close() diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index fa7e7dbe..6056047a 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -4,7 +4,7 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/keepalive" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/socks5" ) @@ -33,7 +33,7 @@ func (l *Listener) Close() error { func (l *Listener) handleTProxy(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) - N.TCPKeepAlive(conn) + keepalive.TCPKeepAlive(conn) // TProxy's conn.LocalAddr() is target address, so we set from l.listener additions = append([]inbound.Addition{inbound.WithInAddr(l.listener.Addr())}, additions...) tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TPROXY, additions...)) diff --git a/listener/tunnel/tcp.go b/listener/tunnel/tcp.go index 794dc8ac..7c916a38 100644 --- a/listener/tunnel/tcp.go +++ b/listener/tunnel/tcp.go @@ -5,7 +5,6 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/socks5" ) @@ -35,7 +34,6 @@ func (l *Listener) Close() error { } func (l *Listener) handleTCP(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { - N.TCPKeepAlive(conn) tunnel.HandleTCPConn(inbound.NewSocket(l.target, conn, C.TUNNEL, additions...)) } diff --git a/main.go b/main.go index 8910a006..685fc89f 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,11 @@ package main import ( + "context" "encoding/base64" "flag" "fmt" + "net" "os" "os/signal" "path/filepath" @@ -35,6 +37,7 @@ var ( externalUI string externalController string externalControllerUnix string + externalControllerPipe string secret string ) @@ -45,6 +48,7 @@ func init() { flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory") flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address") flag.StringVar(&externalControllerUnix, "ext-ctl-unix", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_UNIX"), "override external controller unix address") + flag.StringVar(&externalControllerPipe, "ext-ctl-pipe", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_PIPE"), "override external controller pipe address") flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API") flag.BoolVar(&geodataMode, "m", false, "set geodata mode") flag.BoolVar(&version, "v", false, "show current version of mihomo") @@ -53,6 +57,12 @@ func init() { } func main() { + // Defensive programming: panic when code mistakenly calls net.DefaultResolver + net.DefaultResolver.PreferGo = true + net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) { + panic("should never be called") + } + _, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {})) if len(os.Args) > 1 && os.Args[1] == "convert-ruleset" { @@ -133,6 +143,9 @@ func main() { if externalControllerUnix != "" { options = append(options, hub.WithExternalControllerUnix(externalControllerUnix)) } + if externalControllerPipe != "" { + options = append(options, hub.WithExternalControllerPipe(externalControllerPipe)) + } if secret != "" { options = append(options, hub.WithSecret(secret)) } @@ -156,19 +169,9 @@ func main() { case <-termSign: return case <-hupSign: - var cfg *config.Config - var err error - if configString != "" { - cfg, err = executor.ParseWithBytes(configBytes) - } else { - cfg, err = executor.ParseWithPath(C.Path.Config()) - } - if err == nil { - hub.ApplyConfig(cfg) - } else { + if err := hub.Parse(configBytes, options...); err != nil { log.Errorln("Parse config error: %s", err.Error()) } - } } } diff --git a/rules/common/ipasn.go b/rules/common/ipasn.go index 813923ac..7d554103 100644 --- a/rules/common/ipasn.go +++ b/rules/common/ipasn.go @@ -1,8 +1,6 @@ package common import ( - "strconv" - "github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/mmdb" C "github.com/metacubex/mihomo/constant" @@ -26,17 +24,14 @@ func (a *ASN) Match(metadata *C.Metadata) (bool, string) { return false, "" } - result := mmdb.ASNInstance().LookupASN(ip.AsSlice()) - asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10) - ipASN := asnNumber + " " + result.AutonomousSystemOrganization + asn, aso := mmdb.ASNInstance().LookupASN(ip.AsSlice()) if a.isSourceIP { - metadata.SrcIPASN = ipASN + metadata.SrcIPASN = asn + " " + aso } else { - metadata.DstIPASN = ipASN + metadata.DstIPASN = asn + " " + aso } - match := a.asn == asnNumber - return match, a.adapter + return a.asn == asn, a.adapter } func (a *ASN) RuleType() C.RuleType { diff --git a/transport/hysteria/transport/client.go b/transport/hysteria/transport/client.go index f5cc9f07..91876ea9 100644 --- a/transport/hysteria/transport/client.go +++ b/transport/hysteria/transport/client.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "fmt" "net" - "strings" "time" "github.com/metacubex/quic-go" @@ -16,9 +15,7 @@ import ( "github.com/metacubex/mihomo/transport/hysteria/utils" ) -type ClientTransport struct { - Dialer *net.Dialer -} +type ClientTransport struct{} func (ct *ClientTransport) quicPacketConn(proto string, rAddr net.Addr, serverPorts string, obfs obfsPkg.Obfuscator, hopInterval time.Duration, dialer utils.PacketDialer) (net.PacketConn, error) { server := rAddr.String() @@ -86,23 +83,3 @@ func (ct *ClientTransport) QUICDial(proto string, server string, serverPorts str } return qs, nil } - -func (ct *ClientTransport) DialTCP(raddr *net.TCPAddr) (*net.TCPConn, error) { - conn, err := ct.Dialer.Dial("tcp", raddr.String()) - if err != nil { - return nil, err - } - return conn.(*net.TCPConn), nil -} - -func (ct *ClientTransport) ListenUDP() (*net.UDPConn, error) { - return net.ListenUDP("udp", nil) -} - -func isMultiPortAddr(addr string) bool { - _, portStr, err := net.SplitHostPort(addr) - if err == nil && (strings.Contains(portStr, ",") || strings.Contains(portStr, "-")) { - return true - } - return false -} diff --git a/tunnel/connection.go b/tunnel/connection.go index e96545e8..1ea0678c 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -1,16 +1,109 @@ package tunnel import ( + "context" "errors" "net" "net/netip" "time" + "github.com/metacubex/mihomo/common/lru" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" ) +type packetSender struct { + ctx context.Context + cancel context.CancelFunc + ch chan C.PacketAdapter + cache *lru.LruCache[string, netip.Addr] +} + +// newPacketSender return a chan based C.PacketSender +// It ensures that packets can be sent sequentially and without blocking +func newPacketSender() C.PacketSender { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan C.PacketAdapter, senderCapacity) + return &packetSender{ + ctx: ctx, + cancel: cancel, + ch: ch, + cache: lru.New[string, netip.Addr](lru.WithSize[string, netip.Addr](senderCapacity)), + } +} + +func (s *packetSender) Process(pc C.PacketConn, proxy C.WriteBackProxy) { + for { + select { + case <-s.ctx.Done(): + return // sender closed + case packet := <-s.ch: + if proxy != nil { + proxy.UpdateWriteBack(packet) + } + if err := s.ResolveUDP(packet.Metadata()); err != nil { + log.Warnln("[UDP] Resolve Ip error: %s", err) + } else { + _ = handleUDPToRemote(packet, pc, packet.Metadata()) + } + packet.Drop() + } + } +} + +func (s *packetSender) dropAll() { + for { + select { + case data := <-s.ch: + data.Drop() // drop all data still in chan + default: + return // no data, exit goroutine + } + } +} + +func (s *packetSender) Send(packet C.PacketAdapter) { + select { + case <-s.ctx.Done(): + packet.Drop() // sender closed before Send() + return + default: + } + + select { + case s.ch <- packet: + // put ok, so don't drop packet, will process by other side of chan + case <-s.ctx.Done(): + packet.Drop() // sender closed when putting data to chan + default: + packet.Drop() // chan is full + } +} + +func (s *packetSender) Close() { + s.cancel() + s.dropAll() +} + +func (s *packetSender) ResolveUDP(metadata *C.Metadata) (err error) { + // local resolve UDP dns + if !metadata.Resolved() { + ip, ok := s.cache.Get(metadata.Host) + if !ok { + ip, err = resolver.ResolveIP(s.ctx, metadata.Host) + if err != nil { + return err + } + s.cache.Set(metadata.Host, ip) + } + + metadata.DstIP = ip + } + return nil +} + func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { addr := metadata.UDPAddr() if addr == nil { @@ -26,8 +119,9 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata return nil } -func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) { +func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, sender C.PacketSender, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) { defer func() { + sender.Close() _ = pc.Close() closeAllLocalCoon(key) natTable.Delete(key) diff --git a/tunnel/statistic/manager.go b/tunnel/statistic/manager.go index 08747118..0b309299 100644 --- a/tunnel/statistic/manager.go +++ b/tunnel/statistic/manager.go @@ -114,10 +114,8 @@ func (m *Manager) handle() { ticker := time.NewTicker(time.Second) for range ticker.C { - m.uploadBlip.Store(m.uploadTemp.Load()) - m.uploadTemp.Store(0) - m.downloadBlip.Store(m.downloadTemp.Load()) - m.downloadTemp.Store(0) + m.uploadBlip.Store(m.uploadTemp.Swap(0)) + m.downloadBlip.Store(m.downloadTemp.Swap(0)) } } diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index 0bf7995d..ca592d97 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -117,24 +117,19 @@ func (tt *tcpTracker) Upstream() any { } func parseRemoteDestination(addr net.Addr, conn C.Connection) string { - if addr == nil && conn != nil { - return conn.RemoteDestination() - } - if addrPort, err := netip.ParseAddrPort(addr.String()); err == nil && addrPort.Addr().IsValid() { - return addrPort.Addr().String() - } else { - if conn != nil { - return conn.RemoteDestination() - } else { - return "" + if addr != nil { + if addrPort, err := netip.ParseAddrPort(addr.String()); err == nil && addrPort.Addr().IsValid() { + return addrPort.Addr().String() } } + if conn != nil { + return conn.RemoteDestination() + } + return "" } func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *tcpTracker { - if conn != nil { - metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn) - } + metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn) t := &tcpTracker{ Conn: conn, diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 60ba0323..b1b4add5 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -28,10 +28,15 @@ import ( "github.com/metacubex/mihomo/tunnel/statistic" ) +const ( + queueCapacity = 64 // chan capacity tcpQueue and udpQueue + senderCapacity = 128 // chan capacity of PacketSender +) + var ( status = newAtomicStatus(Suspend) - tcpQueue = make(chan C.ConnContext, 200) - udpQueue = make(chan C.PacketAdapter, 200) + udpInit sync.Once + udpQueues []chan C.PacketAdapter natTable = nat.New() rules []C.Rule listeners = make(map[string]C.InboundListener) @@ -41,6 +46,12 @@ var ( ruleProviders map[string]provider.RuleProvider configMux sync.RWMutex + // for compatibility, lazy init + tcpQueue chan C.ConnContext + tcpInOnce sync.Once + udpQueue chan C.PacketAdapter + udpInOnce sync.Once + // Outbound Rule mode = Rule @@ -68,11 +79,33 @@ func (t tunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) { handleTCPConn(connCtx) } +func initUDP() { + numUDPWorkers := 4 + if num := runtime.GOMAXPROCS(0); num > numUDPWorkers { + numUDPWorkers = num + } + + udpQueues = make([]chan C.PacketAdapter, numUDPWorkers) + for i := 0; i < numUDPWorkers; i++ { + queue := make(chan C.PacketAdapter, queueCapacity) + udpQueues[i] = queue + go processUDP(queue) + } +} + func (t tunnel) HandleUDPPacket(packet C.UDPPacket, metadata *C.Metadata) { + udpInit.Do(initUDP) + packetAdapter := C.NewPacketAdapter(packet, metadata) + key := packetAdapter.Key() + + hash := utils.MapHash(key) + queueNo := uint(hash) % uint(len(udpQueues)) + select { - case udpQueue <- packetAdapter: + case udpQueues[queueNo] <- packetAdapter: default: + packet.Drop() } } @@ -128,19 +161,31 @@ func IsSniffing() bool { return sniffingEnable } -func init() { - go process() -} - // TCPIn return fan-in queue // Deprecated: using Tunnel instead func TCPIn() chan<- C.ConnContext { + tcpInOnce.Do(func() { + tcpQueue = make(chan C.ConnContext, queueCapacity) + go func() { + for connCtx := range tcpQueue { + go handleTCPConn(connCtx) + } + }() + }) return tcpQueue } // UDPIn return fan-in udp queue // Deprecated: using Tunnel instead func UDPIn() chan<- C.PacketAdapter { + udpInOnce.Do(func() { + udpQueue = make(chan C.PacketAdapter, queueCapacity) + go func() { + for packet := range udpQueue { + Tunnel.HandleUDPPacket(packet, packet.Metadata()) + } + }() + }) return udpQueue } @@ -242,29 +287,6 @@ func isHandle(t C.Type) bool { return status == Running || (status == Inner && t == C.INNER) } -// processUDP starts a loop to handle udp packet -func processUDP() { - queue := udpQueue - for conn := range queue { - handleUDPConn(conn) - } -} - -func process() { - numUDPWorkers := 4 - if num := runtime.GOMAXPROCS(0); num > numUDPWorkers { - numUDPWorkers = num - } - for i := 0; i < numUDPWorkers; i++ { - go processUDP() - } - - queue := tcpQueue - for conn := range queue { - go handleTCPConn(conn) - } -} - func needLookupIP(metadata *C.Metadata) bool { return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP.IsValid() } @@ -324,6 +346,13 @@ func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err erro return } +// processUDP starts a loop to handle udp packet +func processUDP(queue chan C.PacketAdapter) { + for conn := range queue { + handleUDPConn(conn) + } +} + func handleUDPConn(packet C.PacketAdapter) { if !isHandle(packet.Metadata().Type) { packet.Drop() @@ -353,85 +382,58 @@ func handleUDPConn(packet C.PacketAdapter) { snifferDispatcher.UDPSniff(packet) } - // local resolve UDP dns - if !metadata.Resolved() { - ip, err := resolver.ResolveIP(context.Background(), metadata.Host) - if err != nil { - return - } - metadata.DstIP = ip - } - - key := packet.LocalAddr().String() - - handle := func() bool { - pc, proxy := natTable.Get(key) - if pc != nil { - if proxy != nil { - proxy.UpdateWriteBack(packet) + key := packet.Key() + sender, loaded := natTable.GetOrCreate(key, newPacketSender) + if !loaded { + dial := func() (C.PacketConn, C.WriteBackProxy, error) { + if err := sender.ResolveUDP(metadata); err != nil { + log.Warnln("[UDP] Resolve Ip error: %s", err) + return nil, nil, err } - _ = handleUDPToRemote(packet, pc, metadata) - return true - } - return false - } - if handle() { - packet.Drop() - return - } + proxy, rule, err := resolveMetadata(metadata) + if err != nil { + log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) + return nil, nil, err + } - cond, loaded := natTable.GetOrCreateLock(key) + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) + defer cancel() + rawPc, err := retry(ctx, func(ctx context.Context) (C.PacketConn, error) { + return proxy.ListenPacketContext(ctx, metadata.Pure()) + }, func(err error) { + logMetadataErr(metadata, rule, proxy, err) + }) + if err != nil { + return nil, nil, err + } + logMetadata(metadata, rule, rawPc) - go func() { - defer packet.Drop() + pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true) - if loaded { - cond.L.Lock() - cond.Wait() - handle() - cond.L.Unlock() - return + if rawPc.Chains().Last() == "REJECT-DROP" { + _ = pc.Close() + return nil, nil, errors.New("rejected drop packet") + } + + oAddrPort := metadata.AddrPort() + writeBackProxy := nat.NewWriteBackProxy(packet) + + go handleUDPToLocal(writeBackProxy, pc, sender, key, oAddrPort, fAddr) + return pc, writeBackProxy, nil } - defer func() { - natTable.DeleteLock(key) - cond.Broadcast() + go func() { + pc, proxy, err := dial() + if err != nil { + sender.Close() + natTable.Delete(key) + return + } + sender.Process(pc, proxy) }() - - proxy, rule, err := resolveMetadata(metadata) - if err != nil { - log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) - return - } - - ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) - defer cancel() - rawPc, err := retry(ctx, func(ctx context.Context) (C.PacketConn, error) { - return proxy.ListenPacketContext(ctx, metadata.Pure()) - }, func(err error) { - logMetadataErr(metadata, rule, proxy, err) - }) - if err != nil { - return - } - logMetadata(metadata, rule, rawPc) - - pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true) - - if rawPc.Chains().Last() == "REJECT-DROP" { - pc.Close() - return - } - - oAddrPort := metadata.AddrPort() - writeBackProxy := nat.NewWriteBackProxy(packet) - natTable.Set(key, pc, writeBackProxy) - - go handleUDPToLocal(writeBackProxy, pc, key, oAddrPort, fAddr) - - handle() - }() + } + sender.Send(packet) // nonblocking } func handleTCPConn(connCtx C.ConnContext) { @@ -623,7 +625,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { // normal check for process uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort)) if err != nil { - log.Debugln("[Process] find process %s error: %v", metadata.String(), err) + log.Debugln("[Process] find process error for %s: %v", metadata.String(), err) } else { metadata.Process = filepath.Base(path) metadata.ProcessPath = path @@ -637,7 +639,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { // check package names pkg, err := P.FindPackageName(metadata) if err != nil { - log.Debugln("[Process] find process %s error: %v", metadata.String(), err) + log.Debugln("[Process] find process error for %s: %v", metadata.String(), err) } else { metadata.Process = pkg }