# 分流规则

`[Rule]` / `[Proxy Group]` / `[Host]` 三段的完整语法，外加模块能改的那部分 DNS 设置。

## `[Rule]` 行语法

```
类型,匹配值,出口
类型,匹配值,出口,选项
FINAL,出口
AND,((类型,值),(类型,值)),出口
RULE-SET,https://example.com/list.txt,出口
```

字段按逗号切分，每个字段前后的空格会被去掉。第三个字段是**出口**，它后面的都当作选项。

**匹配顺序是从上到下，第一条命中的规则决定出口，后面的不再看。** 所以精确规则写在前、宽泛规则写在后。

一行只写一个匹配值，不能写 `DOMAIN-SUFFIX,a.com,b.com,PROXY`。多个值写多行。

行尾不能写注释，`,DIRECT # 说明` 会把 `DIRECT # 说明` 整个当成出口名。注释写在上一行。

### 规则类型全表

| 类型 | 匹配对象 | 取值写法 | 例子 |
|---|---|---|---|
| `DOMAIN` | 完整域名，精确相等 | 域名，不带协议和路径 | `DOMAIN,www.google.com,默认` |
| `DOMAIN-SUFFIX` | 域名后缀 | 不带前导点。`google.com` 同时命中 `google.com` 和 `a.google.com` | `DOMAIN-SUFFIX,google.com,默认` |
| `DOMAIN-KEYWORD` | 域名里含某段字符 | 任意子串 | `DOMAIN-KEYWORD,google,默认` |
| `DOMAIN-REGEX` | 域名正则 | RE2 正则，建议加 `^` `$` 锚定 | `DOMAIN-REGEX,^.*\.google\.com$,默认` |
| `DOMAIN-WILDCARD` | 域名通配 | `*` 任意长度，`?` 单字符 | `DOMAIN-WILDCARD,*.google.com,默认` |
| `IP-CIDR` | 目标 IPv4 网段 | 带掩码，单个 IP 写 `/32` | `IP-CIDR,149.154.160.0/20,默认` |
| `IP-CIDR6` | 目标 IPv6 网段 | 带掩码，单个 IP 写 `/128` | `IP-CIDR6,2001:b28:f23d::/48,默认` |
| `IP6-CIDR` | 同 `IP-CIDR6` | | |
| `SRC-IP-CIDR` | 来源 IP 网段 | 带掩码 | `SRC-IP-CIDR,192.168.1.0/24,DIRECT` |
| `GEOIP` | 目标 IP 归属地 | 规则集名，见下文 | `GEOIP,cn,DIRECT` |
| `GEOSITE` | 域名所属集合 | 规则集名，见下文 | `GEOSITE,category-ads-all,REJECT` |
| `DST-PORT` | 目标端口 | 单个端口号 | `DST-PORT,443,默认` |
| `PORT` | 同 `DST-PORT` | | |
| `SRC-PORT` | 来源端口 | 单个端口号 | `SRC-PORT,1080,DIRECT` |
| `PROCESS-NAME` | 发起连接的进程 | 可执行文件名 | `PROCESS-NAME,Google Chrome,默认` |
| `PROTOCOL` | 传输层/应用层协议 | 见下表 | `PROTOCOL,UDP,REJECT` |

补充：

- **域名类的匹配值必须自己写成小写。** 客户端只把被匹配到的域名转小写，规则里的值原样使用 —— 写了 `DOMAIN-SUFFIX,Google.COM` 就永远不命中，而且没有任何报错。`GEOIP` / `GEOSITE` 的名字例外，它们会被自动转小写。
- `IP-CIDR` 和 `IP-CIDR6` 内部是一回事，写对应的那个只是为了可读。裸 IP 不带掩码也能认，但写全更清楚。
- **IP 配置不是合法地址或网段时，内核将无法启动**，不会只跳过该规则。`DOMAIN-REGEX` 使用无效正则表达式时也会导致相同结果。
- **端口不支持区间**，`DST-PORT,80-443` 会让整条规则失效。多个端口写多行。
- `PROCESS-NAME` 只在 macOS 和 Windows 桌面端生效。手机端没有进程归属能力，写了不会命中。macOS 填 `.app` 里的可执行文件名(一个应用常有主程序 + 若干 Helper，都要写)，Windows 填 `chrome.exe` 这样的文件名。

### `PROTOCOL` 的取值

