//go:build linux || darwin || (windows && (amd64 || 386)) //nolint:unused package bridge import ( "context" "net/netip" "slices" "sync" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" ) type sysctlState struct { name string value string } type backendBase struct { ctx context.Context logger logger.ContextLogger networkManager adapter.NetworkManager tag string index uint32 bridgeName string tunName string inet4Port netip.Addr inet6Port netip.Addr boundInterface string tunInterface tun.Tun returnAccess sync.Mutex returnPaths []tun.Return egressAccess sync.Mutex forwardingRestore []sysctlState unregister func() session adapter.BridgeSession currentEgress string closeOnce sync.Once closed chan struct{} readDone chan struct{} } func (b *backendBase) init(ctx context.Context, logger logger.ContextLogger, networkManager adapter.NetworkManager, tag string, options option.BridgeOutboundOptions) error { index, err := allocateBridgeIndex() if err != nil { return err } b.ctx = ctx b.logger = logger b.networkManager = networkManager b.tag = tag b.index = index b.bridgeName = options.BridgeName if b.bridgeName == "" { b.bridgeName = "bridge" } b.boundInterface = options.Interface b.inet4Port = addressAt(bridgeInet4Base, index) b.inet6Port = addressAt(bridgeInet6Base, index) return nil } func (b *backendBase) PortAddresses() (netip.Addr, netip.Addr) { return b.inet4Port, b.inet6Port } func (b *backendBase) AttachReturn(returnPath tun.Return) error { b.returnAccess.Lock() defer b.returnAccess.Unlock() if slices.Contains(b.returnPaths, returnPath) { return nil } b.returnPaths = append(b.returnPaths[:len(b.returnPaths):len(b.returnPaths)], returnPath) return nil } func (b *backendBase) DetachReturn(returnPath tun.Return) error { b.returnAccess.Lock() defer b.returnAccess.Unlock() returnPaths := make([]tun.Return, 0, len(b.returnPaths)) for _, existing := range b.returnPaths { if existing != returnPath { returnPaths = append(returnPaths, existing) } } b.returnPaths = returnPaths return nil } func (b *backendBase) registerMonitors(syncFunc func()) { var unregisterFuncs []func() networkMonitor := b.networkManager.NetworkMonitor() if networkMonitor != nil { networkElement := networkMonitor.RegisterCallback(syncFunc) unregisterFuncs = append(unregisterFuncs, func() { networkMonitor.UnregisterCallback(networkElement) }) } else if b.boundInterface != "" { b.logger.Debug("network monitor unavailable, pinned egress will not track interface changes") } if b.boundInterface == "" { interfaceMonitor := b.networkManager.InterfaceMonitor() if interfaceMonitor != nil { interfaceElement := interfaceMonitor.RegisterCallback(func(_ *control.Interface, _ int) { syncFunc() }) unregisterFuncs = append(unregisterFuncs, func() { interfaceMonitor.UnregisterCallback(interfaceElement) }) } } if len(unregisterFuncs) > 0 { b.unregister = func() { for _, unregisterFunc := range unregisterFuncs { unregisterFunc() } } } } func (b *backendBase) syncSessionEgress() { b.egressAccess.Lock() defer b.egressAccess.Unlock() select { case <-b.closed: return default: } egress := b.resolveEgress() if egress == b.currentEgress { return } err := b.session.SetEgress(egress) if err != nil { b.logger.Debug(E.Cause(err, "apply bridge egress ", egress)) return } b.currentEgress = egress if egress == "" { b.logger.Debug("bridge egress unavailable, dropping forwarded traffic") } else { b.logger.Debug("bridge egress ", egress) } } func (b *backendBase) resolveEgress() string { if b.boundInterface != "" { return b.boundInterface } monitor := b.networkManager.InterfaceMonitor() if monitor == nil { return "" } defaultInterface := monitor.DefaultInterface() if defaultInterface == nil { return "" } return defaultInterface.Name } func (b *backendBase) readLoop() { defer close(b.readDone) buffer := make([]byte, tun.PacketOffset+bridgeTunMTU) for { n, err := b.tunInterface.Read(buffer) if err != nil { select { case <-b.closed: default: b.logger.Debug(E.Cause(err, "bridge tun read")) } return } if n <= tun.PacketOffset { continue } packet := buffer[tun.PacketOffset:n] // On checksum-offloading NICs (notably virtio) the kernel leaves the L4 // checksum uncomputed when the forwarding path TXes to a tun; recompute it. fixReturnChecksum(packet) b.deliverReturn(packet) } } func (b *backendBase) deliverReturn(packet []byte) { b.returnAccess.Lock() returnPaths := b.returnPaths b.returnAccess.Unlock() for _, returnPath := range returnPaths { headroom := returnPath.ReturnHeadroom() buffer := make([]byte, headroom+len(packet)) copy(buffer[headroom:], packet) unconsumed := returnPath.ReturnPackets([][]byte{buffer}) if len(unconsumed) == 0 { return } } }