mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-10-21 11:33:20 +00:00
Fix bridge forwarding with auto-redirect
This commit is contained in:
parent
5618bef173
commit
2f10bfc8ea
9 changed files with 251 additions and 194 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ icon: material/new-box
|
|||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且仅在设置了 `interface` 时生效。
|
||||
仅支持 Linux。
|
||||
|
||||
用于固定出口路由的 Linux iproute2 路由表索引。
|
||||
Linux iproute2 路由表索引。
|
||||
|
||||
默认使用 `2200` + 实例索引。
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue