Add network_[type/is_expensive/is_constrained] rule items

This commit is contained in:
世界 2024-11-11 16:30:25 +08:00
parent 49ad3b71a2
commit 0f4bfe9505
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
23 changed files with 968 additions and 68 deletions

View file

@ -1,6 +1,7 @@
package adguard package adguard
import ( import (
"context"
"strings" "strings"
"testing" "testing"
@ -26,7 +27,7 @@ example.arpa
`)) `))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, rules, 1) require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(nil, rules[0]) rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
require.NoError(t, err) require.NoError(t, err)
matchDomain := []string{ matchDomain := []string{
"example.org", "example.org",
@ -85,7 +86,7 @@ func TestHosts(t *testing.T) {
`)) `))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, rules, 1) require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(nil, rules[0]) rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
require.NoError(t, err) require.NoError(t, err)
matchDomain := []string{ matchDomain := []string{
"google.com", "google.com",
@ -115,7 +116,7 @@ www.example.org
`)) `))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, rules, 1) require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(nil, rules[0]) rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
require.NoError(t, err) require.NoError(t, err)
matchDomain := []string{ matchDomain := []string{
"example.com", "example.com",

View file

@ -38,6 +38,9 @@ const (
ruleItemWIFIBSSID ruleItemWIFIBSSID
ruleItemAdGuardDomain ruleItemAdGuardDomain
ruleItemProcessPathRegex ruleItemProcessPathRegex
ruleItemNetworkType
ruleItemNetworkIsExpensive
ruleItemNetworkIsConstrained
ruleItemFinal uint8 = 0xFF ruleItemFinal uint8 = 0xFF
) )
@ -222,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
return return
} }
rule.AdGuardDomainMatcher = matcher rule.AdGuardDomainMatcher = matcher
case ruleItemNetworkType:
rule.NetworkType, err = readRuleItemString(reader)
case ruleItemNetworkIsExpensive:
rule.NetworkIsExpensive = true
case ruleItemNetworkIsConstrained:
rule.NetworkIsConstrained = true
case ruleItemFinal: case ruleItemFinal:
err = binary.Read(reader, binary.BigEndian, &rule.Invert) err = binary.Read(reader, binary.BigEndian, &rule.Invert)
return return
@ -336,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
return err 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 { if len(rule.WIFISSID) > 0 {
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
if err != nil { if err != nil {

View file

@ -21,7 +21,8 @@ const (
const ( const (
RuleSetVersion1 = 1 + iota RuleSetVersion1 = 1 + iota
RuleSetVersion2 RuleSetVersion2
RuleSetVersionCurrent = RuleSetVersion2 RuleSetVersion3
RuleSetVersionCurrent = RuleSetVersion3
) )
const ( const (

View file

@ -9,6 +9,9 @@ icon: material/new-box
:material-alert: [disable_cache](#disable_cache) :material-alert: [disable_cache](#disable_cache)
:material-alert: [rewrite_ttl](#rewrite_ttl) :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" !!! quote "Changes in sing-box 1.10.0"
@ -125,6 +128,11 @@ icon: material/new-box
1000 1000
], ],
"clash_mode": "direct", "clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@ -310,6 +318,39 @@ Match user id.
Match Clash mode. 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 #### wifi_ssid
!!! quote "" !!! quote ""

View file

@ -2,6 +2,17 @@
icon: material/new-box 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 中的更改" !!! quote "sing-box 1.10.0 中的更改"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
@ -117,6 +128,11 @@ icon: material/new-box
1000 1000
], ],
"clash_mode": "direct", "clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@ -135,17 +151,15 @@ icon: material/new-box
"outbound": [ "outbound": [
"direct" "direct"
], ],
"server": "local", "action": "route",
"disable_cache": false, "server": "local"
"client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
"mode": "and", "mode": "and",
"rules": [], "rules": [],
"server": "local", "action": "route",
"disable_cache": false, "server": "local"
"client_subnet": "127.0.0.1/24"
} }
] ]
} }
@ -304,6 +318,39 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配 Clash 模式。 匹配 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 #### wifi_ssid
!!! quote "" !!! quote ""
@ -352,29 +399,35 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
`any` 可作为值用于匹配任意出站。 `any` 可作为值用于匹配任意出站。
#### server #### action
==必填== ==必填==
目标 DNS 服务器的标签。 参阅 [规则动作](../rule_action/)。
#### server
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### disable_cache #### disable_cache
在此查询中禁用缓存。 !!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### rewrite_ttl #### rewrite_ttl
重写 DNS 回应中的 TTL。 !!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### client_subnet #### client_subnet
!!! question "自 sing-box 1.9.0 起" !!! failure "已在 sing-box 1.11.0 废弃"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 已移动到 [DNS 规则动作](../rule_action#route).
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet``servers.[].client_subnet`
### 地址筛选字段 ### 地址筛选字段
@ -420,8 +473,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### mode #### mode
==必填==
`and``or` `and``or`
#### rules #### rules
==必填==
包括的规则。 包括的规则。

View file

@ -6,6 +6,9 @@ icon: material/new-box
:material-plus: [action](#action) :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" !!! quote "Changes in sing-box 1.10.0"
@ -120,6 +123,11 @@ icon: material/new-box
1000 1000
], ],
"clash_mode": "direct", "clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@ -322,6 +330,39 @@ Match user id.
Match Clash mode. 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 #### wifi_ssid
!!! quote "" !!! quote ""

View file

@ -6,6 +6,9 @@ icon: material/new-box
:material-plus: [action](#action) :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 中的更改" !!! 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-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [process_path_regex](#process_path_regex) :material-plus: [process_path_regex](#process_path_regex)
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set) :material-plus: [rule_set](#rule_set)
@ -118,6 +120,11 @@ icon: material/new-box
1000 1000
], ],
"clash_mode": "direct", "clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@ -153,7 +160,7 @@ icon: material/new-box
当内容只有一项时,可以忽略 JSON 数组 [] 标签。 当内容只有一项时,可以忽略 JSON 数组 [] 标签。
### Default Fields ### 默认字段
!!! note "" !!! note ""
@ -320,6 +327,39 @@ icon: material/new-box
匹配 Clash 模式。 匹配 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 #### wifi_ssid
!!! quote "" !!! quote ""
@ -366,13 +406,13 @@ icon: material/new-box
==必填== ==必填==
参阅 [规则动](../rule_action/)。 参阅 [规则](../rule_action/)。
#### outbound #### outbound
!!! failure "已在 sing-box 1.11.0 废弃" !!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [规则动](../rule_action#route). 已移动到 [规则](../rule_action#route).
### 逻辑字段 ### 逻辑字段

View file

@ -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 <file-name>.srs] <file-name>.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 一样,只匹配完全相同的域。

View file

@ -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 ### Structure
!!! question "Since sing-box 1.8.0" !!! question "Since sing-box 1.8.0"
@ -63,6 +73,11 @@
"package_name": [ "package_name": [
"com.termux" "com.termux"
], ],
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@ -177,6 +192,39 @@ Match process path using regular expression.
Match android package name. 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 #### wifi_ssid
!!! quote "" !!! quote ""

View file

@ -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
==必填==
包括的规则。

View file

@ -74,7 +74,7 @@ Tag of rule-set.
==Required== ==Required==
List of [Headless Rule](./headless-rule.md/). List of [Headless Rule](../headless-rule/).
### Local or Remote Fields ### Local or Remote Fields

View file

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

View file

@ -4,6 +4,10 @@ icon: material/new-box
# Source Format # Source Format
!!! quote "Changes in sing-box 1.11.0"
:material-plus: version `3`
!!! quote "Changes in sing-box 1.10.0" !!! quote "Changes in sing-box 1.10.0"
:material-plus: version `2` :material-plus: version `2`
@ -14,7 +18,7 @@ icon: material/new-box
```json ```json
{ {
"version": 2, "version": 3,
"rules": [] "rules": []
} }
``` ```
@ -29,19 +33,14 @@ Use `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to c
==Required== ==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. * 1: sing-box 1.8.0: Initial rule-set version.
* 2: Optimized memory usages of `domain_suffix` rules. * 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.
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.
#### rules #### rules
==Required== ==Required==
List of [Headless Rule](./headless-rule.md/). List of [Headless Rule](../headless-rule/).

View file

@ -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 <file-name>.srs] <file-name>.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/).

