Skip to content

Commit 9bb7b2e

Browse files
committed
Update bypass action behavior for auto redirect
1 parent 9ee0c9c commit 9bb7b2e

File tree

11 files changed

+57
-29
lines changed

11 files changed

+57
-29
lines changed

adapter/router.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
type Router interface {
2222
Lifecycle
2323
ConnectionRouter
24-
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
24+
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
2525
ConnectionRouterEx
2626
RuleSet(tag string) (RuleSet, bool)
2727
Rules() []Rule

docs/configuration/route/rule_action.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,18 @@ See `route-options` fields below.
6262
}
6363
```
6464

65-
`bypass` routes connection to the specified outbound.
65+
`bypass` bypasses sing-box at the kernel level for auto redirect connections in pre-match.
6666

67-
For tun connections in [pre-match](/configuration/shared/pre-match/),
68-
the connection will bypass sing-box and connect directly at the kernel level.
69-
70-
For non-tun connections and already established connections, the behavior is the same as `route`.
67+
For non-auto-redirect connections and already established connections,
68+
the behavior is the same as `route` if `outbound` is specified.
7169

7270
#### outbound
7371

74-
==Required==
75-
7672
Tag of target outbound.
7773

74+
If not specified, the rule only matches in [pre-match](/configuration/shared/pre-match/)
75+
from auto redirect, and will be skipped in other contexts.
76+
7877
#### route-options Fields
7978

8079
See `route-options` fields below.

docs/configuration/route/rule_action.zh.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,16 @@ icon: material/new-box
5858
}
5959
```
6060

61-
`bypass` 将连接路由到指定出站
61+
`bypass` 在预匹配中为 auto redirect 连接在内核层面绕过 sing-box
6262

63-
对于[预匹配](/configuration/shared/pre-match/)中的 tun 连接,连接将在内核层面绕过 sing-box 直接连接。
64-
65-
对于非 tun 连接和已建立的连接,行为与 `route` 相同。
63+
对于非 auto redirect 连接和已建立的连接,如果指定了 `outbound`,行为与 `route` 相同。
6664

6765
#### outbound
6866

69-
==必填==
70-
7167
目标出站的标签。
7268

69+
如果未指定,规则仅在来自 auto redirect 的[预匹配](/configuration/shared/pre-match/)中匹配,在其他场景中将被跳过。
70+
7371
#### route-options 字段
7472

7573
参阅下方的 `route-options` 字段。

docs/configuration/shared/pre-match.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ Route ICMP connections to the specified outbound for direct reply.
3737
Only supported on Linux with `auto_redirect` enabled.
3838

3939
Bypass sing-box and connect directly at kernel level.
40+
41+
For all other contexts, bypass with `outbound` behaves like `route` action.

docs/configuration/shared/pre-match.zh.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ icon: material/new-box
3535
仅支持 Linux,且需要启用 `auto_redirect`。
3636

3737
在内核层面绕过 sing-box 直接连接。
38+
39+
对于其他所有场景,指定了 `outbound` 的 bypass 行为与 `route` 相同。

option/rule_action.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,6 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
9393
if err != nil {
9494
return err
9595
}
96-
if r.Action == C.RuleActionTypeBypass && r.BypassOptions.Outbound == "" {
97-
return E.New("missing outbound for bypass action")
98-
}
9996
return nil
10097
}
10198

