Update bypass action behavior for auto redirect

This commit is contained in:
世界
2025-12-27 13:33:58 +08:00
parent b34469986b
commit eaee6bc493
11 changed files with 75 additions and 29 deletions

View File

@@ -21,7 +21,7 @@ import (
type Router interface { type Router interface {
Lifecycle Lifecycle
ConnectionRouter ConnectionRouter
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
ConnectionRouterEx ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
Rules() []Rule Rules() []Rule

View File

@@ -62,19 +62,19 @@ See `route-options` fields below.
} }
``` ```
`bypass` routes connection to the specified outbound. `bypass` bypasses sing-box at the kernel level for auto redirect connections in pre-match.
For tun connections in [pre-match](/configuration/shared/pre-match/), For non-auto-redirect connections and already established connections,
the connection will bypass sing-box and connect directly at the kernel level. if `outbound` is specified, the behavior is the same as `route`;
otherwise, the rule will be skipped.
For non-tun connections and already established connections, the behavior is the same as `route`.
#### outbound #### outbound
==Required==
Tag of target outbound. Tag of target outbound.
If not specified, the rule only matches in [pre-match](/configuration/shared/pre-match/)
from auto redirect, and will be skipped in other contexts.
#### route-options Fields #### route-options Fields
See `route-options` fields below. See `route-options` fields below.

View File

@@ -58,18 +58,16 @@ icon: material/new-box
} }
``` ```
`bypass` 将连接路由到指定出站 `bypass` 在预匹配中为 auto redirect 连接在内核层面绕过 sing-box
对于[预匹配](/configuration/shared/pre-match/)中的 tun 连接,连接将在内核层面绕过 sing-box 直接连接 对于非 auto redirect 连接和已建立的连接,如果指定了 `outbound`,行为与 `route` 相同;否则规则将被跳过
对于非 tun 连接和已建立的连接,行为与 `route` 相同。
#### outbound #### outbound
==必填==
目标出站的标签。 目标出站的标签。
如果未指定,规则仅在来自 auto redirect 的[预匹配](/configuration/shared/pre-match/)中匹配,在其他场景中将被跳过。
#### route-options 字段 #### route-options 字段
参阅下方的 `route-options` 字段。 参阅下方的 `route-options` 字段。

View File

