diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index e91abc28..8417f6a0 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -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) { interfaces := networkManager.NetworkInterfaces() - myInterface := networkManager.InterfaceMonitor().MyInterface() - if myInterface != "" { + myInterfaces := networkManager.InterfaceMonitor().MyInterfaces() + if len(myInterfaces) > 0 { interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { - return it.Name != myInterface + return !common.Contains(myInterfaces, it.Name) }) } switch strategy { diff --git a/dns/transport/local/resolv_windows.go b/dns/transport/local/resolv_windows.go index 04b8d4ef..c22e394a 100644 --- a/dns/transport/local/resolv_windows.go +++ b/dns/transport/local/resolv_windows.go @@ -11,6 +11,7 @@ import ( "unsafe" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/service" "golang.org/x/sys/windows" @@ -77,12 +78,12 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig { }{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr}) } } - var myInterface string + var myInterfaces []string if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil { - myInterface = networkManager.InterfaceMonitor().MyInterface() + myInterfaces = networkManager.InterfaceMonitor().MyInterfaces() } for _, address := range dnsAddresses { - if address.ifName == myInterface { + if common.Contains(myInterfaces, address.ifName) { continue } conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53")) diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index b1676ab6..3071e47b 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -189,8 +189,8 @@ func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.Defa func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) { } -func (s *interfaceMonitorStub) MyInterface() string { - return "" +func (s *interfaceMonitorStub) MyInterfaces() []string { + return nil } func FormatConfig(configContent string) (*StringBox, error) { diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 62f91613..45b11c46 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -15,9 +15,9 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - logger logger.Logger - callbacks list.List[tun.DefaultInterfaceUpdateCallback] - myInterface string + logger logger.Logger + callbacks list.List[tun.DefaultInterfaceUpdateCallback] + myInterfaces []string } func (m *platformDefaultInterfaceMonitor) Start() error { @@ -106,11 +106,11 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) { m.defaultInterfaceAccess.Lock() 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() defer m.defaultInterfaceAccess.Unlock() - return m.myInterface + return m.myInterfaces } diff --git a/go.mod b/go.mod index 9d927ee0..0c1ceda6 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 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/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 diff --git a/go.sum b/go.sum index 4ae93f7b..2de2f391 100644 --- a/go.sum +++ b/go.sum @@ -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-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-tun v0.8.9 h1:ixFKKUGdVcJl4wb0xbL36hobiw9l6DIH497EQf5ILpM= -github.com/sagernet/sing-tun v0.8.9/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ= +github.com/sagernet/sing-tun v0.8.10 h1:1Kc1hr8/2YcnQrW00fWxt6LTnj1xYQO96ZcrhV7V3As= +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/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 630a6755..b4e7eba0 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -20,6 +20,7 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) func RegisterOutbound(registry *outbound.Registry) { @@ -37,10 +38,12 @@ type Outbound struct { outbound.Adapter ctx context.Context logger logger.ContextLogger + network adapter.NetworkManager dialer dialer.ParallelInterfaceDialer domainStrategy C.DomainStrategy fallbackDelay time.Duration 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) { @@ -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), ctx: ctx, logger: logger, + network: service.FromContext[adapter.NetworkManager](ctx), //nolint:staticcheck domainStrategy: C.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), @@ -74,7 +78,48 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL 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) { + if h.isMyLoopbackAddress(destination.Addr) { + return nil, E.New("loopback connection to TUN range") + } ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() 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) { + if h.isMyLoopbackAddress(destination.Addr) { + return nil, E.New("loopback connection to TUN range") + } ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() 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) { + if h.isMyLoopbackAddress(destinationAddresses...) { + return nil, E.New("loopback connection to TUN range") + } ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() 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) { + if h.isMyLoopbackAddress(destinationAddresses...) { + return nil, E.New("loopback connection to TUN range") + } ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() 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) { + if h.isMyLoopbackAddress(destinationAddresses...) { + return nil, netip.Addr{}, E.New("loopback connection to TUN range") + } ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination diff --git a/route/rule/rule_network_interface_address.go b/route/rule/rule_network_interface_address.go index 135a703e..e331d622 100644 --- a/route/rule/rule_network_interface_address.go +++ b/route/rule/rule_network_interface_address.go @@ -40,11 +40,11 @@ func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, inter func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { interfaces := r.networkManager.NetworkInterfaces() - myInterface := r.networkManager.InterfaceMonitor().MyInterface() + myInterfaces := r.networkManager.InterfaceMonitor().MyInterfaces() match: for ifType, addresses := range r.interfaceAddresses { for _, networkInterface := range interfaces { - if networkInterface.Name == myInterface { + if common.Contains(myInterfaces, networkInterface.Name) { continue } if networkInterface.Type != ifType {