View file

@ -95,6 +95,9 @@ type RawDefaultRule struct {
User badoption.Listable[string] `json:"user,omitempty"` User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,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"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`

View file

@ -97,6 +97,9 @@ type RawDefaultDNSRule struct {
UserID badoption.Listable[int32] `json:"user_id,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound badoption.Listable[string] `json:"outbound,omitempty"` Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,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"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`

View file

@ -162,6 +162,9 @@ type DefaultHeadlessRule struct {
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,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"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
@ -200,7 +203,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
var v any var v any
switch r.Version { switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2: case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
v = r.Options v = r.Options
default: default:
return nil, E.New("unknown rule-set version: ", r.Version) return nil, E.New("unknown rule-set version: ", r.Version)
@ -215,7 +218,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
} }
var v any var v any
switch r.Version { switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2: case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
v = &r.Options v = &r.Options
case 0: case 0:
return E.New("missing rule-set version") return E.New("missing rule-set version")
@ -231,7 +234,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
switch r.Version { switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2: case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
default: default:
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
} }

View file

@ -223,6 +223,21 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, 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 { if len(options.WIFISSID) > 0 {
item := NewWIFISSIDItem(networkManager, options.WIFISSID) item := NewWIFISSIDItem(networkManager, options.WIFISSID)
rule.items = append(rule.items, item) rule.items = append(rule.items, item)

View file

@ -220,6 +220,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, 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 { if len(options.WIFISSID) > 0 {
item := NewWIFISSIDItem(networkManager, options.WIFISSID) item := NewWIFISSIDItem(networkManager, options.WIFISSID)
rule.items = append(rule.items, item) rule.items = append(rule.items, item)

View file

@ -140,18 +140,33 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.WIFISSID) > 0 {
if networkManager != nil { 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 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) item := NewWIFISSIDItem(networkManager, options.WIFISSID)
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
}
} }
if len(options.WIFIBSSID) > 0 { if len(options.WIFIBSSID) > 0 {
if networkManager != nil {
item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
} }
if len(options.AdGuardDomain) > 0 { if len(options.AdGuardDomain) > 0 {

View file

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

View file

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

View file

@ -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), " ")+"]")
}
}