Sfoglia il codice sorgente

Update bypass action behavior for auto redirect

世界 4 giorni fa
parent
commit
eaee6bc493

+ 1 - 1
adapter/router.go

@@ -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

+ 7 - 7
docs/configuration/route/rule_action.md

@@ -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
 #### 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.

+ 4 - 6
docs/configuration/route/rule_action.zh.md

@@ -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
 #### outbound
 
 
-==必填==
-
 目标出站的标签。
 目标出站的标签。
 
 
+如果未指定,规则仅在来自 auto redirect 的[预匹配](/configuration/shared/pre-match/)中匹配,在其他场景中将被跳过。
+
 #### route-options 字段
 #### route-options 字段
 
 
 参阅下方的 `route-options` 字段。
 参阅下方的 `route-options` 字段。

+ 11 - 0
docs/configuration/shared/pre-match.md

@@ -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.

+ 10 - 0
docs/configuration/shared/pre-match.zh.md

@@ -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)。

+ 0 - 3
option/rule_action.go

@@ -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
 }
 }
 
 

+ 1 - 1
protocol/tailscale/endpoint.go

@@ -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):

+ 2 - 2
protocol/tun/inbound.go

@@ -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):

+ 1 - 1
protocol/wireguard/endpoint.go

@@ -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):

+ 35 - 8
route/route.go

@@ -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) {
-	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 {
 	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.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
 			selectedRule = currentRule
 			selectedRuleIndex = currentRuleIndex
 			selectedRuleIndex = currentRuleIndex
 			break match
 			break match

+ 3 - 0
route/rule/rule_action.go

@@ -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()...)