diff --git a/.github/mihomo.service b/.github/mihomo.service index a884059f..f34b6a6a 100644 --- a/.github/mihomo.service +++ b/.github/mihomo.service @@ -6,8 +6,8 @@ After=network.target NetworkManager.service systemd-networkd.service iwd.service Type=simple LimitNPROC=500 LimitNOFILE=1000000 -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE Restart=always ExecStartPre=/usr/bin/sleep 2s ExecStart=/usr/bin/mihomo -d /etc/mihomo diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f9bbbba9..df320d29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,8 @@ jobs: - { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test } - { goos: linux, goarch: amd64, goamd64: v3, output: amd64 } - { goos: linux, goarch: arm64, output: arm64 } + - { goos: linux, goarch: arm, goarm: '5', output: armv5 } + - { goos: linux, goarch: arm, goarm: '6', output: armv6 } - { goos: linux, goarch: arm, goarm: '7', output: armv7 } - { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat } - { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat } @@ -141,7 +143,7 @@ jobs: run: | go test ./... - - name: Update UA + - name: Update CA run: | sudo apt-get install ca-certificates sudo update-ca-certificates @@ -175,6 +177,11 @@ jobs: else ARCH=${{matrix.jobs.goarch}} fi + PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' ) + if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then + PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}" + fi + mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo @@ -192,7 +199,7 @@ jobs: cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control < 0 { if len(option.Reserved) != 3 { @@ -162,29 +157,28 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { copy(reserved[:], option.Reserved) } var isConnect bool - var connectAddr M.Socksaddr if len(option.Peers) < 2 { isConnect = true if len(option.Peers) == 1 { - connectAddr = option.Peers[0].Addr() + outbound.connectAddr = option.Peers[0].Addr() } else { - connectAddr = option.Addr() + outbound.connectAddr = option.Addr() } } - outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr.AddrPort(), reserved) + outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, outbound.connectAddr.AddrPort(), reserved) - localPrefixes, err := option.Prefixes() + var err error + outbound.localPrefixes, err = option.Prefixes() if err != nil { return nil, err } - var privateKey string { bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey) if err != nil { return nil, E.Cause(err, "decode private key") } - privateKey = hex.EncodeToString(bytes) + option.PrivateKey = hex.EncodeToString(bytes) } if len(option.Peers) > 0 { @@ -230,110 +224,16 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { option.PreSharedKey = hex.EncodeToString(bytes) } } - - var ( - initOk atomic.Bool - initMutex sync.Mutex - initErr error - ) - - outbound.init = func(ctx context.Context) error { - if initOk.Load() { - return nil - } - initMutex.Lock() - defer initMutex.Unlock() - // double check like sync.Once - if initOk.Load() { - return nil - } - if initErr != nil { - return initErr - } - - outbound.bind.ResetReservedForEndpoint() - ipcConf := "private_key=" + privateKey - if len(option.Peers) > 0 { - for i, peer := range option.Peers { - destination, err := resolv(ctx, peer.Addr()) - if err != nil { - // !!! do not set initErr here !!! - // let us can retry domain resolve in next time - return E.Cause(err, "resolve endpoint domain for peer ", i) - } - ipcConf += "\npublic_key=" + peer.PublicKey - ipcConf += "\nendpoint=" + destination.String() - if peer.PreSharedKey != "" { - ipcConf += "\npreshared_key=" + peer.PreSharedKey - } - for _, allowedIP := range peer.AllowedIPs { - ipcConf += "\nallowed_ip=" + allowedIP - } - if len(peer.Reserved) > 0 { - copy(reserved[:], option.Reserved) - outbound.bind.SetReservedForEndpoint(destination, reserved) - } - } - } else { - ipcConf += "\npublic_key=" + option.PublicKey - destination, err := resolv(ctx, connectAddr) - if err != nil { - // !!! do not set initErr here !!! - // let us can retry domain resolve in next time - return E.Cause(err, "resolve endpoint domain") - } - outbound.bind.SetConnectAddr(destination) - ipcConf += "\nendpoint=" + destination.String() - if option.PreSharedKey != "" { - ipcConf += "\npreshared_key=" + option.PreSharedKey - } - var has4, has6 bool - for _, address := range localPrefixes { - if address.Addr().Is4() { - has4 = true - } else { - has6 = true - } - } - if has4 { - ipcConf += "\nallowed_ip=0.0.0.0/0" - } - if has6 { - ipcConf += "\nallowed_ip=::/0" - } - } - - if option.PersistentKeepalive != 0 { - ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive) - } - - if debug.Enabled { - log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf)) - } - err = outbound.device.IpcSet(ipcConf) - if err != nil { - initErr = E.Cause(err, "setup wireguard") - return initErr - } - - err = outbound.tunDevice.Start() - if err != nil { - initErr = err - return initErr - } - - initOk.Store(true) - return nil - } + outbound.option = option mtu := option.MTU if mtu == 0 { mtu = 1408 } - if len(localPrefixes) == 0 { + if len(outbound.localPrefixes) == 0 { return nil, E.New("missing local address") } - outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu)) + outbound.tunDevice, err = wireguard.NewStackDevice(outbound.localPrefixes, uint32(mtu)) if err != nil { return nil, E.Cause(err, "create WireGuard device") } @@ -347,7 +247,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { }, option.Workers) var has6 bool - for _, address := range localPrefixes { + for _, address := range outbound.localPrefixes { if !address.Addr().Unmap().Is4() { has6 = true break @@ -373,11 +273,117 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { return outbound, nil } +func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) { + if address.Addr.IsValid() { + return address.AddrPort(), nil + } + udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.prefer) + if err != nil { + return netip.AddrPort{}, err + } + // net.ResolveUDPAddr maybe return 4in6 address, so unmap at here + addrPort := udpAddr.AddrPort() + return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil +} + +func (w *WireGuard) init(ctx context.Context) error { + if w.initOk.Load() { + return nil + } + w.initMutex.Lock() + defer w.initMutex.Unlock() + // double check like sync.Once + if w.initOk.Load() { + return nil + } + if w.initErr != nil { + return w.initErr + } + + w.bind.ResetReservedForEndpoint() + ipcConf := "private_key=" + w.option.PrivateKey + if len(w.option.Peers) > 0 { + for i, peer := range w.option.Peers { + destination, err := w.resolve(ctx, peer.Addr()) + if err != nil { + // !!! do not set initErr here !!! + // let us can retry domain resolve in next time + return E.Cause(err, "resolve endpoint domain for peer ", i) + } + ipcConf += "\npublic_key=" + peer.PublicKey + ipcConf += "\nendpoint=" + destination.String() + if peer.PreSharedKey != "" { + ipcConf += "\npreshared_key=" + peer.PreSharedKey + } + for _, allowedIP := range peer.AllowedIPs { + ipcConf += "\nallowed_ip=" + allowedIP + } + if len(peer.Reserved) > 0 { + var reserved [3]uint8 + copy(reserved[:], w.option.Reserved) + w.bind.SetReservedForEndpoint(destination, reserved) + } + } + } else { + ipcConf += "\npublic_key=" + w.option.PublicKey + destination, err := w.resolve(ctx, w.connectAddr) + if err != nil { + // !!! do not set initErr here !!! + // let us can retry domain resolve in next time + return E.Cause(err, "resolve endpoint domain") + } + w.bind.SetConnectAddr(destination) + ipcConf += "\nendpoint=" + destination.String() + if w.option.PreSharedKey != "" { + ipcConf += "\npreshared_key=" + w.option.PreSharedKey + } + var has4, has6 bool + for _, address := range w.localPrefixes { + if address.Addr().Is4() { + has4 = true + } else { + has6 = true + } + } + if has4 { + ipcConf += "\nallowed_ip=0.0.0.0/0" + } + if has6 { + ipcConf += "\nallowed_ip=::/0" + } + } + + if w.option.PersistentKeepalive != 0 { + ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", w.option.PersistentKeepalive) + } + + if debug.Enabled { + log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf)) + } + err := w.device.IpcSet(ipcConf) + if err != nil { + w.initErr = E.Cause(err, "setup wireguard") + return w.initErr + } + + err = w.tunDevice.Start() + if err != nil { + w.initErr = err + return w.initErr + } + + w.initOk.Store(true) + return nil +} + func closeWireGuard(w *WireGuard) { if w.device != nil { w.device.Close() } _ = common.Close(w.tunDevice) + if w.closeCh != nil { + close(w.closeCh) + } } func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { @@ -416,9 +422,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat if err = w.init(ctx); err != nil { return nil, err } - if err != nil { - return nil, err - } if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" { r := resolver.DefaultResolver if w.resolver != nil { diff --git a/adapter/outbound/wireguard_test.go b/adapter/outbound/wireguard_test.go new file mode 100644 index 00000000..20dbdbdd --- /dev/null +++ b/adapter/outbound/wireguard_test.go @@ -0,0 +1,44 @@ +//go:build with_gvisor + +package outbound + +import ( + "context" + "runtime" + "testing" + "time" +) + +func TestWireGuardGC(t *testing.T) { + option := WireGuardOption{} + option.Server = "162.159.192.1" + option.Port = 2408 + option.PrivateKey = "iOx7749AdqH3IqluG7+0YbGKd0m1mcEXAfGRzpy9rG8=" + option.PublicKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" + option.Ip = "172.16.0.2" + option.Ipv6 = "2606:4700:110:8d29:be92:3a6a:f4:c437" + option.Reserved = []uint8{51, 69, 125} + wg, err := NewWireGuard(option) + if err != nil { + t.Error(err) + } + closeCh := make(chan struct{}) + wg.closeCh = closeCh + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + err = wg.init(ctx) + if err != nil { + t.Error(err) + } + // must do a small sleep before test GC + // because it maybe deadlocks if w.device.Close call too fast after w.device.Start + time.Sleep(10 * time.Millisecond) + wg = nil + runtime.GC() + select { + case <-closeCh: + return + case <-ctx.Done(): + t.Error("timeout not GC") + } +} diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 74947587..876c92fa 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -88,6 +88,29 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide } groupOption.ExpectedStatus = status + if len(groupOption.Use) != 0 { + PDs, err := getProviders(providersMap, groupOption.Use) + if err != nil { + return nil, fmt.Errorf("%s: %w", groupName, err) + } + + // if test URL is empty, use the first health check URL of providers + if groupOption.URL == "" { + for _, pd := range PDs { + if pd.HealthCheckURL() != "" { + groupOption.URL = pd.HealthCheckURL() + break + } + } + if groupOption.URL == "" { + groupOption.URL = C.DefaultTestURL + } + } else { + addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval)) + } + providers = append(providers, PDs...) + } + if len(groupOption.Proxies) != 0 { ps, err := getProxies(proxyMap, groupOption.Proxies) if err != nil { @@ -98,14 +121,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) } - // select don't need health check + if groupOption.URL == "" { + groupOption.URL = C.DefaultTestURL + } + + // select don't need auto health check if groupOption.Type != "select" && groupOption.Type != "relay" { if groupOption.Interval == 0 { groupOption.Interval = 300 } - if groupOption.URL == "" { - groupOption.URL = C.DefaultTestURL - } } hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus) @@ -115,34 +139,10 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, fmt.Errorf("%s: %w", groupName, err) } - providers = append(providers, pd) + providers = append([]types.ProxyProvider{pd}, providers...) providersMap[groupName] = pd } - if len(groupOption.Use) != 0 { - list, err := getProviders(providersMap, groupOption.Use) - if err != nil { - return nil, fmt.Errorf("%s: %w", groupName, err) - } - - if groupOption.URL == "" { - for _, p := range list { - if p.HealthCheckURL() != "" { - groupOption.URL = p.HealthCheckURL() - } - break - } - - if groupOption.URL == "" { - groupOption.URL = C.DefaultTestURL - } - } - - // different proxy groups use different test URL - addTestUrlToProviders(list, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval)) - providers = append(providers, list...) - } - var group C.ProxyAdapter switch groupOption.Type { case "url-test": diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index 2e436669..1094668d 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -44,14 +44,16 @@ type proxyProviderSchema struct { Type string `provider:"type"` Path string `provider:"path,omitempty"` URL string `provider:"url,omitempty"` + Proxy string `provider:"proxy,omitempty"` Interval int `provider:"interval,omitempty"` Filter string `provider:"filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"` ExcludeType string `provider:"exclude-type,omitempty"` DialerProxy string `provider:"dialer-proxy,omitempty"` - HealthCheck healthCheckSchema `provider:"health-check,omitempty"` - Override OverrideSchema `provider:"override,omitempty"` + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Override OverrideSchema `provider:"override,omitempty"` + Header map[string][]string `provider:"header,omitempty"` } func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { @@ -86,16 +88,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide path := C.Path.Resolve(schema.Path) vehicle = resource.NewFileVehicle(path) case "http": + path := C.Path.GetPathByHash("proxies", schema.URL) if schema.Path != "" { - path := C.Path.Resolve(schema.Path) + path = C.Path.Resolve(schema.Path) if !features.CMFA && !C.Path.IsSafePath(path) { return nil, fmt.Errorf("%w: %s", errSubPath, path) } - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } else { - path := C.Path.GetPathByHash("proxies", schema.URL) - vehicle = resource.NewHTTPVehicle(schema.URL, path) } + vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header) default: return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 2715a309..a40a50ec 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -124,8 +124,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() { go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), - http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), + http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy()) if err != nil { return } @@ -133,8 +133,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() { userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) if userInfoStr == "" { - resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), - http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil) + resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), + http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy()) if err != nil { return } diff --git a/common/convert/converter.go b/common/convert/converter.go index 809aa94f..222dd9fa 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -330,7 +330,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { vmess["h2-opts"] = h2Opts - case "ws": + case "ws", "httpupgrade": headers := make(map[string]any) wsOpts := make(map[string]any) wsOpts["path"] = []string{"/"} @@ -338,7 +338,30 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { headers["Host"] = host.(string) } if path, ok := values["path"]; ok && path != "" { - wsOpts["path"] = path.(string) + path := path.(string) + pathURL, err := url.Parse(path) + if err == nil { + query := pathURL.Query() + if earlyData := query.Get("ed"); earlyData != "" { + med, err := strconv.Atoi(earlyData) + if err == nil { + switch network { + case "ws": + wsOpts["max-early-data"] = med + wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol" + case "httpupgrade": + wsOpts["v2ray-http-upgrade-fast-open"] = true + } + query.Del("ed") + pathURL.RawQuery = query.Encode() + path = pathURL.String() + } + } + if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" { + wsOpts["early-data-header-name"] = earlyDataHeader + } + } + wsOpts["path"] = path } wsOpts["headers"] = headers vmess["ws-opts"] = wsOpts diff --git a/common/convert/v.go b/common/convert/v.go index 2d8cf732..4102ab75 100644 --- a/common/convert/v.go +++ b/common/convert/v.go @@ -100,7 +100,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m h2Opts["headers"] = headers proxy["h2-opts"] = h2Opts - case "ws": + case "ws", "httpupgrade": headers := make(map[string]any) wsOpts := make(map[string]any) headers["User-Agent"] = RandUserAgent() @@ -113,7 +113,13 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m if err != nil { return fmt.Errorf("bad WebSocket max early data size: %v", err) } - wsOpts["max-early-data"] = med + switch network { + case "ws": + wsOpts["max-early-data"] = med + wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol" + case "httpupgrade": + wsOpts["v2ray-http-upgrade-fast-open"] = true + } } if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" { wsOpts["early-data-header-name"] = earlyDataHeader diff --git a/component/ca/config.go b/component/ca/config.go index 53cb98ab..d56809a1 100644 --- a/component/ca/config.go +++ b/component/ca/config.go @@ -13,6 +13,8 @@ import ( "strconv" "strings" "sync" + + C "github.com/metacubex/mihomo/constant" ) var trustCerts []*x509.Certificate @@ -117,7 +119,7 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu var certificate []byte var err error if len(customCA) > 0 { - certificate, err = os.ReadFile(customCA) + certificate, err = os.ReadFile(C.Path.Resolve(customCA)) if err != nil { return nil, fmt.Errorf("load ca error: %w", err) } diff --git a/component/dhcp/conn.go b/component/dhcp/conn.go index ff26275b..61c801bd 100644 --- a/component/dhcp/conn.go +++ b/component/dhcp/conn.go @@ -3,6 +3,7 @@ package dhcp import ( "context" "net" + "net/netip" "runtime" "github.com/metacubex/mihomo/component/dialer" @@ -24,5 +25,5 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er options = append(options, dialer.WithFallbackBind(true)) } - return dialer.ListenPacket(ctx, "udp4", listenAddr, options...) + return dialer.ListenPacket(ctx, "udp4", listenAddr, netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 67), options...) } diff --git a/component/dialer/bind.go b/component/dialer/bind.go index 9b6471a3..de6aacd3 100644 --- a/component/dialer/bind.go +++ b/component/dialer/bind.go @@ -75,7 +75,7 @@ func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network str return nil } -func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { +func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) { _, port, err := net.SplitHostPort(address) if err != nil { port = "0" diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index f83b86f8..fdea24bf 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -46,7 +46,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A return nil } -func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) { ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { return "", err diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go index 1ec98f3d..79cf735b 100644 --- a/component/dialer/bind_linux.go +++ b/component/dialer/bind_linux.go @@ -35,7 +35,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A return nil } -func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) { addControlToListenConfig(lc, bindControl(ifaceName)) return address, nil diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index 44181610..b7db962a 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -11,8 +11,8 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination) } -func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) { - return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address) +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) { + return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address, rAddrPort) } func ParseNetwork(network string, addr netip.Addr) string { diff --git a/component/dialer/bind_windows.go b/component/dialer/bind_windows.go index 120f1657..98a076d0 100644 --- a/component/dialer/bind_windows.go +++ b/component/dialer/bind_windows.go @@ -36,7 +36,7 @@ func bind6(handle syscall.Handle, ifaceIdx int) error { return err } -func bindControl(ifaceIdx int) controlFn { +func bindControl(ifaceIdx int, rAddrPort netip.AddrPort) controlFn { return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { addrPort, err := netip.ParseAddrPort(address) if err == nil && !addrPort.Addr().IsGlobalUnicast() { @@ -55,7 +55,7 @@ func bindControl(ifaceIdx int) controlFn { innerErr = bind4err case "udp6": // golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "") - if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil { + if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil && rAddrPort.Addr().Unmap().Is4() { // try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6 if bind4err != nil { innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err) @@ -76,23 +76,23 @@ func bindControl(ifaceIdx int) controlFn { } } -func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, destination netip.Addr) error { ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { return err } - addControlToDialer(dialer, bindControl(ifaceObj.Index)) + addControlToDialer(dialer, bindControl(ifaceObj.Index, netip.AddrPortFrom(destination, 0))) return nil } -func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) { ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { return "", err } - addControlToListenConfig(lc, bindControl(ifaceObj.Index)) + addControlToListenConfig(lc, bindControl(ifaceObj.Index, rAddrPort)) return address, nil } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 12e7c960..8fb5a0a6 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -78,7 +78,7 @@ func DialContext(ctx context.Context, network, address string, options ...Option } } -func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { +func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) { if features.CMFA && DefaultSocketHook != nil { return listenPacketHooked(ctx, network, address) } @@ -91,7 +91,7 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio if cfg.fallbackBind { bind = fallbackBindIfaceToListenConfig } - addr, err := bind(cfg.interfaceName, lc, network, address) + addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort) if err != nil { return nil, err } @@ -133,11 +133,9 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po var address string if IP4PEnable { - NewDestination, NewPort := lookupIP4P(destination.String(), port) - address = net.JoinHostPort(NewDestination, NewPort) - } else { - address = net.JoinHostPort(destination.String(), port) + destination, port = lookupIP4P(destination, port) } + address = net.JoinHostPort(destination.String(), port) netDialer := opt.netDialer switch netDialer.(type) { @@ -385,7 +383,7 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr // avoid "The requested address is not valid in its context." opt = WithInterface("") } - return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt) + return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, opt) } func NewDialer(options ...Option) Dialer { @@ -399,13 +397,13 @@ func GetIP4PEnable(enableIP4PConvert bool) { // kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go -func lookupIP4P(addr string, port string) (string, string) { - ip := net.ParseIP(addr) +func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) { + ip := addr.AsSlice() if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && ip[3] == 0x00 { - addr = net.IPv4(ip[12], ip[13], ip[14], ip[15]).String() + addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]}) port = strconv.Itoa(int(ip[10])<<8 + int(ip[11])) - log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr, port)) + log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port)) return addr, port } return addr, port diff --git a/component/http/http.go b/component/http/http.go index 455db681..21d65d2e 100644 --- a/component/http/http.go +++ b/component/http/http.go @@ -17,7 +17,10 @@ import ( ) func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { - UA := C.UA + return HttpRequestWithProxy(ctx, url, method, header, body, "") +} + +func HttpRequestWithProxy(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) { method = strings.ToUpper(method) urlRes, err := URL.Parse(url) if err != nil { @@ -32,7 +35,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st } if _, ok := header["User-Agent"]; !ok { - req.Header.Set("User-Agent", UA) + req.Header.Set("User-Agent", C.UA) } if err != nil { @@ -54,7 +57,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - if conn, err := inner.HandleTcp(address); err == nil { + if conn, err := inner.HandleTcp(address, specialProxy); err == nil { return conn, nil } else { d := net.Dialer{} @@ -66,5 +69,4 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st client := http.Client{Transport: transport} return client.Do(req) - } diff --git a/component/iface/iface.go b/component/iface/iface.go index 1d0219df..d543725a 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -23,7 +23,7 @@ var ( var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20) -func ResolveInterface(name string) (*Interface, error) { +func Interfaces() (map[string]*Interface, error) { value, err, _ := interfaces.Do(func() (map[string]*Interface, error) { ifaces, err := net.Interfaces() if err != nil { @@ -69,11 +69,15 @@ func ResolveInterface(name string) (*Interface, error) { return r, nil }) + return value, err +} + +func ResolveInterface(name string) (*Interface, error) { + ifaces, err := Interfaces() if err != nil { return nil, err } - ifaces := value iface, ok := ifaces[name] if !ok { return nil, ErrIfaceNotFound @@ -82,6 +86,21 @@ func ResolveInterface(name string) (*Interface, error) { return iface, nil } +func IsLocalIp(ip netip.Addr) (bool, error) { + ifaces, err := Interfaces() + if err != nil { + return false, err + } + for _, iface := range ifaces { + for _, addr := range iface.Addrs { + if addr.Contains(ip) { + return true, nil + } + } + } + return false, nil +} + func FlushCache() { interfaces.Reset() } diff --git a/component/loopback/detector.go b/component/loopback/detector.go new file mode 100644 index 00000000..8ec96a9d --- /dev/null +++ b/component/loopback/detector.go @@ -0,0 +1,89 @@ +package loopback + +import ( + "errors" + "fmt" + "net/netip" + + "github.com/metacubex/mihomo/common/callback" + "github.com/metacubex/mihomo/component/iface" + C "github.com/metacubex/mihomo/constant" + + "github.com/puzpuzpuz/xsync/v3" +) + +var ErrReject = errors.New("reject loopback connection") + +type Detector struct { + connMap *xsync.MapOf[netip.AddrPort, struct{}] + packetConnMap *xsync.MapOf[uint16, struct{}] +} + +func NewDetector() *Detector { + return &Detector{ + connMap: xsync.NewMapOf[netip.AddrPort, struct{}](), + packetConnMap: xsync.NewMapOf[uint16, struct{}](), + } +} + +func (l *Detector) NewConn(conn C.Conn) C.Conn { + metadata := C.Metadata{} + if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { + return conn + } + connAddr := metadata.AddrPort() + if !connAddr.IsValid() { + return conn + } + l.connMap.Store(connAddr, struct{}{}) + return callback.NewCloseCallbackConn(conn, func() { + l.connMap.Delete(connAddr) + }) +} + +func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn { + metadata := C.Metadata{} + if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { + return conn + } + connAddr := metadata.AddrPort() + if !connAddr.IsValid() { + return conn + } + port := connAddr.Port() + l.packetConnMap.Store(port, struct{}{}) + return callback.NewCloseCallbackPacketConn(conn, func() { + l.packetConnMap.Delete(port) + }) +} + +func (l *Detector) CheckConn(metadata *C.Metadata) error { + connAddr := metadata.SourceAddrPort() + if !connAddr.IsValid() { + return nil + } + if _, ok := l.connMap.Load(connAddr); ok { + return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress()) + } + return nil +} + +func (l *Detector) CheckPacketConn(metadata *C.Metadata) error { + connAddr := metadata.SourceAddrPort() + if !connAddr.IsValid() { + return nil + } + + isLocalIp, err := iface.IsLocalIp(connAddr.Addr()) + if err != nil { + return err + } + if !isLocalIp && !connAddr.Addr().IsLoopback() { + return nil + } + + if _, ok := l.packetConnMap.Load(connAddr.Port()); ok { + return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress()) + } + return nil +} diff --git a/component/resolver/relay.go b/component/resolver/relay.go index 27b25af1..818b4152 100644 --- a/component/resolver/relay.go +++ b/component/resolver/relay.go @@ -46,7 +46,7 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration) ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) defer cancel() inData := buff[:n] - msg, err := RelayDnsPacket(ctx, inData, buff) + msg, err := relayDnsPacket(ctx, inData, buff, 0) if err != nil { return err } @@ -69,7 +69,7 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration) return nil } -func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) { +func relayDnsPacket(ctx context.Context, payload []byte, target []byte, maxSize int) ([]byte, error) { msg := &D.Msg{} if err := msg.Unpack(payload); err != nil { return nil, err @@ -83,6 +83,14 @@ func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, } r.SetRcode(msg, r.Rcode) + if maxSize > 0 { + r.Truncate(maxSize) + } r.Compress = true return r.PackBuffer(target) } + +// RelayDnsPacket will truncate udp message up to SafeDnsPacketSize +func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) { + return relayDnsPacket(ctx, payload, target, SafeDnsPacketSize) +} diff --git a/component/resolver/system.go b/component/resolver/system.go new file mode 100644 index 00000000..a134bb05 --- /dev/null +++ b/component/resolver/system.go @@ -0,0 +1,39 @@ +package resolver + +import "sync" + +var blacklist struct { + Map map[string]struct{} + Mutex sync.Mutex +} + +func init() { + blacklist.Map = make(map[string]struct{}) +} + +func AddSystemDnsBlacklist(names ...string) { + blacklist.Mutex.Lock() + defer blacklist.Mutex.Unlock() + for _, name := range names { + blacklist.Map[name] = struct{}{} + } +} + +func RemoveSystemDnsBlacklist(names ...string) { + blacklist.Mutex.Lock() + defer blacklist.Mutex.Unlock() + for _, name := range names { + delete(blacklist.Map, name) + } +} + +func IsSystemDnsBlacklisted(names ...string) bool { + blacklist.Mutex.Lock() + defer blacklist.Mutex.Unlock() + for _, name := range names { + if _, ok := blacklist.Map[name]; ok { + return true + } + } + return false +} diff --git a/component/resource/vehicle.go b/component/resource/vehicle.go index b2e29418..b13369d2 100644 --- a/component/resource/vehicle.go +++ b/component/resource/vehicle.go @@ -28,13 +28,19 @@ func (f *FileVehicle) Read() ([]byte, error) { return os.ReadFile(f.path) } +func (f *FileVehicle) Proxy() string { + return "" +} + func NewFileVehicle(path string) *FileVehicle { return &FileVehicle{path: path} } type HTTPVehicle struct { - url string - path string + url string + path string + proxy string + header http.Header } func (h *HTTPVehicle) Url() string { @@ -49,10 +55,14 @@ func (h *HTTPVehicle) Path() string { return h.path } +func (h *HTTPVehicle) Proxy() string { + return h.proxy +} + func (h *HTTPVehicle) Read() ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil) + resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, h.header, nil, h.proxy) if err != nil { return nil, err } @@ -67,6 +77,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) { return buf, nil } -func NewHTTPVehicle(url string, path string) *HTTPVehicle { - return &HTTPVehicle{url, path} +func NewHTTPVehicle(url string, path string, proxy string, header http.Header) *HTTPVehicle { + return &HTTPVehicle{url, path, proxy, header} } diff --git a/config/config.go b/config/config.go index c7931573..f425092a 100644 --- a/config/config.go +++ b/config/config.go @@ -91,10 +91,11 @@ type Inbound struct { // Controller config type Controller struct { - ExternalController string `json:"-"` - ExternalControllerTLS string `json:"-"` - ExternalUI string `json:"-"` - Secret string `json:"-"` + ExternalController string `json:"-"` + ExternalControllerTLS string `json:"-"` + ExternalControllerUnix string `json:"-"` + ExternalUI string `json:"-"` + Secret string `json:"-"` } // NTP config @@ -304,6 +305,7 @@ type RawConfig struct { LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` IPv6 bool `yaml:"ipv6" json:"ipv6"` ExternalController string `yaml:"external-controller"` + ExternalControllerUnix string `yaml:"external-controller-unix"` ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalUI string `yaml:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` @@ -413,7 +415,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { ProxyGroup: []map[string]any{}, TCPConcurrent: false, FindProcessMode: P.FindProcessStrict, - GlobalUA: "clash.meta", + GlobalUA: "clash.meta/" + C.Version, Tun: RawTun{ Enable: false, Device: "", @@ -678,10 +680,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) { InboundMPTCP: cfg.InboundMPTCP, }, Controller: Controller{ - ExternalController: cfg.ExternalController, - ExternalUI: cfg.ExternalUI, - Secret: cfg.Secret, - ExternalControllerTLS: cfg.ExternalControllerTLS, + ExternalController: cfg.ExternalController, + ExternalUI: cfg.ExternalUI, + Secret: cfg.Secret, + ExternalControllerUnix: cfg.ExternalControllerUnix, + ExternalControllerTLS: cfg.ExternalControllerTLS, }, UnifiedDelay: cfg.UnifiedDelay, Mode: cfg.Mode, diff --git a/constant/provider/interface.go b/constant/provider/interface.go index f2b6939e..bb73d1bc 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -31,6 +31,7 @@ func (v VehicleType) String() string { type Vehicle interface { Read() ([]byte, error) Path() string + Proxy() string Type() VehicleType } diff --git a/constant/rule.go b/constant/rule.go index fcefaba6..8fdd75a6 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -8,8 +8,10 @@ const ( DomainRegex GEOSITE GEOIP - IPCIDR + SrcGEOIP IPASN + SrcIPASN + IPCIDR SrcIPCIDR IPSuffix SrcIPSuffix @@ -48,10 +50,14 @@ func (rt RuleType) String() string { return "GeoSite" case GEOIP: return "GeoIP" - case IPCIDR: - return "IPCIDR" + case SrcGEOIP: + return "SrcGeoIP" case IPASN: return "IPASN" + case SrcIPASN: + return "SrcIPASN" + case IPCIDR: + return "IPCIDR" case SrcIPCIDR: return "SrcIPCIDR" case IPSuffix: diff --git a/dns/system.go b/dns/system.go index 37607a60..dc1006fa 100644 --- a/dns/system.go +++ b/dns/system.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/log" D "github.com/miekg/dns" @@ -39,6 +40,9 @@ func (c *systemClient) getDnsClients() ([]dnsClient, error) { if nameservers, err = dnsReadConfig(); err == nil { log.Debugln("[DNS] system dns update to %s", nameservers) for _, addr := range nameservers { + if resolver.IsSystemDnsBlacklisted(addr) { + continue + } if _, ok := c.dnsClients[addr]; !ok { clients := transform( []NameServer{{ diff --git a/docs/config.yaml b/docs/config.yaml index 4d0e995e..987491e3 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -8,15 +8,15 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口 allow-lan: true # 允许局域网连接 bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址 -authentication: # http,socks入口的验证用户名,密码 +authentication: # http,socks 入口的验证用户名,密码 - "username:password" -skip-auth-prefixes: # 设置跳过验证的IP段 +skip-auth-prefixes: # 设置跳过验证的 IP 段 - 127.0.0.1/8 - ::1/128 -lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0 +lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为 0.0.0.0/0 和::/0 - 0.0.0.0/0 - ::/0 -lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空 +lan-disallowed-ips: # 禁止连接的 IP 地址段,黑名单优先级高于白名单,默认值为空 - 192.168.0.3/32 # find-process-mode has 3 values:always, strict, off @@ -58,6 +58,11 @@ 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 Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 ) +# !!!注意: 从Unix socket访问api接口不会验证secret, 如果开启请自行保证安全问题 !!! +# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ +external-controller-unix: mihomo.sock + # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 @@ -109,9 +114,9 @@ tun: # auto-detect-interface: true # 自动识别出口网卡 # auto-route: true # 配置路由表 # mtu: 9000 # 最大传输单元 - # gso: false # 启用通用分段卸载, 仅支持 Linux + # gso: false # 启用通用分段卸载,仅支持 Linux # gso-max-size: 65536 # 通用分段卸载包的最大大小 - # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 + # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - 0.0.0.0/1 - 128.0.0.0/1 @@ -119,9 +124,9 @@ tun: - "::/1" - "8000::/1" # endpoint-independent-nat: false # 启用独立于端点的 NAT - # include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突 + # include-interface: # 限制被路由的接口。默认不限制,与 `exclude-interface` 冲突 # - "lan0" - # exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突 + # exclude-interface: # 排除路由的接口,与 `include-interface` 冲突 # - "lan1" # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # - 0 @@ -143,7 +148,7 @@ tun: # exclude-package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin -#ebpf配置 +#ebpf 配置 ebpf: auto-redir: # redirect 模式,仅支持 TCP - eth0 @@ -200,7 +205,7 @@ tunnels: # one line config target: target.com proxy: proxy -# DNS配置 +# DNS 配置 dns: cache-algorithm: arc enable: false # 关闭将使用系统 DNS @@ -208,7 +213,7 @@ dns: listen: 0.0.0.0:53 # 开启 DNS 服务器监听 # ipv6: false # false 将返回 AAAA 的空结果 # ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms - # 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名 + # 用于解析 nameserver,fallback 以及其他 DNS 服务器配置的,DNS 服务域名 # 只能使用纯 IP 地址,可使用加密 DNS default-nameserver: - 114.114.114.114 @@ -222,12 +227,12 @@ dns: # use-hosts: true # 查询 hosts - # 配置不使用fake-ip的域名 + # 配置不使用 fake-ip 的域名 # fake-ip-filter: # - '*.lan' # - localhost.ptlogin2.qq.com - # DNS主要域名配置 + # DNS 主要域名配置 # 支持 UDP,TCP,DoT,DoH,DoQ # 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS nameserver: @@ -239,7 +244,7 @@ dns: - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3 - dhcp://en0 # dns from dhcp - quic://dns.adguard.com:784 # DNS over QUIC - # - '8.8.8.8#en0' # 兼容指定DNS出口网卡 + # - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡 # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置 # 当不是 CN,则使用 fallback 中的 DNS 查询结果 @@ -249,7 +254,6 @@ dns: # - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡 # 专用于节点域名解析的 DNS 服务器,非必要配置项 - # 配置服务器若查询失败将使用 nameserver,非并发查询 # proxy-server-nameserver: # - https://dns.google/dns-query # - tls://one.one.one.one @@ -338,7 +342,7 @@ proxies: # socks5 # udp-over-tcp: false # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 - # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, + # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, # UDP 则为双栈解析,获取结果中的第一个 IPv4 # ipv6-prefer 同 ipv4-prefer # 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效 @@ -350,7 +354,7 @@ proxies: # socks5 # max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams. # padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later. # statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接 - # only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议 + # only-tcp: false # 如果设置为 true, smux 的设置将不会对 udp 生效,udp 连接会直接走底层协议 - name: "ss2" type: ss @@ -383,6 +387,7 @@ proxies: # socks5 # headers: # custom: value # v2ray-http-upgrade: false + # v2ray-http-upgrade-fast-open: false - name: "ss4-shadow-tls" type: ss @@ -405,18 +410,18 @@ proxies: # socks5 password: [YOUR_SS_PASSWORD] client-fingerprint: chrome # One of: chrome, ios, firefox or safari - # 可以是chrome, ios, firefox, safari中的一个 + # 可以是 chrome, ios, firefox, safari 中的一个 plugin: restls plugin-opts: host: "www.microsoft.com" # Must be a TLS 1.3 server - # 应当是一个TLS 1.3 服务器 + # 应当是一个 TLS 1.3 服务器 password: [YOUR_RESTLS_PASSWORD] version-hint: "tls13" # Control your post-handshake traffic through restls-script # Hide proxy behaviors like "tls in tls". # see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md - # 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征 + # 用 restls 剧本来控制握手后的行为,隐藏"tls in tls"等特征 # 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100" @@ -428,18 +433,18 @@ proxies: # socks5 password: [YOUR_SS_PASSWORD] client-fingerprint: chrome # One of: chrome, ios, firefox or safari - # 可以是chrome, ios, firefox, safari中的一个 + # 可以是 chrome, ios, firefox, safari 中的一个 plugin: restls plugin-opts: host: "vscode.dev" # Must be a TLS 1.2 server - # 应当是一个TLS 1.2 服务器 + # 应当是一个 TLS 1.2 服务器 password: [YOUR_RESTLS_PASSWORD] version-hint: "tls12" restls-script: "1000?100<1,500~100,350~100,600~100,400~200" # vmess - # cipher支持 auto/aes-128-gcm/chacha20-poly1305/none + # cipher 支持 auto/aes-128-gcm/chacha20-poly1305/none - name: "vmess" type: vmess server: server @@ -461,6 +466,7 @@ proxies: # socks5 # max-early-data: 2048 # early-data-header-name: Sec-WebSocket-Protocol # v2ray-http-upgrade: false + # v2ray-http-upgrade-fast-open: false - name: "vmess-h2" type: vmess @@ -589,6 +595,7 @@ proxies: # socks5 headers: Host: example.com # v2ray-http-upgrade: false + # v2ray-http-upgrade-fast-open: false # Trojan - name: "trojan" @@ -633,6 +640,7 @@ proxies: # socks5 # headers: # Host: example.com # v2ray-http-upgrade: false + # v2ray-http-upgrade-fast-open: false - name: "trojan-xtls" type: trojan @@ -676,11 +684,11 @@ proxies: # socks5 port: 443 # ports: 1000,2000-3000,5000 # port 不可省略 # hop-interval: 15 - # up和down均不写或为0则使用BBR流控 + # up 和 down 均不写或为 0 则使用 BBR 流控 # up: "30 Mbps" # 若不写单位,默认为 Mbps # down: "200 Mbps" # 若不写单位,默认为 Mbps password: yourpassword - # obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander + # obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander # obfs-password: yourpassword # sni: server.com # skip-cert-verify: false @@ -706,9 +714,9 @@ proxies: # socks5 # reserved: [209,98,59] # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接 # dialer-proxy: "ss1" - # remote-dns-resolve: true # 强制dns远程解析,默认值为false - # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 - # 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 + # remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false + # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve 为 true 时生效 + # 如果 peers 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1 # port: 2480 @@ -722,9 +730,9 @@ proxies: # socks5 server: www.example.com port: 10443 type: tuic - # tuicV4必须填写token (不可同时填写uuid和password) + # tuicV4 必须填写 token(不可同时填写 uuid 和 password) token: TOKEN - # tuicV5必须填写uuid和password(不可同时填写token) + # tuicV5 必须填写 uuid 和 password(不可同时填写 token) uuid: 00000000-0000-0000-0000-000000000001 password: PASSWORD_1 # ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server' @@ -742,8 +750,8 @@ proxies: # socks5 # max-open-streams: 20 # default 100, too many open streams may hurt performance # sni: example.com # - # meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效 - # 警告,与原版tuic不兼容!!! + # meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效 + # 警告,与原版 tuic 不兼容!!! # udp-over-stream: false # udp-over-stream-version: 1 @@ -776,12 +784,12 @@ proxies: # socks5 password: password privateKey: path -# dns出站会将请求劫持到内部dns模块,所有请求均在内部处理 +# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理 - name: "dns-out" type: dns proxy-groups: - # 代理链,目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic - # wireguard目前不支持在relay中使用,请使用proxy中的dialer-proxy配置项 + # 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic + # wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项 # Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet - name: "relay" type: relay @@ -855,10 +863,19 @@ proxy-groups: # Mihomo 格式的节点或支持 *ray 的分享格式 proxy-providers: provider1: - type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5 + type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5 url: "url" interval: 3600 path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 + proxy: DIRECT + header: + User-Agent: + - "Clash/v1.18.0" + - "mihomo/1.18.3" + # Accept: + # - 'application/vnd.github.v3.raw' + # Authorization: + # - 'token 1231231' health-check: enable: true interval: 600 @@ -888,8 +905,9 @@ rule-providers: behavior: classical # domain ipcidr interval: 259200 path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 - type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5 + type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5 url: "url" + proxy: DIRECT rule2: behavior: classical interval: 259200 @@ -938,7 +956,7 @@ listeners: port: 10808 #listen: 0.0.0.0 # 默认监听 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理 + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 # udp: false # 默认 true - name: http-in-1 @@ -946,14 +964,14 @@ listeners: port: 10809 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) - name: mixed-in-1 type: mixed # HTTP(S) 和 SOCKS 代理混合 port: 10810 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) # udp: false # 默认 true - name: reidr-in-1 @@ -961,14 +979,14 @@ listeners: port: 10811 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) - name: tproxy-in-1 type: tproxy port: 10812 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) # udp: false # 默认 true - name: shadowsocks-in-1 @@ -976,7 +994,7 @@ listeners: port: 10813 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg= cipher: 2022-blake3-aes-256-gcm @@ -985,13 +1003,13 @@ listeners: port: 10814 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) users: - username: 1 uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 alterId: 1 - # ws-path: "/" # 如果不为空则开启websocket传输层 - # 下面两项如果填写则开启tls(需要同时填写) + # ws-path: "/" # 如果不为空则开启 websocket 传输层 + # 下面两项如果填写则开启 tls(需要同时填写) # certificate: ./server.crt # private-key: ./server.key @@ -1000,10 +1018,10 @@ listeners: port: 10815 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - # token: # tuicV4填写(可以同时填写users) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) + # token: # tuicV4 填写(可以同时填写 users) # - TOKEN - # users: # tuicV5填写(可以同时填写token) + # users: # tuicV5 填写(可以同时填写 token) # 00000000-0000-0000-0000-000000000000: PASSWORD_0 # 00000000-0000-0000-0000-000000000001: PASSWORD_1 # certificate: ./server.crt @@ -1020,25 +1038,25 @@ listeners: port: 10816 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) network: [tcp, udp] target: target.com - name: tun-in-1 type: tun # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules - # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) stack: system # gvisor / mixed dns-hijack: - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: false # 自动识别出口网卡 # auto-route: false # 配置路由表 # mtu: 9000 # 最大传输单元 - inet4-address: # 必须手动设置ipv4地址段 + inet4-address: # 必须手动设置 ipv4 地址段 - 198.19.0.1/30 - inet6-address: # 必须手动设置ipv6地址段 + inet6-address: # 必须手动设置 ipv6 地址段 - "fdfe:dcba:9877::1/126" - # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 + # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 # - 0.0.0.0/1 # - 128.0.0.0/1 @@ -1066,17 +1084,17 @@ listeners: # exclude-package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin # 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理 -# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) +# shadowsocks,vmess 入口配置(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理) # ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456 # vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345 -# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) +# tuic 服务器入口(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理) # tuic-server: # enable: true # listen: 127.0.0.1:10443 -# token: # tuicV4填写(可以同时填写users) +# token: # tuicV4 填写(可以同时填写 users) # - TOKEN -# users: # tuicV5填写(可以同时填写token) +# users: # tuicV5 填写(可以同时填写 token) # 00000000-0000-0000-0000-000000000000: PASSWORD_0 # 00000000-0000-0000-0000-000000000001: PASSWORD_1 # certificate: ./server.crt diff --git a/go.mod b/go.mod index c70aca97..a8feb726 100644 --- a/go.mod +++ b/go.mod @@ -13,48 +13,48 @@ require ( github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.3.2 - github.com/gofrs/uuid/v5 v5.0.0 - github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 + github.com/gofrs/uuid/v5 v5.1.0 + github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 github.com/klauspost/cpuid/v2 v2.2.7 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/mdlayher/netlink v1.7.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c - github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 + github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98 + github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.0 - github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd + github.com/metacubex/sing-tun v0.2.6 github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 - github.com/miekg/dns v1.1.58 + github.com/miekg/dns v1.1.59 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 github.com/puzpuzpuz/xsync/v3 v3.1.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.3.6 + github.com/sagernet/sing v0.3.8 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/utls v1.5.4 github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e github.com/samber/lo v1.39.0 - github.com/shirou/gopsutil/v3 v3.24.2 + github.com/shirou/gopsutil/v3 v3.24.3 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 github.com/wk8/go-ordered-map/v2 v2.1.8 - github.com/zhangyunhao116/fastrand v0.3.0 + github.com/zhangyunhao116/fastrand v0.4.0 go.uber.org/automaxprocs v1.5.3 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.21.0 - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/net v0.22.0 - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 + golang.org/x/crypto v0.22.0 + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f + golang.org/x/net v0.24.0 + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.19.0 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 - lukechampine.com/blake3 v1.2.1 + lukechampine.com/blake3 v1.2.2 ) require ( @@ -104,10 +104,10 @@ require ( 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.15.0 // indirect + golang.org/x/mod v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.20.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764 diff --git a/go.sum b/go.sum index 7422a062..8cd6d29d 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q= github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= -github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= -github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.1.0 h1:S5rqVKIigghZTCBKPCw0Y+bXkn26K3TB5mvQq2Ix8dk= +github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= @@ -80,6 +80,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs= github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw= +github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg= +github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/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= @@ -104,26 +106,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.42.1-0.20240319071510-a251e5c66a5c h1:AhaPKvVqF3N/jXFmlW51Cf1+KddslKAsZqcdgGhZjr0= -github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ= -github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 h1:e9nBnrJBv3HzZVeSzJN0G2SADjebd2ZLF1F5dmsjUTc= -github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= -github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 h1:5INHs85Gp1JZsdF7fQp1pXUjfJOX2dhwZjuUQWJVSt8= -github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8= +github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98 h1:oMLlJV4a9AylNo8ZLBNUiqZ02Vme6GLLHjuWJz8amSk= +github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ= +github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764 h1:+czGKoynxYA90YaL3NlCAIJHnlqwoUlLWgmOhdm5ZU8= +github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= +github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d h1:RAe0ND8J5SOPGI623oEXfaHKaaUrrzHx+U1DN9Awcco= +github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd h1:NgLb6Lvr8ZxX0inWswVYjal2SUzsJJ54dFQNOluUJuE= -github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= +github.com/metacubex/sing-tun v0.2.6 h1:frc58BqnIClqcC9KcYBfVAn5bgO6WW1ANKvZW3/HYAQ= +github.com/metacubex/sing-tun v0.2.6/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= +github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= @@ -169,8 +171,8 @@ github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2F github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= -github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= +github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= +github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -210,8 +212,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ 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= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= -github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc= +github.com/zhangyunhao116/fastrand v0.4.0 h1:86QB6Y+GGgLZRFRDCjMmAS28QULwspK9sgL5d1Bx3H4= +github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA= 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= @@ -222,21 +224,21 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs 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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -252,18 +254,18 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= @@ -273,5 +275,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= -lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +lukechampine.com/blake3 v1.2.2 h1:wEAbSg0IVU4ih44CVlpMqMZMpzr5hf/6aqodLlevd/w= +lukechampine.com/blake3 v1.2.2/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/hub/hub.go b/hub/hub.go index 323f8749..38779e13 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -21,6 +21,12 @@ func WithExternalController(externalController string) Option { } } +func WithExternalControllerUnix(externalControllerUnix string) Option { + return func(cfg *config.Config) { + cfg.General.ExternalControllerUnix = externalControllerUnix + } +} + func WithSecret(secret string) Option { return func(cfg *config.Config) { cfg.General.Secret = secret @@ -47,6 +53,10 @@ func Parse(options ...Option) error { cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG) } + if cfg.General.ExternalControllerUnix != "" { + go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.LogLevel == log.DEBUG) + } + executor.ApplyConfig(cfg, true) return nil } diff --git a/hub/route/server.go b/hub/route/server.go index 8e7f225f..c28782b9 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -7,8 +7,11 @@ import ( "encoding/json" "net" "net/http" + "os" + "path/filepath" "runtime/debug" "strings" + "syscall" "time" "github.com/metacubex/mihomo/adapter/inbound" @@ -47,15 +50,7 @@ func SetUIPath(path string) { uiPath = C.Path.Resolve(path) } -func Start(addr string, tlsAddr string, secret string, - certificat, privateKey string, isDebug bool) { - if serverAddr != "" { - return - } - - serverAddr = addr - serverSecret = secret - +func router(isDebug bool, withAuth bool) *chi.Mux { r := chi.NewRouter() corsM := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, @@ -77,7 +72,9 @@ func Start(addr string, tlsAddr string, secret string, }()) } r.Group(func(r chi.Router) { - r.Use(authentication) + if withAuth { + r.Use(authentication) + } r.Get("/", hello) r.Get("/logs", getLogs) r.Get("/traffic", traffic) @@ -107,10 +104,21 @@ func Start(addr string, tlsAddr string, secret string, }) }) } + return r +} + +func Start(addr string, tlsAddr string, secret string, + certificate, privateKey string, isDebug bool) { + if serverAddr != "" { + return + } + + serverAddr = addr + serverSecret = secret if len(tlsAddr) > 0 { go func() { - c, err := CN.ParseCert(certificat, privateKey, C.Path) + c, err := CN.ParseCert(certificate, privateKey, C.Path) if err != nil { log.Errorln("External controller tls listen error: %s", err) return @@ -125,7 +133,7 @@ func Start(addr string, tlsAddr string, secret string, serverAddr = l.Addr().String() log.Infoln("RESTful API tls listening at: %s", serverAddr) tlsServe := &http.Server{ - Handler: r, + Handler: router(isDebug, true), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{c}, }, @@ -144,12 +152,45 @@ func Start(addr string, tlsAddr string, secret string, serverAddr = l.Addr().String() log.Infoln("RESTful API listening at: %s", serverAddr) - if err = http.Serve(l, r); err != nil { + if err = http.Serve(l, router(isDebug, true)); err != nil { log.Errorln("External controller serve error: %s", err) } } +func StartUnix(addr string, isDebug bool) { + addr = C.Path.Resolve(addr) + + dir := filepath.Dir(addr) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, 0o755); err != nil { + log.Errorln("External controller unix listen error: %s", err) + return + } + } + + // https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ + // + // Note: As mentioned above in the ‘security’ section, when a socket binds a socket to a valid pathname address, + // a socket file is created within the filesystem. On Linux, the application is expected to unlink + // (see the notes section in the man page for AF_UNIX) before any other socket can be bound to the same address. + // The same applies to Windows unix sockets, except that, DeleteFile (or any other file delete API) + // should be used to delete the socket file prior to calling bind with the same path. + _ = syscall.Unlink(addr) + + l, err := inbound.Listen("unix", addr) + if err != nil { + log.Errorln("External controller unix listen error: %s", err) + return + } + serverAddr = l.Addr().String() + log.Infoln("RESTful API unix listening at: %s", serverAddr) + + if err = http.Serve(l, router(isDebug, false)); 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") != "" { diff --git a/listener/inner/tcp.go b/listener/inner/tcp.go index dac3d721..ee34ada6 100644 --- a/listener/inner/tcp.go +++ b/listener/inner/tcp.go @@ -16,7 +16,7 @@ func New(t C.Tunnel) { tunnel = t } -func HandleTcp(address string) (conn net.Conn, err error) { +func HandleTcp(address string, proxy string) (conn net.Conn, err error) { if tunnel == nil { return nil, errors.New("tcp uninitialized") } @@ -28,6 +28,9 @@ func HandleTcp(address string) (conn net.Conn, err error) { metadata.Type = C.INNER metadata.DNSMode = C.DNSNormal metadata.Process = C.MihomoName + if proxy != "" { + metadata.SpecialProxy = proxy + } if h, port, err := net.SplitHostPort(address); err == nil { if port, err := strconv.ParseUint(port, 10, 16); err == nil { metadata.DstPort = uint16(port) diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index 384ff016..7cd9fc72 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -12,6 +12,7 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/iface" + "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/sing" @@ -39,6 +40,8 @@ type Listener struct { networkUpdateMonitor tun.NetworkUpdateMonitor defaultInterfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager + + dnsServerIp []string } func CalculateInterfaceName(name string) (tunName string) { @@ -147,12 +150,16 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis dnsAdds = append(dnsAdds, addrPort) } + + var dnsServerIp []string for _, a := range options.Inet4Address { addrPort := netip.AddrPortFrom(a.Addr().Next(), 53) + dnsServerIp = append(dnsServerIp, a.Addr().Next().String()) dnsAdds = append(dnsAdds, addrPort) } for _, a := range options.Inet6Address { addrPort := netip.AddrPortFrom(a.Addr().Next(), 53) + dnsServerIp = append(dnsServerIp, a.Addr().Next().String()) dnsAdds = append(dnsAdds, addrPort) } @@ -244,6 +251,10 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis return } + l.dnsServerIp = dnsServerIp + // after tun.New sing-tun has set DNS to TUN interface + resolver.AddSystemDnsBlacklist(dnsServerIp...) + stackOptions := tun.StackOptions{ Context: context.TODO(), Tun: tunIf, @@ -261,15 +272,17 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis } } l.tunIf = tunIf - l.tunStack, err = tun.NewStack(strings.ToLower(options.Stack.String()), stackOptions) + + tunStack, err := tun.NewStack(strings.ToLower(options.Stack.String()), stackOptions) if err != nil { return } - err = l.tunStack.Start() + err = tunStack.Start() if err != nil { return } + l.tunStack = tunStack //l.openAndroidHotspot(tunOptions) @@ -334,6 +347,7 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. func (l *Listener) Close() error { l.closed = true + resolver.RemoveSystemDnsBlacklist(l.dnsServerIp...) return common.Close( l.tunStack, l.tunIf, diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index efb144a9..fa7e7dbe 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -34,6 +34,8 @@ 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) + // 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/main.go b/main.go index 748fa2e3..afe9cfd2 100644 --- a/main.go +++ b/main.go @@ -23,16 +23,17 @@ import ( ) var ( - version bool - testConfig bool - geodataMode bool - homeDir string - configFile string - externalUI string - externalController string - secret string - updateGeoMux sync.Mutex - updatingGeo = false + version bool + testConfig bool + geodataMode bool + homeDir string + configFile string + externalUI string + externalController string + externalControllerUnix string + secret string + updateGeoMux sync.Mutex + updatingGeo = false ) func init() { @@ -40,6 +41,7 @@ func init() { flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file") 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(&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") @@ -102,6 +104,9 @@ func main() { if externalController != "" { options = append(options, hub.WithExternalController(externalController)) } + if externalControllerUnix != "" { + options = append(options, hub.WithExternalControllerUnix(externalControllerUnix)) + } if secret != "" { options = append(options, hub.WithSecret(secret)) } diff --git a/rules/common/domain.go b/rules/common/domain.go index 23f21185..306eb65f 100644 --- a/rules/common/domain.go +++ b/rules/common/domain.go @@ -4,6 +4,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + "golang.org/x/net/idna" ) type Domain struct { @@ -29,9 +30,10 @@ func (d *Domain) Payload() string { } func NewDomain(domain string, adapter string) *Domain { + punycode, _ := idna.ToASCII(strings.ToLower(domain)) return &Domain{ Base: &Base{}, - domain: strings.ToLower(domain), + domain: punycode, adapter: adapter, } } diff --git a/rules/common/domain_keyword.go b/rules/common/domain_keyword.go index ec01293a..9d6f1c15 100644 --- a/rules/common/domain_keyword.go +++ b/rules/common/domain_keyword.go @@ -4,6 +4,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + "golang.org/x/net/idna" ) type DomainKeyword struct { @@ -30,9 +31,10 @@ func (dk *DomainKeyword) Payload() string { } func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { + punycode, _ := idna.ToASCII(strings.ToLower(keyword)) return &DomainKeyword{ Base: &Base{}, - keyword: strings.ToLower(keyword), + keyword: punycode, adapter: adapter, } } diff --git a/rules/common/domain_suffix.go b/rules/common/domain_suffix.go index b7b1794d..c5b87208 100644 --- a/rules/common/domain_suffix.go +++ b/rules/common/domain_suffix.go @@ -4,6 +4,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + "golang.org/x/net/idna" ) type DomainSuffix struct { @@ -30,9 +31,10 @@ func (ds *DomainSuffix) Payload() string { } func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { + punycode, _ := idna.ToASCII(strings.ToLower(suffix)) return &DomainSuffix{ Base: &Base{}, - suffix: strings.ToLower(suffix), + suffix: punycode, adapter: adapter, } } diff --git a/rules/common/geoip.go b/rules/common/geoip.go index 223a7904..b50680a4 100644 --- a/rules/common/geoip.go +++ b/rules/common/geoip.go @@ -17,6 +17,7 @@ type GEOIP struct { country string adapter string noResolveIP bool + isSourceIP bool geoIPMatcher *router.GeoIPMatcher recodeSize int } @@ -24,11 +25,17 @@ type GEOIP struct { var _ C.Rule = (*GEOIP)(nil) func (g *GEOIP) RuleType() C.RuleType { + if g.isSourceIP { + return C.SrcGEOIP + } return C.GEOIP } func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP + if g.isSourceIP { + ip = metadata.SrcIP + } if !ip.IsValid() { return false, "" } @@ -49,6 +56,16 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { } if !C.GeodataMode { + if g.isSourceIP { + codes := mmdb.IPInstance().LookupCode(ip.AsSlice()) + for _, code := range codes { + if g.country == code { + return true, g.adapter + } + } + return false, g.adapter + } + if metadata.DstGeoIP != nil { return false, g.adapter } @@ -62,7 +79,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { } match := g.geoIPMatcher.Match(ip) - if match { + if match && !g.isSourceIP { metadata.DstGeoIP = append(metadata.DstGeoIP, g.country) } return match, g.adapter @@ -92,7 +109,7 @@ func (g *GEOIP) GetRecodeSize() int { return g.recodeSize } -func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) { +func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP, error) { if err := geodata.InitGeoIP(); err != nil { log.Errorln("can't initial GeoIP: %s", err) return nil, err @@ -105,6 +122,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) country: country, adapter: adapter, noResolveIP: noResolveIP, + isSourceIP: isSrc, } return geoip, nil } @@ -120,6 +138,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) country: country, adapter: adapter, noResolveIP: noResolveIP, + isSourceIP: isSrc, geoIPMatcher: geoIPMatcher, recodeSize: size, } diff --git a/rules/common/ipasn.go b/rules/common/ipasn.go index 1fce8af4..df4b6531 100644 --- a/rules/common/ipasn.go +++ b/rules/common/ipasn.go @@ -14,24 +14,32 @@ type ASN struct { asn string adapter string noResolveIP bool + isSourceIP bool } func (a *ASN) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP + if a.isSourceIP { + ip = metadata.SrcIP + } if !ip.IsValid() { return false, "" } result := mmdb.ASNInstance().LookupASN(ip.AsSlice()) - asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10) - metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization + if !a.isSourceIP { + metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization + } match := a.asn == asnNumber return match, a.adapter } func (a *ASN) RuleType() C.RuleType { + if a.isSourceIP { + return C.SrcIPASN + } return C.IPASN } @@ -51,7 +59,7 @@ func (a *ASN) GetASN() string { return a.asn } -func NewIPASN(asn string, adapter string, noResolveIP bool) (*ASN, error) { +func NewIPASN(asn string, adapter string, isSrc, noResolveIP bool) (*ASN, error) { C.ASNEnable = true if err := geodata.InitASN(); err != nil { log.Errorln("can't initial ASN: %s", err) @@ -63,5 +71,6 @@ func NewIPASN(asn string, adapter string, noResolveIP bool) (*ASN, error) { asn: asn, adapter: adapter, noResolveIP: noResolveIP, + isSourceIP: isSrc, }, nil } diff --git a/rules/parser.go b/rules/parser.go index b69cc4fd..032a02e4 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -23,13 +23,17 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string] parsed, parseErr = RC.NewGEOSITE(payload, target) case "GEOIP": noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) + parsed, parseErr = RC.NewGEOIP(payload, target, false, noResolve) + case "SRC-GEOIP": + parsed, parseErr = RC.NewGEOIP(payload, target, true, true) + case "IP-ASN": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPASN(payload, target, false, noResolve) + case "SRC-IP-ASN": + parsed, parseErr = RC.NewIPASN(payload, target, true, true) case "IP-CIDR", "IP-CIDR6": noResolve := RC.HasNoResolve(params) parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) - case "IP-ASN": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPASN(payload, target, noResolve) case "SRC-IP-CIDR": parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) case "IP-SUFFIX": diff --git a/rules/provider/parse.go b/rules/provider/parse.go index a867d570..a20da28d 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -21,6 +21,7 @@ type ruleProviderSchema struct { Behavior string `provider:"behavior"` Path string `provider:"path,omitempty"` URL string `provider:"url,omitempty"` + Proxy string `provider:"proxy,omitempty"` Format string `provider:"format,omitempty"` Interval int `provider:"interval,omitempty"` } @@ -61,17 +62,14 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t path := C.Path.Resolve(schema.Path) vehicle = resource.NewFileVehicle(path) case "http": + path := C.Path.GetPathByHash("rules", schema.URL) if schema.Path != "" { - path := C.Path.Resolve(schema.Path) + path = C.Path.Resolve(schema.Path) if !features.CMFA && !C.Path.IsSafePath(path) { return nil, fmt.Errorf("%w: %s", errSubPath, path) } - vehicle = resource.NewHTTPVehicle(schema.URL, path) - } else { - path := C.Path.GetPathByHash("rules", schema.URL) - vehicle = resource.NewHTTPVehicle(schema.URL, path) } - + vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil) default: return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d5a226e9..608ab2c5 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -12,6 +12,7 @@ import ( "time" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/nat" P "github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/resolver" @@ -694,6 +695,9 @@ func shouldStopRetry(err error) bool { if errors.Is(err, resolver.ErrIPv6Disabled) { return true } + if errors.Is(err, loopback.ErrReject) { + return true + } return false }