From 8ef771652ceb171debd7b40b9ff466472508db7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:30:25 +0800 Subject: [PATCH] Add `network_[type/is_expensive/is_constrained]` rule items --- .../convertor/adguard/convertor_test.go | 7 +- common/srs/binary.go | 30 ++ constant/rule.go | 3 +- docs/configuration/dns/rule.md | 43 ++- docs/configuration/dns/rule.zh.md | 91 ++++-- docs/configuration/route/rule.md | 43 ++- docs/configuration/route/rule.zh.md | 50 +++- docs/configuration/rule-set/adguard.zh.md | 70 +++++ docs/configuration/rule-set/headless-rule.md | 48 ++++ .../rule-set/headless-rule.zh.md | 258 ++++++++++++++++++ docs/configuration/rule-set/index.md | 2 +- docs/configuration/rule-set/index.zh.md | 117 ++++++++ docs/configuration/rule-set/source-format.md | 21 +- .../rule-set/source-format.zh.md | 46 ++++ option/rule.go | 3 + option/rule_dns.go | 3 + option/rule_set.go | 47 ++-- route/rule/rule_default.go | 15 + route/rule/rule_dns.go | 15 + route/rule/rule_headless.go | 27 +- .../rule/rule_item_network_is_constrained.go | 29 ++ route/rule/rule_item_network_is_expensive.go | 29 ++ route/rule/rule_item_network_type.go | 39 +++ 23 files changed, 968 insertions(+), 68 deletions(-) create mode 100644 docs/configuration/rule-set/adguard.zh.md create mode 100644 docs/configuration/rule-set/headless-rule.zh.md create mode 100644 docs/configuration/rule-set/index.zh.md create mode 100644 docs/configuration/rule-set/source-format.zh.md create mode 100644 route/rule/rule_item_network_is_constrained.go create mode 100644 route/rule/rule_item_network_is_expensive.go create mode 100644 route/rule/rule_item_network_type.go diff --git a/cmd/sing-box/internal/convertor/adguard/convertor_test.go b/cmd/sing-box/internal/convertor/adguard/convertor_test.go index 6098485c..212c2170 100644 --- a/cmd/sing-box/internal/convertor/adguard/convertor_test.go +++ b/cmd/sing-box/internal/convertor/adguard/convertor_test.go @@ -1,6 +1,7 @@ package adguard import ( + "context" "strings" "testing" @@ -26,7 +27,7 @@ example.arpa `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "example.org", @@ -85,7 +86,7 @@ func TestHosts(t *testing.T) { `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "google.com", @@ -115,7 +116,7 @@ www.example.org `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "example.com", diff --git a/common/srs/binary.go b/common/srs/binary.go index dd670fc8..fbed78ad 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -38,6 +38,9 @@ const ( ruleItemWIFIBSSID ruleItemAdGuardDomain ruleItemProcessPathRegex + ruleItemNetworkType + ruleItemNetworkIsExpensive + ruleItemNetworkIsConstrained ruleItemFinal uint8 = 0xFF ) @@ -222,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea return } rule.AdGuardDomainMatcher = matcher + case ruleItemNetworkType: + rule.NetworkType, err = readRuleItemString(reader) + case ruleItemNetworkIsExpensive: + rule.NetworkIsExpensive = true + case ruleItemNetworkIsConstrained: + rule.NetworkIsConstrained = true case ruleItemFinal: err = binary.Read(reader, binary.BigEndian, &rule.Invert) return @@ -336,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen return err } } + if len(rule.NetworkType) > 0 { + if generateVersion < C.RuleSetVersion3 { + return E.New("network_type rule item is only supported in version 3 or later") + } + err = writeRuleItemString(writer, ruleItemNetworkType, rule.NetworkType) + if err != nil { + return err + } + } + if rule.NetworkIsExpensive { + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive) + if err != nil { + return err + } + } + if rule.NetworkIsConstrained { + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained) + if err != nil { + return err + } + } if len(rule.WIFISSID) > 0 { err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) if err != nil { diff --git a/constant/rule.go b/constant/rule.go index b1f91c60..c4a77838 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -21,7 +21,8 @@ const ( const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 - RuleSetVersionCurrent = RuleSetVersion2 + RuleSetVersion3 + RuleSetVersionCurrent = RuleSetVersion3 ) const ( diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 5cb24e81..1f04b299 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -8,7 +8,10 @@ icon: material/new-box :material-alert: [server](#server) :material-alert: [disable_cache](#disable_cache) :material-alert: [rewrite_ttl](#rewrite_ttl) - :material-alert: [client_subnet](#client_subnet) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "Changes in sing-box 1.10.0" @@ -125,6 +128,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -310,6 +318,39 @@ Match user id. Match Clash mode. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 205b01ae..e904f8cd 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -2,6 +2,17 @@ icon: material/new-box --- +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + !!! quote "sing-box 1.10.0 中的更改" :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) @@ -117,6 +128,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -135,17 +151,15 @@ icon: material/new-box "outbound": [ "direct" ], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -304,6 +318,39 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配 Clash 模式。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + #### wifi_ssid !!! quote "" @@ -352,29 +399,35 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 `any` 可作为值用于匹配任意出站。 -#### server +#### action ==必填== -目标 DNS 服务器的标签。 +参阅 [规则动作](../rule_action/)。 + +#### server + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### disable_cache -在此查询中禁用缓存。 +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### rewrite_ttl -重写 DNS 回应中的 TTL。 +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### client_subnet -!!! question "自 sing-box 1.9.0 起" +!!! failure "已在 sing-box 1.11.0 废弃" -默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 - -如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 - -将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + 已移动到 [DNS 规则动作](../rule_action#route). ### 地址筛选字段 @@ -420,8 +473,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### mode +==必填== + `and` 或 `or` #### rules -包括的规则。 +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index fe40d565..43954a78 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [action](#action) - :material-alert: [outbound](#outbound) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "Changes in sing-box 1.10.0" @@ -120,6 +123,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -322,6 +330,39 @@ Match user id. Match Clash mode. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 316339f6..e5c5f017 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "sing-box 1.11.0 中的更改" :material-plus: [action](#action) - :material-alert: [outbound](#outbound) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "sing-box 1.10.0 中的更改" @@ -13,7 +16,6 @@ icon: material/new-box :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [process_path_regex](#process_path_regex) - !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -118,6 +120,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -153,7 +160,7 @@ icon: material/new-box 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 -### Default Fields +### 默认字段 !!! note "" @@ -320,6 +327,39 @@ icon: material/new-box 匹配 Clash 模式。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + #### wifi_ssid !!! quote "" @@ -366,13 +406,13 @@ icon: material/new-box ==必填== -参阅 [规则行动](../rule_action/)。 +参阅 [规则动作](../rule_action/)。 #### outbound !!! failure "已在 sing-box 1.11.0 废弃" - 已移动到 [规则行动](../rule_action#route). + 已移动到 [规则动作](../rule_action#route). ### 逻辑字段 diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md new file mode 100644 index 00000000..b08ab34b --- /dev/null +++ b/docs/configuration/rule-set/adguard.zh.md @@ -0,0 +1,70 @@ +--- +icon: material/new-box +--- + +# AdGuard DNS Filter + +!!! question "自 sing-box 1.10.0 起" + +sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, +目前只有 AdGuard DNS Filter。 + +这些格式不直接作为源格式支持, +而是需要将它们转换为二进制规则集。 + +## 转换 + +使用 `sing-box rule-set convert --type adguard [--output .srs] .txt` 以转换为二进制规则集。 + +## 性能 + +AdGuard 将所有规则保存在内存中并按顺序匹配, +而 sing-box 选择高性能和较小的内存使用量。 +作为权衡,您无法知道匹配了哪个规则项。 + +## 兼容性 + +[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter) +中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list) +中列出的规则集中的规则均受支持。 + +## 支持的格式 + +### AdGuard Filter + +#### 基本规则语法 + +| 语法 | 支持 | +|--------|------------------| +| `@@` | :material-check: | +| `\|\|` | :material-check: | +| `\|` | :material-check: | +| `^` | :material-check: | +| `*` | :material-check: | + +#### 主机语法 + +| 语法 | 示例 | 支持 | +|-------------|--------------------------|--------------------------| +| Scheme | `https://` | :material-alert: Ignored | +| Domain Host | `example.org` | :material-check: | +| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: | +| Regexp | `/regexp/` | :material-check: | +| Port | `example.org:80` | :material-close: | +| Path | `example.org/path/ad.js` | :material-close: | + +#### 描述符语法 + +| 描述符 | 支持 | +|-----------------------|--------------------------| +| `$important` | :material-check: | +| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored | +| 任何其他描述符 | :material-close: | + +### Hosts + +只有 IP 地址为 `0.0.0.0` 的条目将被接受。 + +### 简易 + +当所有行都是有效域时,它们被视为简单的逐行域规则, 与 hosts 一样,只匹配完全相同的域。 \ No newline at end of file diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index 891b1278..bdad22f0 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -1,3 +1,13 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + ### Structure !!! question "Since sing-box 1.8.0" @@ -63,6 +73,11 @@ "package_name": [ "com.termux" ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -177,6 +192,39 @@ Match process path using regular expression. Match android package name. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/headless-rule.zh.md b/docs/configuration/rule-set/headless-rule.zh.md new file mode 100644 index 00000000..c5281504 --- /dev/null +++ b/docs/configuration/rule-set/headless-rule.zh.md @@ -0,0 +1,258 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_type](#network_type) + :material-alert: [network_is_expensive](#network_is_expensive) + :material-alert: [network_is_constrained](#network_is_constrained) + +### 结构 + +!!! question "自 sing-box 1.8.0 起" + +```json +{ + "rules": [ + { + "query_type": [ + "A", + "HTTPS", + 32768 + ], + "network": [ + "tcp" + ], + "domain": [ + "test.com" + ], + "domain_suffix": [ + ".cn" + ], + "domain_keyword": [ + "test" + ], + "domain_regex": [ + "^stun\\..+" + ], + "source_ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "source_port": [ + 12345 + ], + "source_port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "port": [ + 80, + 443 + ], + "port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "process_name": [ + "curl" + ], + "process_path": [ + "/usr/bin/curl" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], + "package_name": [ + "com.termux" + ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "wifi_ssid": [ + "My WIFI" + ], + "wifi_bssid": [ + "00:00:00:00:00:00" + ], + "invert": false + }, + { + "type": "logical", + "mode": "and", + "rules": [], + "invert": false + } + ] +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 + +### Default Fields + +!!! note "" + + 默认规则使用以下匹配逻辑: + (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) && + (`port` || `port_range`) && + (`source_port` || `source_port_range`) && + `other fields` + +#### query_type + +DNS 查询类型。值可以为整数或者类型名称字符串。 + +#### network + +`tcp` 或 `udp`。 + +#### domain + +匹配完整域名。 + +#### domain_suffix + +匹配域名后缀。 + +#### domain_keyword + +匹配域名关键字。 + +#### domain_regex + +匹配域名正则表达式。 + +#### source_ip_cidr + +匹配源 IP CIDR。 + +#### ip_cidr + +匹配 IP CIDR。 + +#### source_port + +匹配源端口。 + +#### source_port_range + +匹配源端口范围。 + +#### port + +匹配端口。 + +#### port_range + +匹配端口范围。 + +#### process_name + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS。 + +匹配进程名称。 + +#### process_path + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配进程路径。 + +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + +#### package_name + +匹配 Android 应用包名。 + +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + +#### wifi_ssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配 WiFi SSID。 + +#### wifi_bssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +#### invert + +反选匹配结果。 + +### 逻辑字段 + +#### type + +`logical` + +#### mode + +==必填== + +`and` 或 `or` + +#### rules + +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index dfe71d9e..bed3fb54 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -74,7 +74,7 @@ Tag of rule-set. ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). ### Local or Remote Fields diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md new file mode 100644 index 00000000..083c06bd --- /dev/null +++ b/docs/configuration/rule-set/index.zh.md @@ -0,0 +1,117 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: `type: inline` + +# 规则集 + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +=== "内联" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "type": "inline", // 可选 + "tag": "", + "rules": [] + } + ``` + +=== "本地文件" + + ```json + { + "type": "local", + "tag": "", + "format": "source", // or binary + "path": "" + } + ``` + +=== "远程文件" + + !!! info "" + + 远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。 + + ```json + { + "type": "remote", + "tag": "", + "format": "source", // or binary + "url": "", + "download_detour": "", // 可选 + "update_interval": "" // 可选 + } + ``` + +### 字段 + +#### type + +==必填== + +规则集类型, `local` 或 `remote`。 + +#### tag + +==必填== + +规则集的标签。 + +### 内联字段 + +!!! question "自 sing-box 1.10.0 起" + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). + +### 本地或远程字段 + +#### format + +==必填== + +规则集格式, `source` 或 `binary`。 + +### 本地字段 + +#### path + +==必填== + +!!! note "" + + 自 sing-box 1.10.0 起,文件更改时将自动重新加载。 + +规则集的文件路径。 + +### 远程字段 + +#### url + +==必填== + +规则集的下载 URL。 + +#### download_detour + +用于下载规则集的出站的标签。 + +如果为空,将使用默认出站。 + +#### update_interval + +规则集的更新间隔。 + +默认使用 `1d`。 diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 8c46cbf0..43e8ea45 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -4,6 +4,10 @@ icon: material/new-box # Source Format +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: version `3` + !!! quote "Changes in sing-box 1.10.0" :material-plus: version `2` @@ -14,7 +18,7 @@ icon: material/new-box ```json { - "version": 2, + "version": 3, "rules": [] } ``` @@ -29,19 +33,14 @@ Use `sing-box rule-set compile [--output .srs] .json` to c ==Required== -Version of rule-set, one of `1` or `2`. +Version of rule-set. -* 1: Initial rule-set version, since sing-box 1.8.0. -* 2: Optimized memory usages of `domain_suffix` rules. - -The new rule-set version `2` does not make any changes to the format, only affecting `binary` rule-sets compiled by command `rule-set compile` - -Since 1.10.0, the optimization is always applied to `source` rule-sets even if version is set to `1`. - -It is recommended to upgrade to `2` after sing-box 1.10.0 becomes a stable version. +* 1: sing-box 1.8.0: Initial rule-set version. +* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets. +* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items. #### rules ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md new file mode 100644 index 00000000..0e8fc0cb --- /dev/null +++ b/docs/configuration/rule-set/source-format.zh.md @@ -0,0 +1,46 @@ +--- +icon: material/new-box +--- + +# 源文件格式 + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: version `3` + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: version `2` + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +```json +{ + "version": 3, + "rules": [] +} +``` + +### 编译 + +使用 `sing-box rule-set compile [--output .srs] .json` 以编译源文件为二进制规则集。 + +### 字段 + +#### version + +==必填== + +规则集版本。 + +* 1: sing-box 1.8.0: 初始规则集版本。 +* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。 +* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。 + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). diff --git a/option/rule.go b/option/rule.go index d5ff9925..82a53f75 100644 --- a/option/rule.go +++ b/option/rule.go @@ -95,6 +95,9 @@ type RawDefaultRule struct { User badoption.Listable[string] `json:"user,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[string] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 6c9755fd..33dc816c 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -97,6 +97,9 @@ type RawDefaultDNSRule struct { UserID badoption.Listable[int32] `json:"user_id,omitempty"` Outbound badoption.Listable[string] `json:"outbound,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[string] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index fbb527ca..9ccca475 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -146,25 +146,28 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + NetworkType badoption.Listable[string] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` @@ -200,7 +203,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: v = r.Options default: return nil, E.New("unknown rule-set version: ", r.Version) @@ -215,7 +218,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { } var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: v = &r.Options case 0: return E.New("missing rule-set version") @@ -231,7 +234,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: default: return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 12d9e96a..a5d47b44 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -223,6 +223,21 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 1ec652b2..4ac08a5a 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -220,6 +220,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 99488b20..7f2dc5fc 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -140,18 +140,33 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - if len(options.WIFISSID) > 0 { - if networkManager != nil { - item := NewWIFISSIDItem(networkManager, options.WIFISSID) + if networkManager != nil { + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - } - if len(options.WIFIBSSID) > 0 { - if networkManager != nil { + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFISSID) > 0 { + item := NewWIFISSIDItem(networkManager, options.WIFISSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + + } + if len(options.WIFIBSSID) > 0 { item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) + } } if len(options.AdGuardDomain) > 0 { diff --git a/route/rule/rule_item_network_is_constrained.go b/route/rule/rule_item_network_is_constrained.go new file mode 100644 index 00000000..e0368b75 --- /dev/null +++ b/route/rule/rule_item_network_is_constrained.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsConstrainedItem)(nil) + +type NetworkIsConstrainedItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsConstrainedItem(networkManager adapter.NetworkManager) *NetworkIsConstrainedItem { + return &NetworkIsConstrainedItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsConstrainedItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Constrained +} + +func (r *NetworkIsConstrainedItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_is_expensive.go b/route/rule/rule_item_network_is_expensive.go new file mode 100644 index 00000000..83e4f96f --- /dev/null +++ b/route/rule/rule_item_network_is_expensive.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsExpensiveItem)(nil) + +type NetworkIsExpensiveItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsExpensiveItem(networkManager adapter.NetworkManager) *NetworkIsExpensiveItem { + return &NetworkIsExpensiveItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsExpensiveItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Expensive +} + +func (r *NetworkIsExpensiveItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_type.go b/route/rule/rule_item_network_type.go new file mode 100644 index 00000000..8ebdb25e --- /dev/null +++ b/route/rule/rule_item_network_type.go @@ -0,0 +1,39 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*NetworkTypeItem)(nil) + +type NetworkTypeItem struct { + networkManager adapter.NetworkManager + networkType []string +} + +func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem { + return &NetworkTypeItem{ + networkManager: networkManager, + networkType: networkType, + } +} + +func (r *NetworkTypeItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return common.Contains(r.networkType, networkInterface.Type) +} + +func (r *NetworkTypeItem) String() string { + if len(r.networkType) == 1 { + return F.ToString("network_type=", r.networkType[0]) + } else { + return F.ToString("network_type=", "["+strings.Join(F.MapToString(r.networkType), " ")+"]") + } +}