Files
sing-box/protocol/tailscale/system_binding.go
T
2026-11-09 15:17:20 +00:00

150 lines
4.9 KiB
Go

//go:build with_tailscale
package tailscale
import (
"context"
"net"
"net/netip"
"slices"
"sync"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/sagernet/tailscale/net/netmon"
"github.com/sagernet/tailscale/types/nettype"
)
type systemBinding struct {
interfaceFinder control.InterfaceFinder
control func(network, address string, conn syscall.RawConn) error
listenPacket func(ctx context.Context, network, address string) (nettype.PacketConn, error)
defaultRoute func() (netmon.DefaultRouteDetails, error)
}
func newSystemBinding(ctx context.Context, logger logger.ContextLogger) (systemBinding, error) {
network := service.FromContext[adapter.NetworkManager](ctx)
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
var binding systemBinding
if platformInterface != nil && platformInterface.UsePlatformNetworkInterfaces() {
err := network.UpdateInterfaces()
if err != nil {
return systemBinding{}, err
}
}
if interfaceMonitor := network.InterfaceMonitor(); interfaceMonitor != nil {
interfaceFinder := network.InterfaceFinder()
netmon.RegisterInterfaceGetter(func() ([]netmon.Interface, error) {
return common.Map(interfaceFinder.Interfaces(), func(it control.Interface) netmon.Interface {
return netmon.Interface{
Interface: &net.Interface{
Index: it.Index,
MTU: it.MTU,
Name: it.Name,
HardwareAddr: it.HardwareAddr,
Flags: it.Flags,
},
AltAddrs: common.Map(it.Addresses, func(it netip.Prefix) net.Addr {
return &net.IPNet{
IP: it.Addr().AsSlice(),
Mask: net.CIDRMask(it.Bits(), it.Addr().BitLen()),
}
}),
}
}), nil
})
binding.interfaceFinder = interfaceFinder
binding.defaultRoute = func() (netmon.DefaultRouteDetails, error) {
defaultInterface := interfaceMonitor.DefaultInterface()
if defaultInterface == nil {
return netmon.DefaultRouteDetails{}, nil
}
return netmon.DefaultRouteDetails{
InterfaceName: defaultInterface.Name,
InterfaceIndex: defaultInterface.Index,
}, nil
}
}
if network.AutoRedirectOutputMark() != 0 {
binding.control = network.AutoRedirectOutputMarkFunc()
return binding, nil
}
if platformInterface != nil && platformInterface.UsePlatformNetworkInterfaces() {
if platformInterface.UsePlatformAutoDetectInterfaceControl() {
binding.control = func(_, _ string, conn syscall.RawConn) error {
return control.Raw(conn, func(fileDescriptor uintptr) error {
return platformInterface.AutoDetectInterfaceControl(int(fileDescriptor))
})
}
return binding, nil
}
// 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.
binding.control = func(string, string, syscall.RawConn) error {
return nil
}
return binding, nil
}
bindFunc := network.AutoDetectInterfaceFunc()
if bindFunc == nil {
return binding, nil
}
binding.control = bindFunc
binding.listenPacket = func(listenContext context.Context, networkName string, address string) (nettype.PacketConn, error) {
listenConfig := net.ListenConfig{
Control: control.Append(bindFunc, control.DisableUDPNetReset()),
}
packetConn, err := listenConfig.ListenPacket(listenContext, networkName, address)
if err != nil {
return nil, err
}
udpConn := packetConn.(*net.UDPConn)
egressPool := tun.NewUDPEgressPool(tun.UDPEgressPoolOptions{
Logger: logger,
Network: networkName,
InterfaceFinder: network.InterfaceFinder(),
InterfaceMonitor: network.InterfaceMonitor(),
IsExempt: func() bool {
return network.AutoRedirectOutputMark() != 0
},
})
if !egressPool.SetEgressPort(udpConn.LocalAddr().(*net.UDPAddr).AddrPort().Port()) {
egressPool.Close()
return udpConn, nil
}
return tun.NewUDPEgressConn(udpConn, egressPool), nil
}
return binding, nil
}
func (b systemBinding) hooks(dialer N.Dialer) netmon.Hooks {
return netmon.Hooks{Dialer: dialer, Control: b.control, ListenPacket: b.listenPacket, DefaultRoute: b.defaultRoute}
}
func (b systemBinding) watch(netMon *netmon.Monitor) (unwatch func()) {
if b.interfaceFinder == nil {
return func() {}
}
var access sync.Mutex
lastInterfaces := b.interfaceFinder.Interfaces()
element := b.interfaceFinder.RegisterInterfaceUpdateCallback(func(interfaces []control.Interface) {
access.Lock()
changed := !slices.EqualFunc(lastInterfaces, interfaces, control.Interface.Equals)
lastInterfaces = interfaces
access.Unlock()
if changed {
netMon.Poll()
}
})
return func() {
b.interfaceFinder.UnregisterInterfaceUpdateCallback(element)
}
}