mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-07-22 21:00:38 +08:00
206 lines
5.0 KiB
Go
206 lines
5.0 KiB
Go
//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
|
|
}
|
|
}
|
|
}
|