mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Add pre-match support for auto redirect
This commit is contained in:
@@ -30,6 +30,7 @@ const (
|
|||||||
RuleActionTypeRoute = "route"
|
RuleActionTypeRoute = "route"
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
RuleActionTypeRouteOptions = "route-options"
|
||||||
RuleActionTypeDirect = "direct"
|
RuleActionTypeDirect = "direct"
|
||||||
|
RuleActionTypeBypass = "bypass"
|
||||||
RuleActionTypeReject = "reject"
|
RuleActionTypeReject = "reject"
|
||||||
RuleActionTypeHijackDNS = "hijack-dns"
|
RuleActionTypeHijackDNS = "hijack-dns"
|
||||||
RuleActionTypeSniff = "sniff"
|
RuleActionTypeSniff = "sniff"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! 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)
|
:material-plus: [exclude_mptcp](#exclude_mptcp)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
@@ -67,6 +69,8 @@ icon: material/new-box
|
|||||||
"auto_redirect": true,
|
"auto_redirect": true,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
|
"auto_redirect_reset_mark": "0x2025",
|
||||||
|
"auto_redirect_nfqueue": 100,
|
||||||
"exclude_mptcp": false,
|
"exclude_mptcp": false,
|
||||||
"loopback_address": [
|
"loopback_address": [
|
||||||
"10.7.0.1"
|
"10.7.0.1"
|
||||||
@@ -283,6 +287,22 @@ Connection output mark used by `auto_redirect`.
|
|||||||
|
|
||||||
`0x2024` is used by default.
|
`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
|
#### exclude_mptcp
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! 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)
|
:material-plus: [exclude_mptcp](#exclude_mptcp)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
@@ -67,6 +69,8 @@ icon: material/new-box
|
|||||||
"auto_redirect": true,
|
"auto_redirect": true,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
|
"auto_redirect_reset_mark": "0x2025",
|
||||||
|
"auto_redirect_nfqueue": 100,
|
||||||
"exclude_mptcp": false,
|
"exclude_mptcp": false,
|
||||||
"loopback_address": [
|
"loopback_address": [
|
||||||
"10.7.0.1"
|
"10.7.0.1"
|
||||||
@@ -282,6 +286,22 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
默认使用 `0x2024`。
|
默认使用 `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
|
#### exclude_mptcp
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [bypass](#bypass)
|
||||||
:material-alert: [reject](#reject)
|
:material-alert: [reject](#reject)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
@@ -44,6 +45,40 @@ Tag of target outbound.
|
|||||||
|
|
||||||
See `route-options` fields below.
|
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
|
### reject
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [bypass](#bypass)
|
||||||
:material-alert: [reject](#reject)
|
:material-alert: [reject](#reject)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
@@ -40,6 +41,39 @@ icon: material/new-box
|
|||||||
|
|
||||||
参阅下方的 `route-options` 字段。
|
参阅下方的 `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
|
### reject
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|||||||
39
docs/configuration/shared/pre-match.md
Normal file
39
docs/configuration/shared/pre-match.md
Normal 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.
|
||||||
37
docs/configuration/shared/pre-match.zh.md
Normal file
37
docs/configuration/shared/pre-match.zh.md
Normal 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
3
go.mod
@@ -38,7 +38,7 @@ require (
|
|||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
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/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
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
|
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/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
|
||||||
github.com/ebitengine/purego v0.9.1 // 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/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gaissmai/bart v0.18.0 // indirect
|
github.com/gaissmai/bart v0.18.0 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -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/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 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
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=
|
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-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 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
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.20251226064455-a850c4f8a1c8 h1:aIgk6YzS/7fNm92CycFWzithdwIc+NAwXGHAJce1dyM=
|
||||||
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/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 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
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=
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ nav:
|
|||||||
- Dial Fields: configuration/shared/dial.md
|
- Dial Fields: configuration/shared/dial.md
|
||||||
- TLS: configuration/shared/tls.md
|
- TLS: configuration/shared/tls.md
|
||||||
- DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
|
- DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
|
||||||
|
- Pre-match: configuration/shared/pre-match.md
|
||||||
- Multiplex: configuration/shared/multiplex.md
|
- Multiplex: configuration/shared/multiplex.md
|
||||||
- V2Ray Transport: configuration/shared/v2ray-transport.md
|
- V2Ray Transport: configuration/shared/v2ray-transport.md
|
||||||
- UDP over TCP: configuration/shared/udp-over-tcp.md
|
- UDP over TCP: configuration/shared/udp-over-tcp.md
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type _RuleAction struct {
|
|||||||
RouteOptions RouteActionOptions `json:"-"`
|
RouteOptions RouteActionOptions `json:"-"`
|
||||||
RouteOptionsOptions RouteOptionsActionOptions `json:"-"`
|
RouteOptionsOptions RouteOptionsActionOptions `json:"-"`
|
||||||
DirectOptions DirectActionOptions `json:"-"`
|
DirectOptions DirectActionOptions `json:"-"`
|
||||||
|
BypassOptions RouteActionOptions `json:"-"`
|
||||||
RejectOptions RejectActionOptions `json:"-"`
|
RejectOptions RejectActionOptions `json:"-"`
|
||||||
SniffOptions RouteActionSniff `json:"-"`
|
SniffOptions RouteActionSniff `json:"-"`
|
||||||
ResolveOptions RouteActionResolve `json:"-"`
|
ResolveOptions RouteActionResolve `json:"-"`
|
||||||
@@ -38,6 +39,8 @@ func (r RuleAction) MarshalJSON() ([]byte, error) {
|
|||||||
v = r.RouteOptionsOptions
|
v = r.RouteOptionsOptions
|
||||||
case C.RuleActionTypeDirect:
|
case C.RuleActionTypeDirect:
|
||||||
v = r.DirectOptions
|
v = r.DirectOptions
|
||||||
|
case C.RuleActionTypeBypass:
|
||||||
|
v = r.BypassOptions
|
||||||
case C.RuleActionTypeReject:
|
case C.RuleActionTypeReject:
|
||||||
v = r.RejectOptions
|
v = r.RejectOptions
|
||||||
case C.RuleActionTypeHijackDNS:
|
case C.RuleActionTypeHijackDNS:
|
||||||
@@ -69,6 +72,8 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
|
|||||||
v = &r.RouteOptionsOptions
|
v = &r.RouteOptionsOptions
|
||||||
case C.RuleActionTypeDirect:
|
case C.RuleActionTypeDirect:
|
||||||
v = &r.DirectOptions
|
v = &r.DirectOptions
|
||||||
|
case C.RuleActionTypeBypass:
|
||||||
|
v = &r.BypassOptions
|
||||||
case C.RuleActionTypeReject:
|
case C.RuleActionTypeReject:
|
||||||
v = &r.RejectOptions
|
v = &r.RejectOptions
|
||||||
case C.RuleActionTypeHijackDNS:
|
case C.RuleActionTypeHijackDNS:
|
||||||
@@ -84,7 +89,14 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
|
|||||||
// check unknown fields
|
// check unknown fields
|
||||||
return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{})
|
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 {
|
type _DNSRuleAction struct {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ type TunInboundOptions struct {
|
|||||||
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||||
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
||||||
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_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"`
|
ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"`
|
||||||
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
|
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
|
||||||
StrictRoute bool `json:"strict_route,omitempty"`
|
StrictRoute bool `json:"strict_route,omitempty"`
|
||||||
|
|||||||
@@ -481,8 +481,15 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
|
|||||||
Destination: destination,
|
Destination: destination,
|
||||||
}, routeContext, timeout)
|
}, routeContext, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !rule.IsRejected(err) {
|
switch {
|
||||||
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
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
|
return routeDestination, err
|
||||||
|
|||||||
@@ -182,6 +182,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
if outputMark == 0 {
|
if outputMark == 0 {
|
||||||
outputMark = tun.DefaultAutoRedirectOutputMark
|
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)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
||||||
inbound := &Inbound{
|
inbound := &Inbound{
|
||||||
@@ -202,6 +210,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
IPRoute2RuleIndex: ruleIndex,
|
IPRoute2RuleIndex: ruleIndex,
|
||||||
AutoRedirectInputMark: inputMark,
|
AutoRedirectInputMark: inputMark,
|
||||||
AutoRedirectOutputMark: outputMark,
|
AutoRedirectOutputMark: outputMark,
|
||||||
|
AutoRedirectResetMark: resetMark,
|
||||||
|
AutoRedirectNFQueue: nfQueue,
|
||||||
ExcludeMPTCP: options.ExcludeMPTCP,
|
ExcludeMPTCP: options.ExcludeMPTCP,
|
||||||
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
|
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
|
||||||
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
|
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,
|
InboundOptions: t.inboundOptions,
|
||||||
}, routeContext, timeout)
|
}, routeContext, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !rule.IsRejected(err) {
|
switch {
|
||||||
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
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
|
return routeDestination, err
|
||||||
@@ -509,6 +526,37 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
|
|
||||||
type autoRedirectHandler Inbound
|
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) {
|
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
var metadata adapter.InboundContext
|
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.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
t.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -142,8 +142,15 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
|
|||||||
Destination: destination,
|
Destination: destination,
|
||||||
}, routeContext, timeout)
|
}, routeContext, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !rule.IsRejected(err) {
|
switch {
|
||||||
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
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
|
return routeDestination, err
|
||||||
|
|||||||
@@ -113,6 +113,17 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
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:
|
||||||
|
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:
|
case *R.RuleActionReject:
|
||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
if action.Method == C.RuleActionRejectMethodReply {
|
if action.Method == C.RuleActionRejectMethodReply {
|
||||||
@@ -231,6 +242,17 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
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:
|
||||||
|
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:
|
case *R.RuleActionReject:
|
||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
if action.Method == C.RuleActionRejectMethodReply {
|
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())
|
return nil, action.Error(context.Background())
|
||||||
|
case *R.RuleActionBypass:
|
||||||
|
return nil, &R.BypassedError{Cause: tun.ErrBypass}
|
||||||
case *R.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
if routeContext == nil {
|
if routeContext == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -567,7 +591,8 @@ 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
|
selectedRule = currentRule
|
||||||
selectedRuleIndex = currentRuleIndex
|
selectedRuleIndex = currentRuleIndex
|
||||||
break match
|
break match
|
||||||
|
|||||||
@@ -56,6 +56,21 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
|||||||
TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay),
|
TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay),
|
||||||
TLSRecordFragment: action.RouteOptionsOptions.TLSRecordFragment,
|
TLSRecordFragment: action.RouteOptionsOptions.TLSRecordFragment,
|
||||||
}, nil
|
}, 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:
|
case C.RuleActionTypeDirect:
|
||||||
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false)
|
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,6 +173,22 @@ func (r *RuleActionRoute) String() string {
|
|||||||
return F.ToString("route(", strings.Join(descriptions, ","), ")")
|
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 {
|
type RuleActionRouteOptions struct {
|
||||||
OverrideAddress M.Socksaddr
|
OverrideAddress M.Socksaddr
|
||||||
OverridePort uint16
|
OverridePort uint16
|
||||||
@@ -301,6 +332,23 @@ func IsRejected(err error) bool {
|
|||||||
return errors.As(err, &rejected)
|
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 {
|
type RuleActionReject struct {
|
||||||
Method string
|
Method string
|
||||||
NoDrop bool
|
NoDrop bool
|
||||||
|
|||||||
Reference in New Issue
Block a user