Handle TUN loopback in direct outbound

This commit is contained in:
世界
2026-06-03 10:37:53 +08:00
parent 1086ab2563
commit 761b7f4e12
8 changed files with 77 additions and 19 deletions

View File

@@ -182,10 +182,10 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces() interfaces := networkManager.NetworkInterfaces()
myInterface := networkManager.InterfaceMonitor().MyInterface() myInterfaces := networkManager.InterfaceMonitor().MyInterfaces()
if myInterface != "" { if len(myInterfaces) > 0 {
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return it.Name != myInterface return !common.Contains(myInterfaces, it.Name)
}) })
} }
switch strategy { switch strategy {

View File

@@ -11,6 +11,7 @@ import (
"unsafe" "unsafe"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@@ -77,12 +78,12 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr}) }{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
} }
} }
var myInterface string var myInterfaces []string
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil { if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
myInterface = networkManager.InterfaceMonitor().MyInterface() myInterfaces = networkManager.InterfaceMonitor().MyInterfaces()
} }
for _, address := range dnsAddresses { for _, address := range dnsAddresses {
if address.ifName == myInterface { if common.Contains(myInterfaces, address.ifName) {
continue continue
} }
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53")) conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))

View File

@@ -189,8 +189,8 @@ func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.Defa
func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) { func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) {
} }
func (s *interfaceMonitorStub) MyInterface() string { func (s *interfaceMonitorStub) MyInterfaces() []string {
return "" return nil
} }
func FormatConfig(configContent string) (*StringBox, error) { func FormatConfig(configContent string) (*StringBox, error) {

View File

@@ -15,9 +15,9 @@ var (
type platformDefaultInterfaceMonitor struct { type platformDefaultInterfaceMonitor struct {
*platformInterfaceWrapper *platformInterfaceWrapper
logger logger.Logger logger logger.Logger
callbacks list.List[tun.DefaultInterfaceUpdateCallback] callbacks list.List[tun.DefaultInterfaceUpdateCallback]
myInterface string myInterfaces []string
} }
func (m *platformDefaultInterfaceMonitor) Start() error { func (m *platformDefaultInterfaceMonitor) Start() error {
@@ -106,11 +106,11 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s
func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) { func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) {
m.defaultInterfaceAccess.Lock() m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock() defer m.defaultInterfaceAccess.Unlock()
m.myInterface = interfaceName m.myInterfaces = append(m.myInterfaces, interfaceName)
} }
func (m *platformDefaultInterfaceMonitor) MyInterface() string { func (m *platformDefaultInterfaceMonitor) MyInterfaces() []string {
m.defaultInterfaceAccess.Lock() m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock() defer m.defaultInterfaceAccess.Unlock()
return m.myInterface return m.myInterfaces
} }

2
go.mod
View File

@@ -39,7 +39,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.8.9 github.com/sagernet/sing-tun v0.8.10
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 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/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7

4
go.sum
View File

@@ -248,8 +248,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.9 h1:ixFKKUGdVcJl4wb0xbL36hobiw9l6DIH497EQf5ILpM= github.com/sagernet/sing-tun v0.8.10 h1:1Kc1hr8/2YcnQrW00fWxt6LTnj1xYQO96ZcrhV7V3As=
github.com/sagernet/sing-tun v0.8.9/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ= github.com/sagernet/sing-tun v0.8.10/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
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 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= 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 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=

View File

@@ -20,6 +20,7 @@ import (
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
func RegisterOutbound(registry *outbound.Registry) { func RegisterOutbound(registry *outbound.Registry) {
@@ -37,10 +38,12 @@ type Outbound struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
network adapter.NetworkManager
dialer dialer.ParallelInterfaceDialer dialer dialer.ParallelInterfaceDialer
domainStrategy C.DomainStrategy domainStrategy C.DomainStrategy
fallbackDelay time.Duration fallbackDelay time.Duration
isEmpty bool isEmpty bool
myAddresses common.TypedValue[[]netip.Prefix]
} }
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) {
@@ -61,6 +64,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
network: service.FromContext[adapter.NetworkManager](ctx),
//nolint:staticcheck //nolint:staticcheck
domainStrategy: C.DomainStrategy(options.DomainStrategy), domainStrategy: C.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
@@ -74,7 +78,48 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return outbound, nil return outbound, nil
} }
func (h *Outbound) Start(stage adapter.StartStage) error {
switch stage {
case adapter.StartStatePostStart, adapter.StartStateStarted:
h.fetchMyAddresses()
}
return nil
}
func (h *Outbound) fetchMyAddresses() {
if len(h.myAddresses.Load()) > 0 {
return
}
myInterfaceNames := h.network.InterfaceMonitor().MyInterfaces()
if len(myInterfaceNames) == 0 {
return
}
var myAddresses []netip.Prefix
for _, myInterfaceName := range myInterfaceNames {
myInterface, err := h.network.InterfaceFinder().ByName(myInterfaceName)
if err != nil {
continue
}
myAddresses = append(myAddresses, myInterface.Addresses...)
}
h.myAddresses.Store(myAddresses)
}
func (h *Outbound) isMyLoopbackAddress(addresses ...netip.Addr) bool {
for _, prefix := range h.myAddresses.Load() {
for _, address := range addresses {
if prefix.Addr() != address && prefix.Contains(address) {
return true
}
}
}
return false
}
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if h.isMyLoopbackAddress(destination.Addr) {
return nil, E.New("loopback connection to TUN range")
}
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
@@ -89,6 +134,9 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
} }
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if h.isMyLoopbackAddress(destination.Addr) {
return nil, E.New("loopback connection to TUN range")
}
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
@@ -111,6 +159,9 @@ func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, rou
} }
func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
if h.isMyLoopbackAddress(destinationAddresses...) {
return nil, E.New("loopback connection to TUN range")
}
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
@@ -125,6 +176,9 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
} }
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if h.isMyLoopbackAddress(destinationAddresses...) {
return nil, E.New("loopback connection to TUN range")
}
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
@@ -139,6 +193,9 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
} }
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if h.isMyLoopbackAddress(destinationAddresses...) {
return nil, netip.Addr{}, E.New("loopback connection to TUN range")
}
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination

View File

@@ -40,11 +40,11 @@ func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, inter
func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
interfaces := r.networkManager.NetworkInterfaces() interfaces := r.networkManager.NetworkInterfaces()
myInterface := r.networkManager.InterfaceMonitor().MyInterface() myInterfaces := r.networkManager.InterfaceMonitor().MyInterfaces()
match: match:
for ifType, addresses := range r.interfaceAddresses { for ifType, addresses := range r.interfaceAddresses {
for _, networkInterface := range interfaces { for _, networkInterface := range interfaces {
if networkInterface.Name == myInterface { if common.Contains(myInterfaces, networkInterface.Name) {
continue continue
} }
if networkInterface.Type != ifType { if networkInterface.Type != ifType {