From 704b57cf3e83d596e38e7419e4e7acc76317a683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 17 Jul 2026 10:44:40 +0800 Subject: [PATCH] Refactor endpoint listen --- common/dialer/default.go | 4 +- common/dialer/wireguard.go | 2 +- go.mod | 8 ++-- go.sum | 16 +++---- protocol/tailscale/endpoint.go | 60 ++++++++++++++++++++----- protocol/wireguard/endpoint.go | 18 ++++++++ route/network.go | 8 +++- transport/wireguard/endpoint.go | 19 +++++--- transport/wireguard/endpoint_options.go | 1 + 9 files changed, 102 insertions(+), 34 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index cf22ba0b3..a05c42ee3 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -381,8 +381,8 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina return d.trackPacketConn(packetConn, nil) } -func (d *DefaultDialer) WireGuardControl() control.Func { - return d.udpListener.Control +func (d *DefaultDialer) WireGuardControl() (control.Func, bool) { + return d.udpListener.Control, d.autoDetectBindFunc != nil && d.netns == "" } func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go index 8a916a591..2456a0c8d 100644 --- a/common/dialer/wireguard.go +++ b/common/dialer/wireguard.go @@ -5,5 +5,5 @@ import ( ) type WireGuardListener interface { - WireGuardControl() control.Func + WireGuardControl() (control.Func, bool) } diff --git a/go.mod b/go.mod index 9314fff5e..9ee476370 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/nftables v0.3.0-mod.4 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 - github.com/sagernet/sing v0.8.12-0.20260716111929-074fc9988b34 + github.com/sagernet/sing v0.8.12-0.20260717023913-84ab32b56cb8 github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 github.com/sagernet/sing-mux v0.3.5 github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc @@ -51,12 +51,12 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1 github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb - github.com/sagernet/sing-tun v0.8.12-0.20260716125804-79ea1ac88855 + github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260711004045-7d3e867147ad - github.com/sagernet/wireguard-go v0.0.5-0.20260706153856-2c27bbf4f97f + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717024045-1edfbb9ee544 + github.com/sagernet/wireguard-go v0.0.5-0.20260717024847-6f5e8b1947ae github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index aaedbba96..aec72e0af 100644 --- a/go.sum +++ b/go.sum @@ -270,8 +270,8 @@ github.com/sagernet/nftables v0.3.0-mod.4 h1:vnOtcDYeSXv2e5RoRuGH0lrpttQFJ8iC4IC github.com/sagernet/nftables v0.3.0-mod.4/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.12-0.20260716111929-074fc9988b34 h1:rgSs2ttiz8EaubsOt0SkzsqciY0m0PRp3w/fOisPoNo= -github.com/sagernet/sing v0.8.12-0.20260716111929-074fc9988b34/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= +github.com/sagernet/sing v0.8.12-0.20260717023913-84ab32b56cb8 h1:dyRIj+MZ2rc9JVzJoG04jxu+MpvHrLIZLJr0QjNAMGg= +github.com/sagernet/sing v0.8.12-0.20260717023913-84ab32b56cb8/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6++yIa8XlDhxPkpR4p+7RUHVY2KTP9CPIGnWmOlO8= github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg= github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI= @@ -286,18 +286,18 @@ github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkG github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb h1:VvU2/PZqP5tbKTDq0BxkhRO8ZnKI4UJzziakgBiP2Qg= github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= -github.com/sagernet/sing-tun v0.8.12-0.20260716125804-79ea1ac88855 h1:zP2Bb6NLl3/zV24mmBjcpWIdvOKTiMo2SS0eJ8WpYFU= -github.com/sagernet/sing-tun v0.8.12-0.20260716125804-79ea1ac88855/go.mod h1:SF7OPVtqIYY14T4KmnoHND5CFiMSUCTYxzVOT3nZUuc= +github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d h1:UXUGxGTwotY+R+QkRGvhj/N148SDnwMh/d9Hefn5X7Y= +github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260711004045-7d3e867147ad h1:WfqPcs43BJINn2IjQd10cLdd3lvJSanbk7MxGk6RTcA= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260711004045-7d3e867147ad/go.mod h1:p8Ms8FbGlwQJyHb862XmdShTS50fFJ8C71VdO6xvWyk= -github.com/sagernet/wireguard-go v0.0.5-0.20260706153856-2c27bbf4f97f h1:TzN97RL07xWb3gZtmqFhsdkud4f6G/pohiaOLiqSBj4= -github.com/sagernet/wireguard-go v0.0.5-0.20260706153856-2c27bbf4f97f/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717024045-1edfbb9ee544 h1:j2tab0dGHutfclhwZxrkSDMXwGXtozIo5BV4DgwS+1Q= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717024045-1edfbb9ee544/go.mod h1:p8Ms8FbGlwQJyHb862XmdShTS50fFJ8C71VdO6xvWyk= +github.com/sagernet/wireguard-go v0.0.5-0.20260717024847-6f5e8b1947ae h1:GmxlXWnRmeNfPE1tWXRZIFgKJd5BH5okoDHKZkkI5bw= +github.com/sagernet/wireguard-go v0.0.5-0.20260717024847-6f5e8b1947ae/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 911b62d0a..91f766034 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -13,7 +13,6 @@ import ( "os" "path/filepath" "reflect" - "runtime" "strings" "sync" "sync/atomic" @@ -339,22 +338,58 @@ func (t *Endpoint) start() error { t.systemDialer = systemDialer t.server.TunDevice = wgTunDevice } - if mark := t.network.AutoRedirectOutputMark(); mark > 0 { - controlFunc := t.network.AutoRedirectOutputMarkFunc() - if bindFunc := t.network.AutoDetectInterfaceFunc(); bindFunc != nil { - controlFunc = control.Append(controlFunc, bindFunc) - } - netns.SetControlFunc(controlFunc) - } else if runtime.GOOS == "android" && t.platformInterface != nil && t.platformInterface.UsePlatformAutoDetectInterfaceControl() { - netns.SetControlFunc(func(network, address string, c syscall.RawConn) error { - return control.Raw(c, func(fd uintptr) error { - return t.platformInterface.AutoDetectInterfaceControl(int(fd)) + if t.network.AutoRedirectOutputMark() != 0 { + netns.SetControlFunc(t.network.AutoRedirectOutputMarkFunc()) + } else if t.platformInterface != nil && t.platformInterface.UsePlatformNetworkInterfaces() { + if t.platformInterface.UsePlatformAutoDetectInterfaceControl() { + netns.SetControlFunc(func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fileDescriptor uintptr) error { + return t.platformInterface.AutoDetectInterfaceControl(int(fileDescriptor)) + }) }) - }) + } else { + // NEPacketTunnelProvider sockets are excluded from tunnel routes by + // NECP; the empty override only suppresses tailscale's own + // default-interface bind, which would select the sing-box utun. + netns.SetControlFunc(func(string, string, syscall.RawConn) error { + return nil + }) + } + } else { + bindFunc := t.network.AutoDetectInterfaceFunc() + if bindFunc != nil { + netns.SetControlFunc(bindFunc) + netns.SetListenPacketFunc(t.listenPacket) + } } return nil } +func (t *Endpoint) listenPacket(ctx context.Context, network string, address string) (nettype.PacketConn, error) { + listenConfig := net.ListenConfig{ + Control: control.Append(t.network.AutoDetectInterfaceFunc(), control.DisableUDPNetReset()), + } + packetConn, err := listenConfig.ListenPacket(ctx, network, address) + if err != nil { + return nil, err + } + udpConn := packetConn.(*net.UDPConn) + egressPool := tun.NewUDPEgressPool(tun.UDPEgressPoolOptions{ + Logger: t.logger, + Network: network, + InterfaceFinder: t.network.InterfaceFinder(), + InterfaceMonitor: t.network.InterfaceMonitor(), + IsExempt: func() bool { + return t.network.AutoRedirectOutputMark() != 0 + }, + }) + if !egressPool.SetEgressPort(udpConn.LocalAddr().(*net.UDPAddr).AddrPort().Port()) { + egressPool.Close() + return udpConn, nil + } + return tun.NewUDPEgressConn(udpConn, egressPool), nil +} + func (t *Endpoint) postStart() error { err := t.server.Start() if err != nil { @@ -649,6 +684,7 @@ func (t *Endpoint) Close() error { } netmon.RegisterInterfaceGetter(nil) netns.SetControlFunc(nil) + netns.SetListenPacketFunc(nil) if t.fallbackTCPCloser != nil { t.fallbackTCPCloser() t.fallbackTCPCloser = nil diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index e126b9428..bae1d36a1 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -74,6 +74,23 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL udpTimeout = C.UDPTimeout } networkManager := service.FromContext[adapter.NetworkManager](ctx) + var egressPool *tun.UDPEgressPool + wireGuardListener, isWireGuardListener := common.Cast[dialer.WireGuardListener](outboundDialer) + if isWireGuardListener { + anchorControl, egressEnabled := wireGuardListener.WireGuardControl() + if egressEnabled { + egressPool = tun.NewUDPEgressPool(tun.UDPEgressPoolOptions{ + Logger: logger, + Control: anchorControl, + InterfaceFinder: networkManager.InterfaceFinder(), + InterfaceMonitor: networkManager.InterfaceMonitor(), + ExcludeInterface: options.Name, + IsExempt: func() bool { + return networkManager.AutoRedirectOutputMark() != 0 + }, + }) + } + } wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ Context: ctx, Logger: logger, @@ -85,6 +102,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL UDPFiltering: tun.NATFiltering(options.UDPFiltering), UDPNATMax: options.UDPNATMax, InterfaceFinder: networkManager.InterfaceFinder(), + EgressPool: egressPool, Dialer: outboundDialer, CreateDialer: func(interfaceName string) N.Dialer { return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ diff --git a/route/network.go b/route/network.go index 4fbcf22e5..9f65ee926 100644 --- a/route/network.go +++ b/route/network.go @@ -353,7 +353,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { if r.interfaceMonitor == nil { return nil } - return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr if remoteAddr.IsValid() { iif, err := r.interfaceFinder.ByAddr(remoteAddr) @@ -367,6 +367,12 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return defaultInterface.Name, defaultInterface.Index, nil }) + return func(network, address string, conn syscall.RawConn) error { + if r.autoRedirectOutputMark != 0 { + return nil + } + return bindFunc(network, address, conn) + } } } diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 734d5f3c0..97e4dae9a 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -152,13 +152,17 @@ func (e *Endpoint) Start(resolve bool) error { return nil } var bind conn.Bind - wgListener, isWgListener := common.Cast[dialer.WireGuardListener](e.options.Dialer) - if isWgListener { - stdBind := conn.NewStdNetBind(wgListener.WireGuardControl()) + wireGuardListener, isWireGuardListener := common.Cast[dialer.WireGuardListener](e.options.Dialer) + if isWireGuardListener { + wireGuardControl, _ := wireGuardListener.WireGuardControl() + standardBind := conn.NewStdNetBind(wireGuardControl).(*conn.StdNetBind) if e.options.ListenPort == 0 && len(e.peers) == 1 && e.peers[0].endpoint.IsValid() { - stdBind.(*conn.StdNetBind).SetSinglePeerMode() + standardBind.SetSinglePeerMode() } - bind = stdBind + if e.options.EgressPool != nil { + standardBind.SetEgressProvider(e.options.EgressPool) + } + bind = standardBind } else { var ( isConnect bool @@ -172,7 +176,7 @@ func (e *Endpoint) Start(resolve bool) error { } bind = NewClientBind(e.options.Context, e.options.Logger, e.options.Dialer, isConnect, connectAddr, reserved) } - if isWgListener || len(e.peers) > 1 { + if isWireGuardListener || len(e.peers) > 1 { for _, peer := range e.peers { if peer.reserved != [3]uint8{} { bind.SetReservedForEndpoint(peer.endpoint, peer.reserved) @@ -231,6 +235,9 @@ func (e *Endpoint) Close() error { e.pause.UnregisterCallback(e.pauseCallback) e.pauseCallback = nil } + if e.options.EgressPool != nil { + e.options.EgressPool.Close() + } if e.device != nil { e.device.Down() e.device.Close() diff --git a/transport/wireguard/endpoint_options.go b/transport/wireguard/endpoint_options.go index 817c18cb2..0a3e7d997 100644 --- a/transport/wireguard/endpoint_options.go +++ b/transport/wireguard/endpoint_options.go @@ -24,6 +24,7 @@ type EndpointOptions struct { UDPNATMax uint32 InterfaceFinder control.InterfaceFinder + EgressPool *tun.UDPEgressPool Dialer N.Dialer CreateDialer func(interfaceName string) N.Dialer Name string