Update bypass action behavior for auto redirect
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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` 字段。
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)。
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()...)
|
||||||
|
|||||||
Reference in New Issue
Block a user