Add pre-match support for auto redirect

This commit is contained in:
世界
2025-12-26 15:52:28 +08:00
parent a34868468f
commit 78b4eac974
17 changed files with 354 additions and 11 deletions

View File

@@ -30,6 +30,7 @@ const (
RuleActionTypeRoute = "route"
RuleActionTypeRouteOptions = "route-options"
RuleActionTypeDirect = "direct"
RuleActionTypeBypass = "bypass"
RuleActionTypeReject = "reject"
RuleActionTypeHijackDNS = "hijack-dns"
RuleActionTypeSniff = "sniff"

View File

@@ -4,6 +4,8 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
:material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue)
:material-plus: [exclude_mptcp](#exclude_mptcp)
!!! quote "Changes in sing-box 1.12.0"
@@ -67,6 +69,8 @@ icon: material/new-box
"auto_redirect": true,
"auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024",
"auto_redirect_reset_mark": "0x2025",
"auto_redirect_nfqueue": 100,
"exclude_mptcp": false,
"loopback_address": [
"10.7.0.1"
@@ -283,6 +287,22 @@ Connection output mark used by `auto_redirect`.
`0x2024` is used by default.
#### auto_redirect_reset_mark
!!! question "Since sing-box 1.13.0"
Connection reset mark used by `auto_redirect` pre-matching.
`0x2025` is used by default.
#### auto_redirect_nfqueue
!!! question "Since sing-box 1.13.0"
NFQueue number used by `auto_redirect` pre-matching.
`100` is used by default.
#### exclude_mptcp
!!! question "Since sing-box 1.13.0"

View File

@@ -4,6 +4,8 @@ icon: material/new-box
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
:material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue)
:material-plus: [exclude_mptcp](#exclude_mptcp)
!!! quote "sing-box 1.12.0 中的更改"
@@ -67,6 +69,8 @@ icon: material/new-box
"auto_redirect": true,
"auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024",
"auto_redirect_reset_mark": "0x2025",
"auto_redirect_nfqueue": 100,
"exclude_mptcp": false,
"loopback_address": [
"10.7.0.1"
@@ -282,6 +286,22 @@ tun 接口的 IPv6 前缀。
默认使用 `0x2024`
#### auto_redirect_reset_mark
!!! question "自 sing-box 1.13.0 起"
`auto_redirect` 预匹配使用的连接重置标记。
默认使用 `0x2025`
#### auto_redirect_nfqueue
!!! question "自 sing-box 1.13.0 起"
`auto_redirect` 预匹配使用的 NFQueue 编号。
默认使用 `100`
#### exclude_mptcp
!!! question "自 sing-box 1.13.0 起"

View File

@@ -4,6 +4,7 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [bypass](#bypass)
:material-alert: [reject](#reject)
!!! quote "Changes in sing-box 1.12.0"
@@ -44,6 +45,40 @@ Tag of target outbound.
See `route-options` fields below.
### bypass
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux with `auto_redirect` enabled.
```json
{
"action": "bypass",
"outbound": "",
... // route-options Fields
}
```
`bypass` routes connection to the specified outbound.
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`.
#### outbound
==Required==
Tag of target outbound.
#### route-options Fields
See `route-options` fields below.
### reject
!!! quote "Changes in sing-box 1.13.0"

View File

@@ -4,6 +4,7 @@ icon: material/new-box
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [bypass](#bypass)
:material-alert: [reject](#reject)
!!! quote "sing-box 1.12.0 中的更改"
@@ -40,6 +41,39 @@ icon: material/new-box
参阅下方的 `route-options` 字段。
### bypass
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux且需要启用 `auto_redirect`
```json
{
"action": "bypass",
"outbound": "",
... // route-options 字段
}
```
`bypass` 将连接路由到指定出站。
对于[预匹配](/configuration/shared/pre-match/)中的 tun 连接,连接将在内核层面绕过 sing-box 直接连接。
对于非 tun 连接和已建立的连接,行为与 `route` 相同。
#### outbound
==必填==
目标出站的标签。
#### route-options 字段
参阅下方的 `route-options` 字段。
### reject
!!! quote "sing-box 1.13.0 中的更改"

View File

@@ -0,0 +1,39 @@
---
icon: material/new-box
---
# Pre-match
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [bypass](#bypass)
Pre-match is rule matching that runs before the connection is established.
### How it works
When TUN receives a connection request, the connection has not yet been established,
so no connection data can be read. In this phase, sing-box runs the routing rules in pre-match mode.
Since connection data is unavailable, only actions that do not require connection data can be executed.
When a rule matches an action that requires an established connection, pre-match stops at that rule.
### Supported actions
#### reject
Reject with TCP RST / ICMP unreachable.
#### route
Route ICMP connections to the specified outbound for direct reply.
#### bypass
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux with `auto_redirect` enabled.
Bypass sing-box and connect directly at kernel level.

View File

@@ -0,0 +1,37 @@
---
icon: material/new-box
---
# 预匹配
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [bypass](#bypass)
预匹配是在连接建立之前运行的规则匹配。
### 工作原理
当 TUN 收到连接请求时连接尚未建立因此无法读取连接数据。在此阶段sing-box 在预匹配模式下运行路由规则。
由于连接数据不可用,只有不需要连接数据的动作才能执行。当规则匹配到需要已建立连接的动作时,预匹配将在该规则处停止。
### 支持的动作
#### reject
以 TCP RST / ICMP 不可达拒绝。
#### route
将 ICMP 连接路由到指定出站以直接回复。
#### bypass
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux且需要启用 `auto_redirect`
在内核层面绕过 sing-box 直接连接。

3
go.mod
View File

@@ -38,7 +38,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8
@@ -73,6 +73,7 @@ require (
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/florianl/go-nfqueue/v2 v2.0.2 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaissmai/bart v0.18.0 // indirect

6
go.sum
View File

@@ -38,6 +38,8 @@ github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbY
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=
github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
@@ -214,8 +216,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e h1:ZEv+9vy7vC1vbr3LfwZGx3JAOkl/w4+hnGamHw4W36M=
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg=
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8 h1:aIgk6YzS/7fNm92CycFWzithdwIc+NAwXGHAJce1dyM=
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=

View File

@@ -122,6 +122,7 @@ nav:
- Dial Fields: configuration/shared/dial.md
- TLS: configuration/shared/tls.md
- DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
- Pre-match: configuration/shared/pre-match.md
- Multiplex: configuration/shared/multiplex.md
- V2Ray Transport: configuration/shared/v2ray-transport.md
- UDP over TCP: configuration/shared/udp-over-tcp.md

View File

@@ -18,6 +18,7 @@ type _RuleAction struct {
RouteOptions RouteActionOptions `json:"-"`
RouteOptionsOptions RouteOptionsActionOptions `json:"-"`
DirectOptions DirectActionOptions `json:"-"`
BypassOptions RouteActionOptions `json:"-"`
RejectOptions RejectActionOptions `json:"-"`
SniffOptions RouteActionSniff `json:"-"`
ResolveOptions RouteActionResolve `json:"-"`
@@ -38,6 +39,8 @@ func (r RuleAction) MarshalJSON() ([]byte, error) {
v = r.RouteOptionsOptions
case C.RuleActionTypeDirect:
v = r.DirectOptions
case C.RuleActionTypeBypass:
v = r.BypassOptions
case C.RuleActionTypeReject:
v = r.RejectOptions
case C.RuleActionTypeHijackDNS:
@@ -69,6 +72,8 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
v = &r.RouteOptionsOptions
case C.RuleActionTypeDirect:
v = &r.DirectOptions
case C.RuleActionTypeBypass:
v = &r.BypassOptions
case C.RuleActionTypeReject:
v = &r.RejectOptions
case C.RuleActionTypeHijackDNS:
@@ -84,7 +89,14 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
// check unknown fields
return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{})
}
return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v)
err = badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v)
if err != nil {
return err
}
if r.Action == C.RuleActionTypeBypass && r.BypassOptions.Outbound == "" {
return E.New("missing outbound for bypass action")
}
return nil
}
type _DNSRuleAction struct {

View File

@@ -20,6 +20,8 @@ type TunInboundOptions struct {
AutoRedirect bool `json:"auto_redirect,omitempty"`
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
AutoRedirectResetMark FwMark `json:"auto_redirect_reset_mark,omitempty"`
AutoRedirectNFQueue uint16 `json:"auto_redirect_nfqueue,omitempty"`
ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"`
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`

View File

@@ -481,8 +481,15 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Destination: destination,
}, routeContext, timeout)
if err != nil {
if !rule.IsRejected(err) {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
switch {
case rule.IsBypassed(err):
err = nil
case rule.IsRejected(err):
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err

View File

@@ -182,6 +182,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if outputMark == 0 {
outputMark = tun.DefaultAutoRedirectOutputMark
}
resetMark := uint32(options.AutoRedirectResetMark)
if resetMark == 0 {
resetMark = tun.DefaultAutoRedirectResetMark
}
nfQueue := options.AutoRedirectNFQueue
if nfQueue == 0 {
nfQueue = tun.DefaultAutoRedirectNFQueue
}
networkManager := service.FromContext[adapter.NetworkManager](ctx)
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
inbound := &Inbound{
@@ -202,6 +210,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
IPRoute2RuleIndex: ruleIndex,
AutoRedirectInputMark: inputMark,
AutoRedirectOutputMark: outputMark,
AutoRedirectResetMark: resetMark,
AutoRedirectNFQueue: nfQueue,
ExcludeMPTCP: options.ExcludeMPTCP,
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
@@ -472,8 +482,15 @@ func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destinat
InboundOptions: t.inboundOptions,
}, routeContext, timeout)
if err != nil {
if !rule.IsRejected(err) {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
switch {
case rule.IsBypassed(err):
err = nil
case rule.IsRejected(err):
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err
@@ -509,6 +526,37 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
type autoRedirectHandler Inbound
func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.tag,
InboundType: C.TypeTun,
IPVersion: ipVersion,
Network: network,
Source: source,
Destination: destination,
InboundOptions: t.inboundOptions,
}, routeContext, timeout)
if err != nil {
switch {
case rule.IsBypassed(err):
t.logger.Trace("bypass ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
case rule.IsRejected(err):
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err
}
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
ctx = log.ContextWithNewID(ctx)
var metadata adapter.InboundContext
@@ -522,3 +570,7 @@ func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
t.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (t *autoRedirectHandler) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
panic("unexcepted")
}

View File

@@ -142,8 +142,15 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Destination: destination,
}, routeContext, timeout)
if err != nil {
if !rule.IsRejected(err) {
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
switch {
case rule.IsBypassed(err):
err = nil
case rule.IsRejected(err):
w.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err

View File

@@ -113,6 +113,17 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
buf.ReleaseMulti(buffers)
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
}
case *R.RuleActionBypass:
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
buf.ReleaseMulti(buffers)
return E.New("outbound not found: ", action.Outbound)
}
if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) {
buf.ReleaseMulti(buffers)
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
}
case *R.RuleActionReject:
buf.ReleaseMulti(buffers)
if action.Method == C.RuleActionRejectMethodReply {
@@ -231,6 +242,17 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
N.ReleaseMultiPacketBuffer(packetBuffers)
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
}
case *R.RuleActionBypass:
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
N.ReleaseMultiPacketBuffer(packetBuffers)
return E.New("outbound not found: ", action.Outbound)
}
if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) {
N.ReleaseMultiPacketBuffer(packetBuffers)
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
}
case *R.RuleActionReject:
N.ReleaseMultiPacketBuffer(packetBuffers)
if action.Method == C.RuleActionRejectMethodReply {
@@ -287,6 +309,8 @@ 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}
case *R.RuleActionRoute:
if routeContext == nil {
return nil, nil
@@ -567,7 +591,8 @@ match:
actionType := currentRule.Action().Type()
if actionType == C.RuleActionTypeRoute ||
actionType == C.RuleActionTypeReject ||
actionType == C.RuleActionTypeHijackDNS {
actionType == C.RuleActionTypeHijackDNS ||
actionType == C.RuleActionTypeBypass {
selectedRule = currentRule
selectedRuleIndex = currentRuleIndex
break match

View File

@@ -56,6 +56,21 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay),
TLSRecordFragment: action.RouteOptionsOptions.TLSRecordFragment,
}, nil
case C.RuleActionTypeBypass:
return &RuleActionBypass{
Outbound: action.BypassOptions.Outbound,
RuleActionRouteOptions: RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.BypassOptions.OverrideAddress, 0),
OverridePort: action.BypassOptions.OverridePort,
NetworkStrategy: (*C.NetworkStrategy)(action.BypassOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.BypassOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.BypassOptions.UDPDisableDomainUnmapping,
UDPConnect: action.BypassOptions.UDPConnect,
TLSFragment: action.BypassOptions.TLSFragment,
TLSFragmentFallbackDelay: time.Duration(action.BypassOptions.TLSFragmentFallbackDelay),
TLSRecordFragment: action.BypassOptions.TLSRecordFragment,
},
}, nil
case C.RuleActionTypeDirect:
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false)
if err != nil {
@@ -158,6 +173,22 @@ func (r *RuleActionRoute) String() string {
return F.ToString("route(", strings.Join(descriptions, ","), ")")
}
type RuleActionBypass struct {
Outbound string
RuleActionRouteOptions
}
func (r *RuleActionBypass) Type() string {
return C.RuleActionTypeBypass
}
func (r *RuleActionBypass) String() string {
var descriptions []string
descriptions = append(descriptions, r.Outbound)
descriptions = append(descriptions, r.Descriptions()...)
return F.ToString("bypass(", strings.Join(descriptions, ","), ")")
}
type RuleActionRouteOptions struct {
OverrideAddress M.Socksaddr
OverridePort uint16
@@ -301,6 +332,23 @@ func IsRejected(err error) bool {
return errors.As(err, &rejected)
}
type BypassedError struct {
Cause error
}
func (b *BypassedError) Error() string {
return "bypassed"
}
func (b *BypassedError) Unwrap() error {
return b.Cause
}
func IsBypassed(err error) bool {
var bypassed *BypassedError
return errors.As(err, &bypassed)
}
type RuleActionReject struct {
Method string
NoDrop bool