protocol/tailscale/endpoint.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
463463
Network: network,
464464
Source: source,
465465
Destination: destination,
466-
}, routeContext, timeout)
466+
}, routeContext, timeout, false)
467467
if err != nil {
468468
switch {
469469
case rule.IsBypassed(err):

protocol/tun/inbound.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destinat
480480
Source: source,
481481
Destination: destination,
482482
InboundOptions: t.inboundOptions,
483-
}, routeContext, timeout)
483+
}, routeContext, timeout, false)
484484
if err != nil {
485485
switch {
486486
case rule.IsBypassed(err):
@@ -541,7 +541,7 @@ func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksad
541541
Source: source,
542542
Destination: destination,
543543
InboundOptions: t.inboundOptions,
544-
}, routeContext, timeout)
544+
}, routeContext, timeout, true)
545545
if err != nil {
546546
switch {
547547
case rule.IsBypassed(err):

protocol/wireguard/endpoint.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
140140
Network: network,
141141
Source: source,
142142
Destination: destination,
143-
}, routeContext, timeout)
143+
}, routeContext, timeout, false)
144144
if err != nil {
145145
switch {
146146
case rule.IsBypassed(err):

route/route.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
9595
if deadline.NeedAdditionalReadDeadline(conn) {
9696
conn = deadline.NewConn(conn)
9797
}
98-
selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil)
98+
selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, false, conn, nil)
9999
if err != nil {
100100
return err
101101
}
@@ -114,6 +114,9 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
114114
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
115115
}
116116
case *R.RuleActionBypass:
117+
if action.Outbound == "" {
118+
break
119+
}
117120
var loaded bool
118121
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
119122
if !loaded {
@@ -223,7 +226,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
223226
conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn))
224227
}*/
225228

226-
selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn)
229+
selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn)
227230
if err != nil {
228231
return err
229232
}
@@ -243,6 +246,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
243246
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
244247
}
245248
case *R.RuleActionBypass:
249+
if action.Outbound == "" {
250+
break
251+
}
246252
var loaded bool
247253
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
248254
if !loaded {
@@ -289,8 +295,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
289295
return nil
290296
}
291297

292-
func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
293-
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil)
298+
func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) {
299+
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, supportBypass, nil, nil)
294300
if err != nil {
295301
return nil, err
296302
}
@@ -310,7 +316,20 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
310316
}
311317
return nil, action.Error(context.Background())
312318
case *R.RuleActionBypass:
313-
return nil, &R.BypassedError{Cause: tun.ErrBypass}
319+
if supportBypass {
320+
return nil, &R.BypassedError{Cause: tun.ErrBypass}
321+
}
322+
if routeContext == nil {
323+
return nil, nil
324+
}
325+
outbound, loaded := r.outbound.Outbound(action.Outbound)
326+
if !loaded {
327+
return nil, E.New("outbound not found: ", action.Outbound)
328+
}
329+
if !common.Contains(outbound.Network(), metadata.Network) {
330+
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
331+
}
332+
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
314333
case *R.RuleActionRoute:
315334
if routeContext == nil {
316335
return nil, nil
@@ -388,7 +407,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
388407
}
389408

390409
func (r *Router) matchRule(
391-
ctx context.Context, metadata *adapter.InboundContext, preMatch bool,
410+
ctx context.Context, metadata *adapter.InboundContext, preMatch bool, supportBypass bool,
392411
inputConn net.Conn, inputPacketConn N.PacketConn,
393412
) (
394413
selectedRule adapter.Rule, selectedRuleIndex int,
@@ -591,8 +610,16 @@ match:
591610
actionType := currentRule.Action().Type()
592611
if actionType == C.RuleActionTypeRoute ||
593612
actionType == C.RuleActionTypeReject ||
594-
actionType == C.RuleActionTypeHijackDNS ||
595-
actionType == C.RuleActionTypeBypass {
613+
actionType == C.RuleActionTypeHijackDNS {
614+
selectedRule = currentRule
615+
selectedRuleIndex = currentRuleIndex
616+
break match
617+
}
618+
if actionType == C.RuleActionTypeBypass {
619+
bypassAction := currentRule.Action().(*R.RuleActionBypass)
620+
if !supportBypass && bypassAction.Outbound == "" {
621+
continue match
622+
}
596623
selectedRule = currentRule
597624
selectedRuleIndex = currentRuleIndex
598625
break match

0 commit comments

Comments
 (0)