tun: Set address sets to routes

This commit is contained in:
世界 2024-12-23 22:24:10 +08:00
parent 606ff668da
commit 0c754505f7
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
8 changed files with 209 additions and 86 deletions

View file

@ -5,6 +5,8 @@ icon: material/alert-decagram
!!! quote "Changes in sing-box 1.11.0" !!! 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" !!! 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" !!! 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. `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" !!! 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. `0x2024` is used by default.
@ -329,6 +331,8 @@ Exclude custom routes when `auto_route` is enabled.
#### route_address_set #### route_address_set
=== "With `auto_redirect` enabled"
!!! question "Since sing-box 1.10.0" !!! question "Since sing-box 1.10.0"
!!! quote "" !!! quote ""
@ -340,8 +344,21 @@ Unmatched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`. 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_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 #### route_exclude_address_set
=== "With `auto_redirect` enabled"
!!! question "Since sing-box 1.10.0" !!! question "Since sing-box 1.10.0"
!!! quote "" !!! quote ""
@ -353,6 +370,17 @@ Matched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`. 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.
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 #### endpoint_independent_nat
!!! info "" !!! info ""

View file

@ -5,6 +5,8 @@ icon: material/alert-decagram
!!! quote "sing-box 1.11.0 中的更改" !!! 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 中的更改" !!! quote "sing-box 1.10.0 中的更改"
@ -329,6 +331,8 @@ tun 接口的 IPv6 前缀。
#### route_address_set #### route_address_set
=== "`auto_redirect` 已启用"
!!! question "自 sing-box 1.10.0 起" !!! question "自 sing-box 1.10.0 起"
!!! quote "" !!! quote ""
@ -340,8 +344,20 @@ tun 接口的 IPv6 前缀。
`route.default_mark``[dialOptions].routing_mark` 冲突。 `route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用"
!!! question "自 sing-box 1.11.0 起"
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`
不匹配的流量将绕过 sing-box 路由。
请注意,由于 Android VpnService 无法处理大量路由DeadSystemException
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
#### route_exclude_address_set #### route_exclude_address_set
=== "`auto_redirect` 已启用"
!!! question "自 sing-box 1.10.0 起" !!! question "自 sing-box 1.10.0 起"
!!! quote "" !!! quote ""
@ -353,6 +369,16 @@ tun 接口的 IPv6 前缀。
`route.default_mark``[dialOptions].routing_mark` 冲突。 `route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用"
!!! question "自 sing-box 1.11.0 起"
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`
匹配的流量将绕过 sing-box 路由。
请注意,由于 Android VpnService 无法处理大量路由DeadSystemException
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
#### endpoint_independent_nat #### endpoint_independent_nat
启用独立于端点的 NAT。 启用独立于端点的 NAT。

View file

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

View file

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

View file

@ -13,6 +13,7 @@ type Interface interface {
UsePlatformAutoDetectInterfaceControl() bool UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error AutoDetectInterfaceControl(fd int) error
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, 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 CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
Interfaces() ([]adapter.NetworkInterface, error) Interfaces() ([]adapter.NetworkInterface, error)
SetUnderlyingNetworks(networks []adapter.NetworkInterface) error SetUnderlyingNetworks(networks []adapter.NetworkInterface) error

View file

@ -148,10 +148,10 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { 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 { 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) routeRanges, err := options.BuildAutoRouteRanges(true)
if err != nil { if err != nil {
@ -174,6 +174,20 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
return tun.New(*options) 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 { func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
return &platformDefaultInterfaceMonitor{ return &platformDefaultInterfaceMonitor{
platformInterfaceWrapper: w, platformInterfaceWrapper: w,

View file

@ -209,6 +209,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
platformInterface: service.FromContext[platform.Interface](ctx), platformInterface: service.FromContext[platform.Interface](ctx),
platformOptions: common.PtrValueOrDefault(options.Platform), 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.AutoRedirect {
if !options.AutoRoute { if !options.AutoRoute {
return nil, E.New("`auto_route` is required by `auto_redirect`") return nil, E.New("`auto_route` is required by `auto_redirect`")
@ -229,27 +245,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize auto-redirect") return nil, E.Cause(err, "initialize auto-redirect")
} }
if runtime.GOOS != "android" { if runtime.GOOS != "android" && len(inbound.routeAddressSet) > 0 || len(inbound.routeExcludeAddressSet) > 0 {
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 inbound.tunOptions.AutoRedirectMarkMode = true
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
if err != nil { if err != nil {
@ -257,7 +253,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
} }
} }
} }
}
return inbound, nil return inbound, nil
} }
@ -310,18 +305,62 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if t.tunOptions.Name == "" { if t.tunOptions.Name == "" {
t.tunOptions.Name = tun.CalculateInterfaceName("") 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 ( var (
tunInterface tun.Tun tunInterface tun.Tun
err error err error
) )
monitor := taskmonitor.New(t.logger, C.StartTimeout) monitor := taskmonitor.New(t.logger, C.StartTimeout)
monitor.Start("open tun interface") tunOptions := t.tunOptions
if t.platformInterface != nil { if t.autoRedirect == nil && !(runtime.GOOS == "android" && t.platformInterface != nil) {
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions) for _, ipSet := range t.routeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)
} else { } else {
tunInterface, err = tun.New(t.tunOptions) 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(&tunOptions, t.platformOptions)
} else {
tunInterface, err = tun.New(tunOptions)
} }
monitor.Finish() monitor.Finish()
t.tunOptions.Name = tunOptions.Name
if err != nil { if err != nil {
return E.Cause(err, "configure tun interface") return E.Cause(err, "configure tun interface")
} }
@ -366,47 +405,57 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
return E.Cause(err, "starting TUN interface") return E.Cause(err, "starting TUN interface")
} }
if t.autoRedirect != nil { 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") monitor.Start("initialize auto-redirect")
err := t.autoRedirect.Start() err := t.autoRedirect.Start()
monitor.Finish() monitor.Finish()
if err != nil { if err != nil {
return E.Cause(err, "auto-redirect") 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.routeAddressSet = nil
t.routeExcludeAddressSet = nil t.routeExcludeAddressSet = nil
} }
}
return nil return nil
} }
func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
if t.autoRedirect != nil {
t.autoRedirect.UpdateRouteAddressSet() 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.routeAddressSet = nil
t.routeExcludeAddressSet = nil t.routeExcludeAddressSet = nil
} }

View file

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