瀏覽代碼

tun: Set address sets to routes

世界 10 月之前
父節點
當前提交
7aac801ccd

+ 42 - 14
docs/configuration/inbound/tun.md

@@ -4,7 +4,9 @@ icon: material/alert-decagram
 
 !!! quote "Changes in sing-box 1.11.0"
 
-    :material-delete-alert: [gso](#gso)
+    :material-delete-alert: [gso](#gso)  
+    :material-alert-decagram: [route_address_set](#stack)  
+    :material-alert-decagram: [route_exclude_address_set](#stack)
 
 !!! quote "Changes in sing-box 1.10.0"
 
@@ -248,7 +250,7 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
 
 !!! question "Since sing-box 1.10.0"
 
-Connection input mark used by `route_address_set` and `route_exclude_address_set`.
+Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
 
 `0x2023` is used by default.
 
@@ -256,7 +258,7 @@ Connection input mark used by `route_address_set` and `route_exclude_address_set
 
 !!! question "Since sing-box 1.10.0"
 
-Connection output mark used by `route_address_set` and `route_exclude_address_set`.
+Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
 
 `0x2024` is used by default.
 
@@ -329,29 +331,55 @@ Exclude custom routes when `auto_route` is enabled.
 
 #### route_address_set
 
-!!! question "Since sing-box 1.10.0"
+=== "With `auto_redirect` enabled"
 
-!!! quote ""
+    !!! question "Since sing-box 1.10.0"
 
-    Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
+    !!! quote ""
+    
+        Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
+    
+    Add the destination IP CIDR rules in the specified rule-sets to the firewall.
+    Unmatched traffic will bypass the sing-box routes.
+    
+    Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
 
-Add the destination IP CIDR rules in the specified rule-sets to the firewall.
-Unmatched traffic will bypass the sing-box routes.
+=== "Without `auto_redirect` enabled"
 
-Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
+    !!! question "Since sing-box 1.11.0"
+    
+    Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`.
+    Unmatched traffic will bypass the sing-box routes.
+
+    Note that it **doesn't work on the Android graphical client** due to
+    the Android VpnService not being able to handle a large number of routes (DeadSystemException),
+    but otherwise it works fine on all command line clients and Apple platforms.
 
 #### route_exclude_address_set
 
-!!! question "Since sing-box 1.10.0"
+=== "With `auto_redirect` enabled"
 
-!!! quote ""
+    !!! question "Since sing-box 1.10.0"
+
+    !!! quote ""
 
     Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
 
-Add the destination IP CIDR rules in the specified rule-sets to the firewall.
-Matched traffic will bypass the sing-box routes.
+    Add the destination IP CIDR rules in the specified rule-sets to the firewall.
+    Matched traffic will bypass the sing-box routes.
+    
+    Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
+
+=== "Without `auto_redirect` enabled"
+
+    !!! question "Since sing-box 1.11.0"
+    
+    Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`.
+    Matched traffic will bypass the sing-box routes.
 
-Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
+    Note that it **doesn't work on the Android graphical client** due to
+    the Android VpnService not being able to handle a large number of routes (DeadSystemException),
+    but otherwise it works fine on all command line clients and Apple platforms.
 
 #### endpoint_independent_nat
 

+ 39 - 13
docs/configuration/inbound/tun.zh.md

@@ -4,7 +4,9 @@ icon: material/alert-decagram
 
 !!! quote "sing-box 1.11.0 中的更改"
 
-    :material-delete-alert: [gso](#gso)
+    :material-delete-alert: [gso](#gso)  
+    :material-alert-decagram: [route_address_set](#stack)  
+    :material-alert-decagram: [route_exclude_address_set](#stack)
 
 !!! quote "sing-box 1.10.0 中的更改"
 
@@ -329,29 +331,53 @@ tun 接口的 IPv6 前缀。
 
 #### route_address_set
 
-!!! question "自 sing-box 1.10.0 起"
+=== "`auto_redirect` 已启用"
 
-!!! quote ""
+    !!! question "自 sing-box 1.10.0 起"
+    
+    !!! quote ""
+    
+        仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 
+    
+    将指定规则集中的目标 IP CIDR 规则添加到防火墙。
+    不匹配的流量将绕过 sing-box 路由。
+    
+    与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
 
-    仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 
+=== "`auto_redirect` 未启用"
 
-将指定规则集中的目标 IP CIDR 规则添加到防火墙。
-不匹配的流量将绕过 sing-box 路由。
+    !!! question "自 sing-box 1.11.0 起"
 
-与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
+    将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`。
+    不匹配的流量将绕过 sing-box 路由。
+
+    请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException),
+    因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
 
 #### route_exclude_address_set
 
-!!! question "自 sing-box 1.10.0 起"
+=== "`auto_redirect` 已启用"
 
-!!! quote ""
+    !!! question "自 sing-box 1.10.0 起"
+    
+    !!! quote ""
+    
+        仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 
+
+    将指定规则集中的目标 IP CIDR 规则添加到防火墙。
+    匹配的流量将绕过 sing-box 路由。
+
+    与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
+
+=== "`auto_redirect` 未启用"
 
-    仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
+    !!! question "自 sing-box 1.11.0 起"
 
-将指定规则集中的目标 IP CIDR 规则添加到防火墙。
-匹配的流量将绕过 sing-box 路由。
+    将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`
+    匹配的流量将绕过 sing-box 路由。
 
-与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
+    请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException),
+    因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
 
 #### endpoint_independent_nat
 

+ 4 - 0
experimental/libbox/config.go

@@ -66,6 +66,10 @@ func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions op
 	return nil, os.ErrInvalid
 }
 
+func (s *platformInterfaceStub) UpdateRouteOptions(options *tun.Options, platformInterface option.TunPlatformOptions) error {
+	return os.ErrInvalid
+}
+
 func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {
 	return true
 }

+ 1 - 0
experimental/libbox/platform.go

@@ -9,6 +9,7 @@ type PlatformInterface interface {
 	UsePlatformAutoDetectInterfaceControl() bool
 	AutoDetectInterfaceControl(fd int32) error
 	OpenTun(options TunOptions) (int32, error)
+	UpdateRouteOptions(options TunOptions) error
 	WriteLog(message string)
 	UseProcFS() bool
 	FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)

+ 1 - 0
experimental/libbox/platform/interface.go

@@ -13,6 +13,7 @@ type Interface interface {
 	UsePlatformAutoDetectInterfaceControl() bool
 	AutoDetectInterfaceControl(fd int) error
 	OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
+	UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error
 	CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
 	Interfaces() ([]adapter.NetworkInterface, error)
 	UnderNetworkExtension() bool

+ 16 - 2
experimental/libbox/service.go

@@ -148,10 +148,10 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
 
 func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
 	if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
-		return nil, E.New("android: unsupported uid options")
+		return nil, E.New("platform: unsupported uid options")
 	}
 	if len(options.IncludeAndroidUser) > 0 {
-		return nil, E.New("android: unsupported android_user option")
+		return nil, E.New("platform: unsupported android_user option")
 	}
 	routeRanges, err := options.BuildAutoRouteRanges(true)
 	if err != nil {
@@ -174,6 +174,20 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
 	return tun.New(*options)
 }
 
+func (w *platformInterfaceWrapper) UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error {
+	if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
+		return E.New("android: unsupported uid options")
+	}
+	if len(options.IncludeAndroidUser) > 0 {
+		return E.New("android: unsupported android_user option")
+	}
+	routeRanges, err := options.BuildAutoRouteRanges(true)
+	if err != nil {
+		return err
+	}
+	return w.iif.UpdateRouteOptions(&tunOptions{options, routeRanges, platformOptions})
+}
+
 func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
 	return &platformDefaultInterfaceMonitor{
 		platformInterfaceWrapper: w,

+ 105 - 56
protocol/tun/inbound.go

@@ -209,6 +209,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
 		platformInterface: service.FromContext[platform.Interface](ctx),
 		platformOptions:   common.PtrValueOrDefault(options.Platform),
 	}
+	for _, routeAddressSet := range options.RouteAddressSet {
+		ruleSet, loaded := router.RuleSet(routeAddressSet)
+		if !loaded {
+			return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
+		}
+		ruleSet.IncRef()
+		inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
+	}
+	for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
+		ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
+		if !loaded {
+			return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
+		}
+		ruleSet.IncRef()
+		inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
+	}
 	if options.AutoRedirect {
 		if !options.AutoRoute {
 			return nil, E.New("`auto_route` is required by `auto_redirect`")
@@ -229,32 +245,11 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
 		if err != nil {
 			return nil, E.Cause(err, "initialize auto-redirect")
 		}
-		if runtime.GOOS != "android" {
-			var markMode bool
-			for _, routeAddressSet := range options.RouteAddressSet {
-				ruleSet, loaded := router.RuleSet(routeAddressSet)
-				if !loaded {
-					return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
-				}
-				ruleSet.IncRef()
-				inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
-				markMode = true
-			}
-			for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
-				ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
-				if !loaded {
-					return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
-				}
-				ruleSet.IncRef()
-				inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
-				markMode = true
-			}
-			if markMode {
-				inbound.tunOptions.AutoRedirectMarkMode = true
-				err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
-				if err != nil {
-					return nil, err
-				}
+		if !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) {
+			inbound.tunOptions.AutoRedirectMarkMode = true
+			err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
+			if err != nil {
+				return nil, err
 			}
 		}
 	}
@@ -310,18 +305,62 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
 		if t.tunOptions.Name == "" {
 			t.tunOptions.Name = tun.CalculateInterfaceName("")
 		}
+		if t.platformInterface == nil || runtime.GOOS != "android" {
+			t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
+			for _, routeRuleSet := range t.routeRuleSet {
+				ipSets := routeRuleSet.ExtractIPSet()
+				if len(ipSets) == 0 {
+					t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
+				}
+				t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
+				routeRuleSet.DecRef()
+				t.routeAddressSet = append(t.routeAddressSet, ipSets...)
+			}
+			t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
+			for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
+				ipSets := routeExcludeRuleSet.ExtractIPSet()
+				if len(ipSets) == 0 {
+					t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
+				}
+				t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
+				routeExcludeRuleSet.DecRef()
+				t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
+			}
+		}
 		var (
 			tunInterface tun.Tun
 			err          error
 		)
 		monitor := taskmonitor.New(t.logger, C.StartTimeout)
-		monitor.Start("open tun interface")
+		tunOptions := t.tunOptions
+		if t.autoRedirect == nil && !(runtime.GOOS == "android" && t.platformInterface != nil) {
+			for _, ipSet := range t.routeAddressSet {
+				for _, prefix := range ipSet.Prefixes() {
+					if prefix.Addr().Is4() {
+						tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)
+					} else {
+						tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix)
+					}
+				}
+			}
+			for _, ipSet := range t.routeExcludeAddressSet {
+				for _, prefix := range ipSet.Prefixes() {
+					if prefix.Addr().Is4() {
+						tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix)
+					} else {
+						tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix)
+					}
+				}
+			}
+		}
+		monitor.Start("open interface")
 		if t.platformInterface != nil {
-			tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
+			tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions)
 		} else {
-			tunInterface, err = tun.New(t.tunOptions)
+			tunInterface, err = tun.New(tunOptions)
 		}
 		monitor.Finish()
+		t.tunOptions.Name = tunOptions.Name
 		if err != nil {
 			return E.Cause(err, "configure tun interface")
 		}
@@ -366,39 +405,15 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
 			return E.Cause(err, "starting TUN interface")
 		}
 		if t.autoRedirect != nil {
-			t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
-			for _, routeRuleSet := range t.routeRuleSet {
-				ipSets := routeRuleSet.ExtractIPSet()
-				if len(ipSets) == 0 {
-					t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
-				}
-				t.routeAddressSet = append(t.routeAddressSet, ipSets...)
-			}
-			t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
-			for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
-				ipSets := routeExcludeRuleSet.ExtractIPSet()
-				if len(ipSets) == 0 {
-					t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
-				}
-				t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
-			}
 			monitor.Start("initialize auto-redirect")
 			err := t.autoRedirect.Start()
 			monitor.Finish()
 			if err != nil {
 				return E.Cause(err, "auto-redirect")
 			}
-			for _, routeRuleSet := range t.routeRuleSet {
-				t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
-				routeRuleSet.DecRef()
-			}
-			for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
-				t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
-				routeExcludeRuleSet.DecRef()
-			}
-			t.routeAddressSet = nil
-			t.routeExcludeAddressSet = nil
 		}
+		t.routeAddressSet = nil
+		t.routeExcludeAddressSet = nil
 	}
 	return nil
 }
@@ -406,7 +421,41 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
 func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) {
 	t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
 	t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
-	t.autoRedirect.UpdateRouteAddressSet()
+	if t.autoRedirect != nil {
+		t.autoRedirect.UpdateRouteAddressSet()
+	} else {
+		tunOptions := t.tunOptions
+		for _, ipSet := range t.routeAddressSet {
+			for _, prefix := range ipSet.Prefixes() {
+				if prefix.Addr().Is4() {
+					tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)
+				} else {
+					tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix)
+				}
+			}
+		}
+		for _, ipSet := range t.routeExcludeAddressSet {
+			for _, prefix := range ipSet.Prefixes() {
+				if prefix.Addr().Is4() {
+					tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix)
+				} else {
+					tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix)
+				}
+			}
+		}
+		if t.platformInterface != nil {
+			err := t.platformInterface.UpdateRouteOptions(&tunOptions, t.platformOptions)
+			if err != nil {
+				t.logger.Error("update route addresses: ", err)
+			}
+		} else {
+			err := t.tunIf.UpdateRouteOptions(tunOptions)
+			if err != nil {
+				t.logger.Error("update route addresses: ", err)
+			}
+		}
+		t.logger.Info("updated route addresses")
+	}
 	t.routeAddressSet = nil
 	t.routeExcludeAddressSet = nil
 }

+ 1 - 1
route/router.go

@@ -363,7 +363,6 @@ func (r *Router) Start(stage adapter.StartStage) error {
 				return E.Cause(err, "initialize DNS server[", i, "]")
 			}
 		}
-	case adapter.StartStatePostStart:
 		var cacheContext *adapter.HTTPStartContext
 		if len(r.ruleSets) > 0 {
 			monitor.Start("initialize rule-set")
@@ -419,6 +418,7 @@ func (r *Router) Start(stage adapter.StartStage) error {
 				}
 			}
 		}
+	case adapter.StartStatePostStart:
 		for i, rule := range r.rules {
 			monitor.Start("initialize rule[", i, "]")
 			err := rule.Start()