Fix bridge forwarding with auto-redirect

This commit is contained in:
世界 2026-09-24 15:36:18 +08:00
parent 5618bef173
commit 2f10bfc8ea
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
9 changed files with 251 additions and 194 deletions

View file

@ -28,6 +28,8 @@ type NetworkManager interface {
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
AutoRedirectOutputMarkFunc() control.Func
RegisterBridgeInterface(interfaceName string)
BridgeInterfaces() []string
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager

View file

@ -58,9 +58,9 @@ Not effective on Apple platforms.
!!! quote ""
Only supported on Linux, and only takes effect when `interface` is set.
Only supported on Linux.
Linux iproute2 table index for pinned egress routes.
Linux iproute2 table index.
`2200` + instance index is used by default.

View file

@ -53,9 +53,9 @@ icon: material/new-box
!!! quote ""
仅支持 Linux,且仅在设置了 `interface` 时生效。
仅支持 Linux。
用于固定出口路由的 Linux iproute2 路由表索引。
Linux iproute2 路由表索引。
默认使用 `2200` + 实例索引。

View file

@ -36,6 +36,7 @@ type autoRedirectOptions struct {
Inet6RouteExcludeAddress []netip.Prefix `json:"inet6_route_exclude_address,omitempty"`
IncludeInterface []string `json:"include_interface,omitempty"`
ExcludeInterface []string `json:"exclude_interface,omitempty"`
BridgeInterface []string `json:"bridge_interface,omitempty"`
IncludeUID []ranges.Range[uint32] `json:"include_uid,omitempty"`
ExcludeUID []ranges.Range[uint32] `json:"exclude_uid,omitempty"`
IncludeMACAddress []net.HardwareAddr `json:"include_mac_address,omitempty"`
@ -70,6 +71,7 @@ func encodeAutoRedirectOptions(options adapter.AutoRedirectOptions) ([]byte, err
Inet6RouteExcludeAddress: tunOptions.Inet6RouteExcludeAddress,
IncludeInterface: tunOptions.IncludeInterface,
ExcludeInterface: tunOptions.ExcludeInterface,
BridgeInterface: tunOptions.BridgeInterface,
IncludeUID: tunOptions.IncludeUID,
ExcludeUID: tunOptions.ExcludeUID,
IncludeMACAddress: tunOptions.IncludeMACAddress,
@ -113,6 +115,7 @@ func decodeAutoRedirectOptions(content []byte) (*tun.Options, string, uint16, er
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface,
BridgeInterface: options.BridgeInterface,
IncludeUID: options.IncludeUID,
ExcludeUID: options.ExcludeUID,
IncludeMACAddress: options.IncludeMACAddress,

View file

@ -51,9 +51,7 @@ func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager
if instance.ruleIndex == 0 {
instance.ruleIndex = defaultBridgeRuleIndex
}
if instance.boundInterface != "" || instance.platform != nil {
instance.routeTable = options.IPRoute2TableIndex
}
instance.routeTable = options.IPRoute2TableIndex
return instance, nil
}
@ -64,7 +62,7 @@ func (b *backendLinux) Start(stage adapter.StartStage, scope *adapter.Scope) err
if err != nil {
return err
}
if b.routeTable == 0 && (b.boundInterface != "" || b.platform != nil) {
if b.routeTable == 0 {
b.routeTable = defaultBridgeTableIndexBase + int(b.index)
}
case adapter.StartStateStart:
@ -101,6 +99,7 @@ func (b *backendLinux) start(scope *adapter.Scope) error {
if err != nil {
return E.Cause(err, "start bridge tun")
}
b.networkManager.RegisterBridgeInterface(b.tunName)
linuxTUN := tunInterface.(tun.LinuxTUN)
if linuxTUN.BatchSize() > 1 {
b.batchTUN = linuxTUN
@ -130,32 +129,29 @@ func (b *backendLinux) start(scope *adapter.Scope) error {
restoreBridgeForwarding(forwardingRestore)
return nil
})
if b.routeTable != 0 {
scope.Add(func() error {
b.egressAccess.Lock()
flushBridgeRouteTable(b.routeTable)
b.egressAccess.Unlock()
return nil
})
}
if b.boundInterface != "" {
b.syncEgress()
}
scope.Add(func() error {
b.egressAccess.Lock()
removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET, b.inet4Port)
removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET6, b.inet6Port)
flushBridgeRouteTable(b.routeTable)
b.egressAccess.Unlock()
return nil
})
err = setupBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET, b.inet4Port)
b.syncEgress()
preferMainTable := b.boundInterface == ""
scope.Add(func() error {
b.egressAccess.Lock()
removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, preferMainTable, unix.AF_INET, b.inet4Port)
removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, preferMainTable, unix.AF_INET6, b.inet6Port)
b.egressAccess.Unlock()
return nil
})
err = setupBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, preferMainTable, unix.AF_INET, b.inet4Port)
if err != nil {
return E.Cause(err, "set up bridge routing")
}
err = setupBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET6, b.inet6Port)
err = setupBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, preferMainTable, unix.AF_INET6, b.inet6Port)
if err != nil {
b.logger.Debug(E.Cause(err, "IPv6 bridge routing unavailable, disabling IPv6 forwarding"))
removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET6, b.inet6Port)
removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, preferMainTable, unix.AF_INET6, b.inet6Port)
b.inet6Port = netip.Addr{}
}
if b.batchTUN != nil {
@ -166,28 +162,9 @@ func (b *backendLinux) start(scope *adapter.Scope) error {
egress := "auto"
if b.boundInterface != "" {
egress = b.boundInterface
monitor := b.networkManager.NetworkMonitor()
if monitor != nil {
element := monitor.RegisterCallback(func() { b.syncEgress() })
scope.Add(func() error {
monitor.UnregisterCallback(element)
return nil
})
} else {
b.logger.Debug("network monitor unavailable, pinned egress will not track interface changes")
}
b.syncEgress()
} else {
monitor := b.networkManager.InterfaceMonitor()
if monitor != nil {
element := monitor.RegisterCallback(func(_ *control.Interface, _ int) { b.updateClamp() })
scope.Add(func() error {
monitor.UnregisterCallback(element)
return nil
})
}
b.updateClamp()
}
b.registerMonitors(scope, b.syncEgress)
b.syncEgress()
natMode := "masquerade"
if fullConeSupported() {
natMode = "full-cone NAT"
@ -211,6 +188,7 @@ func (b *backendLinux) startPlatform(scope *adapter.Scope) error {
scope.Add(session.Close)
b.session = session
b.tunName = session.Name()
b.networkManager.RegisterBridgeInterface(b.tunName)
if !session.Inet6Active() {
b.inet6Port = netip.Addr{}
}
@ -318,13 +296,11 @@ func (b *backendLinux) batchReadLoop() {
batch := make([][]byte, 0, batchSize)
headroom := -1
var (
buffers [][]byte
readRetry tun.ReadRetry
buffers [][]byte
returnPaths []tun.Return
readRetry tun.ReadRetry
)
for {
b.returnAccess.Lock()
returnPaths := b.returnPaths
b.returnAccess.Unlock()
pathHeadroom := 0
if len(returnPaths) > 0 {
pathHeadroom = returnPaths[0].ReturnHeadroom()
@ -351,6 +327,9 @@ func (b *backendLinux) batchReadLoop() {
} else {
readRetry.Reset()
}
b.returnAccess.Lock()
returnPaths = b.returnPaths
b.returnAccess.Unlock()
if n == 0 || len(returnPaths) == 0 {
continue
}
@ -384,9 +363,6 @@ func (b *backendLinux) batchReadLoop() {
}
}
// The policy rules default to priority 100/101, ahead of sing-tun auto_route's rules,
// so forwarded packets egress the physical interface instead of looping back into
// a tun.
func (b *backendLinux) syncEgress() {
b.egressAccess.Lock()
defer b.egressAccess.Unlock()
@ -396,63 +372,37 @@ func (b *backendLinux) syncEgress() {
default:
}
b.updateClampLocked()
flushBridgeRouteTable(b.routeTable)
link, err := netlink.LinkByName(b.boundInterface)
if err != nil {
for _, family := range activeBridgeFamilies(b.inet6Port) {
blackholeBridgeDefault(b.routeTable, family)
}
b.logger.Debug("pinned egress ", b.boundInterface, " absent, dropping forwarded traffic")
return
egress := b.resolveEgress()
var link netlink.Link
if egress != "" {
link, _ = netlink.LinkByName(egress)
}
fallbackType := unix.RTN_UNREACHABLE
if b.boundInterface != "" {
fallbackType = unix.RTN_BLACKHOLE
}
var routes []netlink.Route
for _, family := range activeBridgeFamilies(b.inet6Port) {
b.syncEgressFamily(family, link.Attrs().Index)
}
}
func (b *backendLinux) syncEgressFamily(family int, linkIndex int) {
connected, err := netlink.RouteListFiltered(family, &netlink.Route{
LinkIndex: linkIndex,
Table: unix.RT_TABLE_MAIN,
}, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE)
if err == nil {
for _, route := range connected {
if route.Gw != nil || route.Dst == nil {
continue
}
pinned := route
pinned.Table = b.routeTable
pinned.ILinkIndex = 0
_ = netlink.RouteReplace(&pinned)
if link == nil {
routes = append(routes, unroutableBridgeRoute(b.routeTable, family, fallbackType))
} else {
routes = append(routes, egressRoutes(b.routeTable, family, link, fallbackType)...)
}
}
resolved, err := netlink.RouteGetWithOptions(probeAddress(family), &netlink.RouteGetOptions{Oif: b.boundInterface})
if err == nil && len(resolved) > 0 {
defaultRoute := &netlink.Route{
LinkIndex: linkIndex,
Table: b.routeTable,
Dst: defaultDestination(family),
}
if len(resolved[0].Gw) > 0 {
defaultRoute.Gw = resolved[0].Gw
}
err = netlink.RouteReplace(defaultRoute)
if err == nil {
return
}
}
blackholeBridgeDefault(b.routeTable, family)
}
func (b *backendLinux) updateClamp() {
b.egressAccess.Lock()
defer b.egressAccess.Unlock()
select {
case <-b.closed:
if bridgeRoutesEqual(listBridgeRoutes(b.routeTable), routes) {
return
default:
}
b.updateClampLocked()
if link == nil {
if b.boundInterface != "" {
b.logger.Debug("bridge egress ", egress, " absent, dropping forwarded traffic")
} else {
b.logger.Debug("no default interface for bridge egress")
}
}
err := applyBridgeRoutes(b.routeTable, routes, fallbackType)
if err != nil {
b.logger.Debug(E.Cause(err, "pin bridge egress default route"))
}
}
func (b *backendLinux) updateClampLocked() {

View file

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"runtime"
"slices"
"strconv"
"strings"
"sync"
@ -289,7 +290,7 @@ func cleanupBridgeIptablesTable(binary string, table string, hookChain string, t
_ = exec.Command(path, "-t", table, "-X", tableName).Run()
}
func setupBridgeFamily(tunName string, ruleIndex int, routeTable int, family int, port netip.Addr) error {
func setupBridgeFamily(tunName string, ruleIndex int, routeTable int, preferMainTable bool, family int, port netip.Addr) error {
if !port.IsValid() {
return nil
}
@ -301,7 +302,7 @@ func setupBridgeFamily(tunName string, ruleIndex int, routeTable int, family int
if err != nil {
return E.Cause(err, "add route")
}
for _, rule := range bridgeFamilyRules(tunName, ruleIndex, routeTable, family, port) {
for _, rule := range bridgeFamilyRules(tunName, ruleIndex, routeTable, preferMainTable, family, port) {
_ = netlink.RuleDel(rule)
err = netlink.RuleAdd(rule)
if err != nil {
@ -311,7 +312,7 @@ func setupBridgeFamily(tunName string, ruleIndex int, routeTable int, family int
return nil
}
func removeBridgeFamily(tunName string, ruleIndex int, routeTable int, family int, port netip.Addr) {
func removeBridgeFamily(tunName string, ruleIndex int, routeTable int, preferMainTable bool, family int, port netip.Addr) {
if !port.IsValid() {
return
}
@ -319,7 +320,7 @@ func removeBridgeFamily(tunName string, ruleIndex int, routeTable int, family in
if err == nil {
_ = netlink.RouteDel(bridgeFamilyRoute(link.Attrs().Index, family, port))
}
for _, rule := range bridgeFamilyRules(tunName, ruleIndex, routeTable, family, port) {
for _, rule := range bridgeFamilyRules(tunName, ruleIndex, routeTable, preferMainTable, family, port) {
_ = netlink.RuleDel(rule)
}
}
@ -337,47 +338,174 @@ func bridgeFamilyRoute(linkIndex int, family int, port netip.Addr) *netlink.Rout
return route
}
func bridgeFamilyRules(tunName string, ruleIndex int, routeTable int, family int, port netip.Addr) []*netlink.Rule {
forwardTable := unix.RT_TABLE_MAIN
if routeTable != 0 {
forwardTable = routeTable
// The rules sit ahead of sing-tun auto_route's rules (9000 by default) so forwarded
// packets never loop back into a tun. netd keeps every network's routes in the
// network's own table behind fwmark and "iif lo" rules that forwarded packets
// never satisfy, so with preferMainTable the main lookup finds nothing on Android
// and the bridge table, filled from the egress interface, takes over.
func bridgeFamilyRules(tunName string, ruleIndex int, routeTable int, preferMainTable bool, family int, port netip.Addr) []*netlink.Rule {
var rules []*netlink.Rule
priority := ruleIndex
if preferMainTable {
mainRule := netlink.NewRule()
mainRule.Priority = priority
mainRule.IifName = tunName
mainRule.Table = unix.RT_TABLE_MAIN
mainRule.Family = family
rules = append(rules, mainRule)
priority++
}
iifRule := netlink.NewRule()
iifRule.Priority = ruleIndex
iifRule.Priority = priority
iifRule.IifName = tunName
iifRule.Table = forwardTable
iifRule.Table = routeTable
iifRule.Family = family
priority++
toRule := netlink.NewRule()
toRule.Priority = ruleIndex + 1
toRule.Priority = priority
toRule.Dst = netip.PrefixFrom(port, port.BitLen())
toRule.Table = unix.RT_TABLE_MAIN
toRule.Family = family
return []*netlink.Rule{iifRule, toRule}
return append(rules, iifRule, toRule)
}
func flushBridgeRouteTable(routeTable int) {
// Default routes are dumped without RTA_DST, and the netlink client refuses to
// delete a route with neither destination nor gateway.
func listBridgeRoutes(routeTable int) []netlink.Route {
var routes []netlink.Route
for _, family := range []int{unix.AF_INET, unix.AF_INET6} {
routes, err := netlink.RouteListFiltered(family, &netlink.Route{Table: routeTable}, netlink.RT_FILTER_TABLE)
familyRoutes, err := netlink.RouteListFiltered(family, &netlink.Route{Table: routeTable}, netlink.RT_FILTER_TABLE)
if err != nil {
continue
}
for _, route := range routes {
toDelete := route
_ = netlink.RouteDel(&toDelete)
for _, route := range familyRoutes {
if route.Dst == nil {
route.Dst = defaultDestination(family)
}
routes = append(routes, route)
}
}
return routes
}
func flushBridgeRouteTable(routeTable int) {
for _, route := range listBridgeRoutes(routeTable) {
_ = netlink.RouteDel(&route)
}
}
func blackholeBridgeDefault(routeTable int, family int) {
_ = netlink.RouteReplace(&netlink.Route{
func unroutableBridgeDefault(routeTable int, family int, routeType int) {
route := unroutableBridgeRoute(routeTable, family, routeType)
_ = netlink.RouteReplace(&route)
}
func unroutableBridgeRoute(routeTable int, family int, routeType int) netlink.Route {
return netlink.Route{
Table: routeTable,
Family: family,
Type: unix.RTN_BLACKHOLE,
Type: routeType,
Dst: defaultDestination(family),
})
}
}
func applyBridgeRoutes(routeTable int, routes []netlink.Route, fallbackType int) error {
flushBridgeRouteTable(routeTable)
var applyErr error
for index := range routes {
err := netlink.RouteReplace(&routes[index])
if err != nil && routes[index].Type == unix.RTN_UNICAST && isDefaultDestination(routes[index].Dst) {
unroutableBridgeDefault(routeTable, routes[index].Family, fallbackType)
applyErr = E.Errors(applyErr, err)
}
}
return applyErr
}
// The kernel drops routes through a link that goes down and routes whose
// preferred source is removed, so the table is compared against what the kernel
// holds rather than against what was last written.
func bridgeRoutesEqual(current []netlink.Route, desired []netlink.Route) bool {
if len(current) != len(desired) {
return false
}
matched := make([]bool, len(current))
for _, route := range desired {
found := false
for index := range current {
if matched[index] || !bridgeRouteEqual(current[index], route) {
continue
}
matched[index] = true
found = true
break
}
if !found {
return false
}
}
return true
}
func bridgeRouteEqual(left netlink.Route, right netlink.Route) bool {
if left.Family != right.Family || left.Type != right.Type {
return false
}
if left.Type == unix.RTN_UNICAST && left.LinkIndex != right.LinkIndex {
return false
}
return left.Dst.String() == right.Dst.String() && left.Gw.Equal(right.Gw)
}
// The connected routes are collected from every table because netd stores them
// in the network's own table instead of main.
func egressRoutes(routeTable int, family int, link netlink.Link, fallbackType int) []netlink.Route {
var routes []netlink.Route
linkRoutes, err := netlink.RouteListFiltered(family, &netlink.Route{
LinkIndex: link.Attrs().Index,
Table: unix.RT_TABLE_UNSPEC,
}, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE)
if err == nil {
for _, route := range linkRoutes {
if route.Table == unix.RT_TABLE_LOCAL || route.Table == routeTable ||
route.Type != unix.RTN_UNICAST || route.Gw != nil || isDefaultDestination(route.Dst) {
continue
}
connected := netlink.Route{
LinkIndex: link.Attrs().Index,
Table: routeTable,
Family: family,
Type: unix.RTN_UNICAST,
Scope: route.Scope,
Dst: route.Dst,
Priority: route.Priority,
}
if slices.ContainsFunc(routes, func(existing netlink.Route) bool {
return existing.Dst.String() == connected.Dst.String() && existing.Priority == connected.Priority
}) {
continue
}
routes = append(routes, connected)
}
}
resolved, err := netlink.RouteGetWithOptions(probeAddress(family), &netlink.RouteGetOptions{Oif: link.Attrs().Name})
if err != nil || len(resolved) == 0 {
return append(routes, unroutableBridgeRoute(routeTable, family, fallbackType))
}
defaultRoute := netlink.Route{
LinkIndex: link.Attrs().Index,
Table: routeTable,
Family: family,
Type: unix.RTN_UNICAST,
Dst: defaultDestination(family),
}
if len(resolved[0].Gw) > 0 {
defaultRoute.Gw = resolved[0].Gw
defaultRoute.Flags = int(unix.RTNH_F_ONLINK)
}
return append(routes, defaultRoute)
}
func activeBridgeFamilies(inet6Port netip.Addr) []int {

View file

@ -99,93 +99,50 @@ func (s *Service) start(bridgeName string) error {
s.inet6Port = netip.Addr{}
}
s.forwardingRestore = enableBridgeForwarding(s.logger, s.tunName, s.inet4Port.IsValid(), s.inet6Port.IsValid())
err = setupBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, unix.AF_INET, s.inet4Port)
err = setupBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, false, unix.AF_INET, s.inet4Port)
if err != nil {
return E.Cause(err, "set up bridge routing")
}
err = setupBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, unix.AF_INET6, s.inet6Port)
err = setupBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, false, unix.AF_INET6, s.inet6Port)
if err != nil {
s.logger.Debug(E.Cause(err, "IPv6 bridge routing unavailable, disabling IPv6 forwarding"))
removeBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, unix.AF_INET6, s.inet6Port)
removeBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, false, unix.AF_INET6, s.inet6Port)
s.inet6Port = netip.Addr{}
}
for _, family := range activeBridgeFamilies(s.inet6Port) {
blackholeBridgeDefault(s.routeTable, family)
unroutableBridgeDefault(s.routeTable, family, unix.RTN_BLACKHOLE)
}
s.startNetworkMonitor()
return nil
}
func (s *Service) syncEgressLocked() error {
flushBridgeRouteTable(s.routeTable)
if s.egressName == "" {
for _, family := range activeBridgeFamilies(s.inet6Port) {
blackholeBridgeDefault(s.routeTable, family)
}
return nil
}
link, err := netlink.LinkByName(s.egressName)
if err != nil {
for _, family := range activeBridgeFamilies(s.inet6Port) {
blackholeBridgeDefault(s.routeTable, family)
}
s.logger.Debug("bridge egress ", s.egressName, " absent, dropping forwarded traffic")
return nil
var link netlink.Link
if s.egressName != "" {
link, _ = netlink.LinkByName(s.egressName)
}
var routes []netlink.Route
for _, family := range activeBridgeFamilies(s.inet6Port) {
s.syncEgressFamilyLocked(family, link.Attrs().Index)
if link == nil {
routes = append(routes, unroutableBridgeRoute(s.routeTable, family, unix.RTN_BLACKHOLE))
} else {
routes = append(routes, egressRoutes(s.routeTable, family, link, unix.RTN_BLACKHOLE)...)
}
}
s.updateClampLocked(link.Attrs().MTU)
return nil
}
// Unlike the in-process backend this copies routes from every table: on Android
// netd leaves the main table empty and keeps each network's routes in its own
// table, resolvable only through fwmark rules that forwarded packets never carry.
func (s *Service) syncEgressFamilyLocked(family int, linkIndex int) {
routes, err := netlink.RouteListFiltered(family, &netlink.Route{
LinkIndex: linkIndex,
Table: unix.RT_TABLE_UNSPEC,
}, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE)
if link != nil {
s.updateClampLocked(link.Attrs().MTU)
}
if bridgeRoutesEqual(listBridgeRoutes(s.routeTable), routes) {
return nil
}
if s.egressName != "" && link == nil {
s.logger.Debug("bridge egress ", s.egressName, " absent, dropping forwarded traffic")
}
err := applyBridgeRoutes(s.routeTable, routes, unix.RTN_BLACKHOLE)
if err != nil {
blackholeBridgeDefault(s.routeTable, family)
return
}
var defaultRoute *netlink.Route
for _, route := range routes {
if route.Table == unix.RT_TABLE_LOCAL || route.Table == s.routeTable {
continue
}
if route.Type != unix.RTN_UNICAST {
continue
}
if isDefaultDestination(route.Dst) {
if defaultRoute == nil {
pinned := route
defaultRoute = &pinned
}
continue
}
if route.Gw != nil {
continue
}
connected := route
connected.Table = s.routeTable
connected.ILinkIndex = 0
_ = netlink.RouteReplace(&connected)
}
if defaultRoute == nil {
blackholeBridgeDefault(s.routeTable, family)
s.logger.Debug("no default route on bridge egress ", s.egressName)
return
}
defaultRoute.Table = s.routeTable
defaultRoute.ILinkIndex = 0
err = netlink.RouteReplace(defaultRoute)
if err != nil {
blackholeBridgeDefault(s.routeTable, family)
s.logger.Debug(E.Cause(err, "pin bridge egress default route"))
}
return nil
}
func (s *Service) updateClampLocked(egressMTU int) {
@ -212,8 +169,8 @@ func (s *Service) Close() error {
defer s.access.Unlock()
if s.tunName != "" {
cleanupBridgeNetfilter(s.nftTableName)
removeBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, unix.AF_INET, s.inet4Port)
removeBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, unix.AF_INET6, s.inet6Port)
removeBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, false, unix.AF_INET, s.inet4Port)
removeBridgeFamily(s.tunName, s.ruleIndex, s.routeTable, false, unix.AF_INET6, s.inet6Port)
flushBridgeRouteTable(s.routeTable)
}
restoreBridgeForwarding(s.forwardingRestore)

View file

@ -369,6 +369,7 @@ func (t *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error {
if t.tunOptions.Name == "" {
t.tunOptions.Name = tun.CalculateInterfaceName("")
}
t.tunOptions.BridgeInterface = t.networkManager.BridgeInterfaces()
if t.tunOptions.NetNs != "" {
manager := service.FromContext[adapter.NetworkNamespaceManager](t.ctx)
if manager != nil {

View file

@ -42,6 +42,8 @@ type NetworkManager struct {
autoDetectInterface bool
defaultOptions adapter.NetworkOptions
autoRedirectOutputMark uint32
bridgeInterfaceAccess sync.Mutex
bridgeInterfaces []string
networkMonitor tun.NetworkUpdateMonitor
interfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager
@ -392,6 +394,20 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 {
return r.autoRedirectOutputMark
}
func (r *NetworkManager) RegisterBridgeInterface(interfaceName string) {
r.bridgeInterfaceAccess.Lock()
defer r.bridgeInterfaceAccess.Unlock()
if !slices.Contains(r.bridgeInterfaces, interfaceName) {
r.bridgeInterfaces = append(r.bridgeInterfaces, interfaceName)
}
}
func (r *NetworkManager) BridgeInterfaces() []string {
r.bridgeInterfaceAccess.Lock()
defer r.bridgeInterfaceAccess.Unlock()
return slices.Clone(r.bridgeInterfaces)
}
func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {
return func(network, address string, conn syscall.RawConn) error {
if r.autoRedirectOutputMark == 0 {