mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-11-10 21:21:02 +08:00
150 lines
4.9 KiB
Go
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)
|
|
}
|
|
}
|