//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) } }