@@ -24,10 +24,14 @@ When a rule matches an action that requires an established connection, pre-match
Reject with TCP RST / ICMP unreachable. Reject with TCP RST / ICMP unreachable.
See [reject](/configuration/route/rule_action/#reject) for details.
#### route #### route
Route ICMP connections to the specified outbound for direct reply. Route ICMP connections to the specified outbound for direct reply.
See [route](/configuration/route/rule_action/#route) for details.
#### bypass #### bypass
!!! question "Since sing-box 1.13.0" !!! question "Since sing-box 1.13.0"
@@ -37,3 +41,10 @@ Route ICMP connections to the specified outbound for direct reply.
Only supported on Linux with `auto_redirect` enabled. Only supported on Linux with `auto_redirect` enabled.
Bypass sing-box and connect directly at kernel level. Bypass sing-box and connect directly at kernel level.
If `outbound` is not specified, the rule only matches in pre-match from auto redirect,
and will be skipped in other contexts.
For all other contexts, bypass with `outbound` behaves like `route` action.
See [bypass](/configuration/route/rule_action/#bypass) for details.

View File

@@ -22,10 +22,14 @@ icon: material/new-box
以 TCP RST / ICMP 不可达拒绝。 以 TCP RST / ICMP 不可达拒绝。
详情参阅 [reject](/configuration/route/rule_action/#reject)。
#### route #### route
将 ICMP 连接路由到指定出站以直接回复。 将 ICMP 连接路由到指定出站以直接回复。
详情参阅 [route](/configuration/route/rule_action/#route)。
#### bypass #### bypass
!!! question "自 sing-box 1.13.0 起" !!! question "自 sing-box 1.13.0 起"
@@ -35,3 +39,9 @@ icon: material/new-box
仅支持 Linux且需要启用 `auto_redirect` 仅支持 Linux且需要启用 `auto_redirect`
在内核层面绕过 sing-box 直接连接。 在内核层面绕过 sing-box 直接连接。
如果未指定 `outbound`,规则仅在来自 auto redirect 的预匹配中匹配,在其他场景中将被跳过。
对于其他所有场景,指定了 `outbound` 的 bypass 行为与 `route` 相同。
详情参阅 [bypass](/configuration/route/rule_action/#bypass)。

View File

@@ -93,9 +93,6 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
if r.Action == C.RuleActionTypeBypass && r.BypassOptions.Outbound == "" {
return E.New("missing outbound for bypass action")
}
return nil return nil
} }

View File

@@ -463,7 +463,7 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}, routeContext, timeout) }, routeContext, timeout, false)
if err != nil { if err != nil {
switch { switch {
case rule.IsBypassed(err): case rule.IsBypassed(err):

View File

@@ -480,7 +480,7 @@ func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destinat
Source: source, Source: source,
Destination: destination, Destination: destination,
InboundOptions: t.inboundOptions, InboundOptions: t.inboundOptions,
}, routeContext, timeout) }, routeContext, timeout, false)
if err != nil { if err != nil {
switch { switch {
case rule.IsBypassed(err): case rule.IsBypassed(err):
@@ -541,7 +541,7 @@ func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksad
Source: source, Source: source,
Destination: destination, Destination: destination,
InboundOptions: t.inboundOptions, InboundOptions: t.inboundOptions,
}, routeContext, timeout) }, routeContext, timeout, true)
if err != nil { if err != nil {
switch { switch {
case rule.IsBypassed(err): case rule.IsBypassed(err):

View File

@@ -140,7 +140,7 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}, routeContext, timeout) }, routeContext, timeout, false)
if err != nil { if err != nil {
switch { switch {
case rule.IsBypassed(err): case rule.IsBypassed(err):

View File

@@ -95,7 +95,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
if deadline.NeedAdditionalReadDeadline(conn) { if deadline.NeedAdditionalReadDeadline(conn) {
conn = deadline.NewConn(conn) conn = deadline.NewConn(conn)
} }
selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil) selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, false, conn, nil)
if err != nil { if err != nil {
return err return err
} }
@@ -114,6 +114,9 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
} }
case *R.RuleActionBypass: case *R.RuleActionBypass:
if action.Outbound == "" {
break
}
var loaded bool var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded { if !loaded {
@@ -223,7 +226,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn))
}*/ }*/
selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn) selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn)
if err != nil { if err != nil {
return err return err
} }
@@ -243,6 +246,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
} }
case *R.RuleActionBypass: case *R.RuleActionBypass:
if action.Outbound == "" {
break
}
var loaded bool var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded { if !loaded {
@@ -289,8 +295,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
return nil return nil
} }
func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) {
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, supportBypass, nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -310,7 +316,20 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
} }
return nil, action.Error(context.Background()) return nil, action.Error(context.Background())
case *R.RuleActionBypass: case *R.RuleActionBypass:
return nil, &R.BypassedError{Cause: tun.ErrBypass} if supportBypass {
return nil, &R.BypassedError{Cause: tun.ErrBypass}
}
if routeContext == nil {
return nil, nil
}
outbound, loaded := r.outbound.Outbound(action.Outbound)
if !loaded {
return nil, E.New("outbound not found: ", action.Outbound)
}
if !common.Contains(outbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
}
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
case *R.RuleActionRoute: case *R.RuleActionRoute:
if routeContext == nil { if routeContext == nil {
return nil, nil return nil, nil
@@ -388,7 +407,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
} }
func (r *Router) matchRule( func (r *Router) matchRule(
ctx context.Context, metadata *adapter.InboundContext, preMatch bool, ctx context.Context, metadata *adapter.InboundContext, preMatch bool, supportBypass bool,
inputConn net.Conn, inputPacketConn N.PacketConn, inputConn net.Conn, inputPacketConn N.PacketConn,
) ( ) (
selectedRule adapter.Rule, selectedRuleIndex int, selectedRule adapter.Rule, selectedRuleIndex int,
@@ -591,8 +610,16 @@ match:
actionType := currentRule.Action().Type() actionType := currentRule.Action().Type()
if actionType == C.RuleActionTypeRoute || if actionType == C.RuleActionTypeRoute ||
actionType == C.RuleActionTypeReject || actionType == C.RuleActionTypeReject ||
actionType == C.RuleActionTypeHijackDNS || actionType == C.RuleActionTypeHijackDNS {
actionType == C.RuleActionTypeBypass { selectedRule = currentRule
selectedRuleIndex = currentRuleIndex
break match
}
if actionType == C.RuleActionTypeBypass {
bypassAction := currentRule.Action().(*R.RuleActionBypass)
if !supportBypass && bypassAction.Outbound == "" {
continue match
}
selectedRule = currentRule selectedRule = currentRule
selectedRuleIndex = currentRuleIndex selectedRuleIndex = currentRuleIndex
break match break match

View File

@@ -183,6 +183,9 @@ func (r *RuleActionBypass) Type() string {
} }
func (r *RuleActionBypass) String() string { func (r *RuleActionBypass) String() string {
if r.Outbound == "" {
return "bypass()"
}
var descriptions []string var descriptions []string
descriptions = append(descriptions, r.Outbound) descriptions = append(descriptions, r.Outbound)
descriptions = append(descriptions, r.Descriptions()...) descriptions = append(descriptions, r.Descriptions()...)