| 值 | 含义 |
|---|---|
| `TCP` | TCP 连接 |
| `UDP` | UDP 连接 |
| `QUIC` | QUIC / HTTP/3 |
| `HTTP` | 明文 HTTP |
| `TLS` | TLS 连接 |
| `HTTPS` | 同 `TLS` |

其它值不认，整条规则被丢弃。

### 明确不支持的类型

以下类型不受支持。包含这些类型的规则会被忽略，且不会显示错误或提示：

`USER-AGENT`、`URL-REGEX`、`IP-ASN`、`DOMAIN-SET`、`DEST-PORT-RANGE`、`SUBNET`、`DEVICE-NAME`、`SCRIPT`、`CELLULAR-RADIO`、`RULE-SET-IP`

上表之外的任何类型同样被跳过，同样没有提示。导入界面不会告诉你「有 N 条被跳过」，所以只用规则类型全表里列出的类型。

要按 URL 路径而不是域名来拦截，用 [`[URL Rewrite]`](./mitm.md#url-rewrite) 而不是 `URL-REGEX`。

### 选项

第三个字段之后的都是选项。只有 `no-resolve` 会被识别(大小写不敏感)，写在 IP 类规则上表示不要为了匹配它去做 DNS 解析：

```
IP-CIDR,91.108.4.0/22,默认,no-resolve
```

其它选项(`force-remote-dns`、`extended-matching`、`pre-matching` 等)被忽略，写了不报错。

### `FINAL`

```
FINAL,默认
```

`FINAL` 指定所有其他规则均未命中时使用的出口，等价于 Clash 风格的 `MATCH,默认`。配置多条时不会报错，**最后一条生效**。`FINAL,REJECT` 以及其他 REJECT 变体会被忽略，默认规则保持不变。

`FINAL` 会替换客户端的默认规则。只有需要控制所有未匹配流量时才应配置它；仅修改少量域名出口的模块不需要 `FINAL`。

### `AND` / `OR`

多个条件组合：

```
AND,((DOMAIN-SUFFIX,googlevideo.com),(PROTOCOL,UDP)),REJECT
OR,((DOMAIN-SUFFIX,youtube.com),(DOMAIN-SUFFIX,youtu.be)),默认
```

- 子条件写在双层括号里，每个子条件用一层括号包住，子条件之间用逗号分隔。
- 子条件**不包含出口**，出口只写在最外层。子条件中多余的第三个字段会被当作选项忽略，且不会显示错误。
- 子条件里可以带 `no-resolve`。
- 子条件里**任何一个类型不被支持，整条规则都会被丢弃**(不会退化成部分匹配)。
- **没有 `NOT`。**

### `RULE-SET`

引用一份外部规则列表：

```
RULE-SET,https://example.com/reject.list,REJECT
```

- 值必须是 `http://` 或 `https://` 开头的完整链接。
- 列表内容是每行 `类型,值` 的纯规则(没有出口列)，整份列表统一走这条写的出口。列表里能用的类型比 `[Rule]` 少，见 [modules.md](./modules.md) 的「独立规则列表」。
- **不支持裸名字**，`RULE-SET,SYSTEM,DIRECT`、`RULE-SET,LAN,DIRECT` 或者引用别处定义的具名 provider 都会被跳过。
- 外部规则集的优先级高于同一模块 `[Rule]` 段里手写的规则。

Loon 风格的 `[Remote Rule]` 段写法也支持：

```ini
[Remote Rule]
https://example.com/reject.list, policy=REJECT, tag=广告, enabled=true
```

`policy` 必填；`tag` 不填时取 `policy`;`enabled` 不填按 true。

### `[Remote Filter]`

Loon 风格的具名筛选分组，等价于一个「用正则从全部订阅节点里筛成员」的手动分组：

```ini
[Remote Filter]
香港 = NameRegex, FilterKey="(?i)(香港|HK)"
```

只有 `FilterKey` 被当成筛选正则；第一个字段(`NameRegex` / `NameKeyword` 等)写什么都按同一种处理。自己写模块用 `[Proxy Group]` 就行，这一段只是为了能直接导入 Loon 的模块。

## 出口怎么写

规则的第三个字段、以及分组的成员，都是「出口」。解析顺序：

1. `REJECT` 开头的名字 → 拦截，见下。
2. 任何**已启用模块**里 `[Proxy Group]` 定义过的分组名，以及**客户端配置里已有的出口名** → 该出口。跨模块有效:A 模块的规则可以指向 B 模块建的分组。
3. `DIRECT`(大小写不敏感)→ 直连。
4. 其他名称 → 使用客户端的「默认」出口。

第 2 条里的「客户端已有的出口名」包括三个内置名，规则里可以直接写：

| 名字 | 含义 |
|---|---|
| `默认` | 主选择器，也就是用户在节点页里选的那个。等于「走代理」 |
| `自动选择` | 按延迟自动挑最快的节点 |
| `直连` | 不走代理，与 `DIRECT` 等价 |

订阅自带的分组名(节点页里能看到的那些)同样可以直接写。

**`PROXY` 可以使用，但它不是关键字。** `PROXY` 未被定义时会按第 4 条使用「默认」出口，结果与直接写 `默认` 相同。其他未定义或拼写错误的分组名也按此规则处理，且不会显示错误。建议直接使用 `默认`，以明确表达配置含义。

结论：走代理写 `默认`(写 `PROXY` 也行，只是配置本身看不出对错)，直连写 `直连` 或 `DIRECT`；要精确控制走哪个地区，自己在 `[Proxy Group]` 里建一个分组，再在规则里写这个分组名。

### REJECT 家族

| 写法 | 效果 |
|---|---|
| `REJECT` | 断开连接 |
| `REJECT-NO-DROP` | 断开连接，不静默丢弃 |
| `REJECT-DROP` | 静默丢弃，不回任何东西 |
| `REJECT-200` | 回一个空的 200 |
| `REJECT-IMG` | 回一张 1×1 PNG |
| `REJECT-TINYGIF` | 回一张 1×1 GIF |
| `REJECT-DICT` | 回 200 `{}` |
| `REJECT-ARRAY` | 回 200 `[]` |
| `REJECT-VIDEO` | 回一段空 mp4 |

后四种和 `REJECT-200`、`REJECT-IMG`、`REJECT-TINYGIF` 属于**内容型拦截**:要伪造一个 HTTP 响应，必须开着 HTTPS 解密，而且规则本身必须是 `DOMAIN` / `DOMAIN-SUFFIX` / `DOMAIN-KEYWORD` 三种之一。

不满足条件时(未启用解密，或者规则类型为 `GEOSITE` / `IP-CIDR` / `DOMAIN-REGEX` / `PROCESS-NAME` / `AND` 等)，会按普通 `REJECT` 处理。目标仍会被拦截，但结果从返回模拟响应变为断开连接。应用可能表现为空白页面或连接错误。

`REJECT`、`REJECT-IMG`、`REJECT-TINYGIF`、`REJECT-DICT`、`REJECT-ARRAY`、`REJECT-VIDEO` 可以带 `-NO-DROP` 后缀。除此之外的 `REJECT-*` 变体(包括 `REJECT-200-NO-DROP`)不受支持，并按普通 `REJECT` 处理。不确定时请使用不带后缀的形式。

> `[Rule]` 段的 `-NO-DROP` 规则和 `[URL Rewrite]` 段的 `-no-drop` 规则**不一样**，后者见 [mitm.md](./mitm.md#url-rewrite)。

## `[Proxy Group]`

```
分组名 = 类型, 成员1, 成员2, key=value, ...
```

### 分组类型

| 写法 | 行为 |
|---|---|
| `select` / `static` | 手动选，用户在客户端里点 |
| `url-test` / `url-latency-benchmark` | 定时测速，自动用最快的 |
| `fallback` / `available` | 按顺序取第一个可用的 |
| `load-balance` / `round-robin` / `balance` | 在成员间轮流分配 |

**无法识别的类型会使整行配置被忽略**，包括 `ssid`、`subnet` 等类型。

### 参数

| key | 作用 |
|---|---|
| `url` | 测速用的探测地址。只对 `url-test` / `fallback` / `load-balance` 有意义 |
| `interval` | 测速间隔，秒。常用 `300` |
| `policy-regex-filter` | 用正则从**全部订阅节点**里筛成员。别名：`filter`、`include-regex`、`FilterKey` |
| `exclude-regex` | 从筛出来的结果里再排除掉匹配的 |
| `hidden` | 写 `hidden` 或 `hidden=1`，这个分组不在客户端列表里显示 |

其它 key(`tolerance`、`no-alert`、`include-all`、`timeout` 等)被忽略，写了不报错。

只要写了 `policy-regex-filter` 或 `exclude-regex`，分组就自动包含全部订阅节点，**不需要手写成员名**。这是推荐写法：节点名变了不用改模块。

正则筛选和手写成员可以混用，例如在 `url-test` 分组外增加一个 `fallback` 分组。

### 例子

```ini
[Proxy Group]
香港 = url-test, policy-regex-filter=(?i)(香港|hong ?kong|HK|🇭🇰), url=http://www.gstatic.com/generate_204, interval=300
新加坡 = url-test, policy-regex-filter=(?i)(新加坡|singapore|SG|🇸🇬), exclude-regex=(?i)(试用|过期), url=http://www.gstatic.com/generate_204, interval=300
流媒体 = select, 香港, 新加坡, DIRECT
香港兜底 = fallback, 香港, 默认, url=http://www.gstatic.com/generate_204, interval=300, hidden=1
```

`流媒体` 这类手动分组的成员可以是其他分组名、`DIRECT`、客户端内置出口名或具体节点名。`hidden=1` 可隐藏仅供其他分组引用的内部分组。

### 正则的限制

分组筛选用的正则是 **RE2**，和 JavaScript 的正则**不一样**:

- **不支持环视**:`(?=...)`、`(?!...)`、`(?<=...)`、`(?<!...)` 全部不行。
- **不支持反向引用**:`\1`、`\2` 不行。
- 支持行首 `(?i)` 表示忽略大小写。
- 支持 `|` 分支、`{2,3}` 量词、字符类、`\d` `\w` `\s` 等常用写法。

使用不受支持的语法会导致**内核无法启动**，通常表现为节点列表为空且无法连接。这是节点列表突然为空的常见原因。需要排除节点时应使用 `exclude-regex`，不要使用环视。

### 引号

分组定义的尾部按逗号切分，但**双引号里的逗号不切**。所以正则里带 `{2,3}` 这类量词时，整个值要加引号：

```ini
测试 = url-test, policy-regex-filter="(?i)节点[0-9]{2,3}", interval=300
```

### 命名禁忌

**分组名不能与已有出口重名。** 发生重名时，新分组会被忽略且不会显示错误；规则仍指向原有同名出口。内核可正常运行，但新分组不会出现。

请勿使用：`默认`、`自动选择`、`直连`、订阅自带分组名，以及其他已启用模块定义的分组名。重复安装同一模块时，后一份同名分组也会被忽略，但不会影响内核启动。

另外两类名称会改变解析结果：

- **分组名以 `REJECT` 开头**(`REJECT广告` 之类):出口解析第 1 步就把它当成拦截策略，这个分组永远解析不到。
- **分组名叫 `DIRECT`**:出口解析第 2 步早于第 3 步，于是所有写 `DIRECT` 的规则都指向这个分组，而不是真直连。

### 成员的处理

- 成员里的 `REJECT` 会被丢掉(分组不能选「拦截」)。
- 成员里的 `DIRECT` 变成直连。
- 无法识别的成员名使用「默认」出口；多个无法识别的成员会合并为同一项。因此建议使用正则筛选，避免依赖未经验证的成员名。
- **分组不能为空。** 未配置成员或正则筛选(`我的组 = select`)，或者所有成员均被忽略(`广告 = select, REJECT`)，都会导致内核无法启动。

## `[Host]`

域名的解析方式，优先于客户端自己的 DNS 规则。两种写法：

```ini
[Host]
# 1. 静态映射:直接回这个 IP,不问任何 DNS
example.com = 1.2.3.4
internal.corp = 10.0.0.5
dual.example.com = 1.2.3.4, 2001:db8::1
*.lab.example.com = 10.0.0.9

# 2. 指定解析器:仍然去查,只是换一台 DNS 问
*.corp.example.com = server:10.0.0.53
*.intranet.example.net = server:10.0.0.53
```

- 静态映射的值只能是 **IP 字面量**。一行可以写多个，逗号分隔，IPv4 / IPv6 混写。
- `*.x.com` 匹配任意子域。
- 同一行两种混写时，静态映射优先，`server:` 部分忽略。
- 指定解析器的地址也**只能是 IP**(可带端口 `10.0.0.53:5353`，可写
  `tls://` `https://` `quic://` 前缀)，或者 `server:system` 表示交给系统解析器。
  写域名的话这条被丢掉。
- 走这台 DNS 的域名不会被分配虚拟 IP，拿到的是真实应答 —— 内网域名解析出内网地址，
  直接可用。
- 用它指到的 DNS 一律直连访问，不经过节点。
- 保留值 `server:fakeip`(也可写 `fake-ip`)会为指定域名分配虚拟 IP，不在本地执行真实
  解析，并由 `[Rule]` 决定出口。客户端内置规则会将部分域名判定为直连。如需让其中某个
  域名使用代理出口，必须同时配置 `[Host]` 和 `[Rule]`。仅配置 `[Rule]` 时，DNS 可能
  已返回中国大陆的真实 IP，随后流量仍会经代理连接该地址，产生不必要的额外路径：

  ```
  [Host]
  weather-map2.apple.com = server:fakeip

  [Rule]
  DOMAIN,weather-map2.apple.com,PROXY
  ```

  它不产生任何 DNS 查询(虚拟 IP 是本地生成的)，真正的解析发生在出站一侧。

## DNS

### `[General]` 的 `dns-server`

模块可以改**直连域名**用哪台 DNS 去解析 —— 换成公司 / 校园内网的解析器，内网的
split-horizon 域名才解析得对。

```ini
[General]
dns-server = 10.0.0.53, system
```

- 此处配置的解析器排在客户端原有解析顺序的**最前面**。查询失败时会继续使用原有解析器，
  因此切换网络后仍可继续解析。
- 地址规则同 `[Host]` 的 `server:`:只收 IP(可带端口和 `tls://` `https://` 前缀)
  或 `system`。
- **`[General]` 段只认 `dns-server` 这一个键**，其余键(代理端口、绕过列表、日志级别……)
  照旧忽略。

### 不受模块控制的部分

**节点和入口域名的解析不受模块影响**，始终使用客户端内置的解析路径。以上配置只作用于
用户访问的目标域名。其他 DNS 行为，包括缓存、虚拟 IP 模式和区域分流判定，也由客户端控制。

### 其它格式的等价写法

从 Clash / Stash(`.stoverride`)或 Quantumult X(`.snippet`)导入时，下面这些字段
会被读取，语义与上面两节相同：

| 来源字段 | 等价于 |
|---|---|
| `dns.nameserver-policy` | `[Host]` 的 `server:` 写法 |
| `dns.direct-nameserver` | `[General] dns-server` |
| `dns.fake-ip-filter` / `fake-ip-filter+` | 这批域名不分配虚拟 IP，拿真实应答 |
| `dns.nameserver-policy` 的值写 `fakeip` | `[Host]` 的 `server:fakeip`，强制分配虚拟 IP |
| QX `[dns]` 的 `server=/域名/IP` | `[Host]` 的 `server:` 写法 |
| QX `[dns]` 的 `server=IP` | `[General] dns-server` |

`dns:` 段里的其它键(`enhanced-mode`、`respect-rules`、`nameserver`、`fallback` 等)
一律忽略。域名模式支持 `+.a.com`、`*.a.com`、`.a.com`、`a.com` 四种写法；
裸通配(单独一个 `*` 或 `+.`)不接受，那等于把所有解析都交出去；通配只能在开头，
`+.stun.*.*` 或 `a.*.com` 这种中间带星的也不接受 —— 匹配的是字面后缀，星号在中间会被
当成一个真的星号字符去比，规则永远不命中。

写法不被接受的 DNS 设置(域名模式表达不了、解析器地址写成了域名、`[Host]` 那行一个 IP
都没有)会在导入时提示，模块详情页的「需注意的规则」里也能逐条看到。模块其余部分照常生效，
但这几条一次都不会生效。

## GeoIP / GeoSite

`GEOIP,cn` 和 `GEOSITE,google` 会转成对公开规则集目录的引用：

```
https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/sing/geo/geoip/<名字>.srs
https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/sing/geo/geosite/<名字>.srs
```

名字会被转成小写。带属性的写法(`google@cn`)和带否定的写法(`geolocation-!cn`)也是目录里的文件名，直接写即可。

常用的 GeoSite 名字：`cn`、`google`、`youtube`、`netflix`、`telegram`、`openai`、`apple`、`microsoft`、`category-ads-all`、`geolocation-!cn`。
常用的 GeoIP 名字：`cn`、`private`、`telegram`、`google`、`netflix`、`cloudflare`。

**要用别的名字，先去上面那个目录确认文件存在。**

### 名字写错不会报错

规则集在运行时下载。名称错误会导致下载返回 404，相应规则不会命中；内核仍会正常运行，且不会显示配置错误。可通过模块详情页的规则数量确认下载结果。

客户端会在导入模块时检查每个 geo 名称是否存在，无法确认的名称会显示提示。出现提示时应更正名称。

模块详情页会显示每个规则集下载的规则数量；显示 0 条表示下载未成功。
