Update bypass action behavior for auto redirect

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

View File

@@ -21,7 +21,7 @@ import (
type Router interface {
Lifecycle
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
RuleSet(tag string) (RuleSet, bool)
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/),
the connection will bypass sing-box and connect directly at the kernel level.
For non-tun connections and already established connections, the behavior is the same as `route`.
For non-auto-redirect connections and already established connections,
if `outbound` is specified, the behavior is the same as `route`;
otherwise, the rule will be skipped.
#### outbound
==Required==
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
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 直接连接
对于非 tun 连接和已建立的连接,行为与 `route` 相同。
对于非 auto redirect 连接和已建立的连接,如果指定了 `outbound`,行为与 `route` 相同;否则规则将被跳过
#### outbound
==必填==
目标出站的标签。
如果未指定,规则仅在来自 auto redirect 的[预匹配](/configuration/shared/pre-match/)中匹配,在其他场景中将被跳过。
#### 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.
See [reject](/configuration/route/rule_action/#reject) for details.
#### route
Route ICMP connections to the specified outbound for direct reply.
See [route](/configuration/route/rule_action/#route) for details.
#### bypass
!!! 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.
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 不可达拒绝。
详情参阅 [reject](/configuration/route/rule_action/#reject)。
#### route
将 ICMP 连接路由到指定出站以直接回复。
详情参阅 [route](/configuration/route/rule_action/#route)。
#### bypass
!!! question "自 sing-box 1.13.0 起"
@@ -35,3 +39,9 @@ icon: material/new-box
仅支持 Linux且需要启用 `auto_redirect`
在内核层面绕过 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 {
return err
}
if r.Action == C.RuleActionTypeBypass && r.BypassOptions.Outbound == "" {
return E.New("missing outbound for bypass action")
}
return nil
}

View File

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

View File

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

View File

@@ -140,7 +140,7 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Network: network,
Source: source,
Destination: destination,
}, routeContext, timeout)
}, routeContext, timeout, false)
if err != nil {
switch {
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) {
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 {
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())
}
case *R.RuleActionBypass:
if action.Outbound == "" {
break
}
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
@@ -223,7 +226,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
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 {
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())
}
case *R.RuleActionBypass:
if action.Outbound == "" {
break
}
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
@@ -289,8 +295,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
return nil
}
func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil)
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, supportBypass, nil, nil)
if err != nil {
return nil, err
}
@@ -310,7 +316,20 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
}
return nil, action.Error(context.Background())
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:
if routeContext == nil {
return nil, nil
@@ -388,7 +407,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
}
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,
) (
selectedRule adapter.Rule, selectedRuleIndex int,
@@ -591,8 +610,16 @@ match:
actionType := currentRule.Action().Type()
if actionType == C.RuleActionTypeRoute ||
actionType == C.RuleActionTypeReject ||
actionType == C.RuleActionTypeHijackDNS ||
actionType == C.RuleActionTypeBypass {
actionType == C.RuleActionTypeHijackDNS {
selectedRule = currentRule
selectedRuleIndex = currentRuleIndex
break match
}
if actionType == C.RuleActionTypeBypass {
bypassAction := currentRule.Action().(*R.RuleActionBypass)
if !supportBypass && bypassAction.Outbound == "" {
continue match
}
selectedRule = currentRule
selectedRuleIndex = currentRuleIndex
break match

View File

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