路由规则
Sail 从上到下评估路由规则。route、reject 和 hijack-dns 会停止匹配;sniff、resolve 与 route-options 补充连接信息后继续处理下一条规则。没有被任何规则停下的连接交给 route.final。
同一字段中的值为“或”,不同条件族之间为“与”。下面规则匹配来自 local-socks、目标为 example.com 或其子域名的 TCP 连接:
{ "domain_suffix": ["example.com"], "network": ["tcp"], "inbound": ["local-socks"], "action": "route", "outbound": "secure"}与 sing-box 一致,目标类条件(各域名字段、GeoSite、IP CIDR、GeoIP 与 external 集合)之间为“或”,端口类条件之间、源地址类条件之间也各为“或”;每一组再与规则上的其他条件取“与”。invert 对整条规则取反。
| 字段 | 示例 | 含义 |
|---|---|---|
domain |
api.example.com |
精确域名 |
domain_suffix |
example.com |
域名及其子域名 |
domain_keyword |
cdn |
包含字符串的域名 |
domain_regex |
^api\. |
匹配正则表达式的域名 |
ip_cidr |
10.0.0.0/8 |
目标网络 |
ip_is_private |
true |
目标地址不是公网地址 |
geoip |
cn |
geo.mmdb 中的国家或地区(Sail 扩展) |
geosite |
category-ads-all |
site.dat 中的网站组(Sail 扩展) |
external |
site:custom.dat:work |
其他数据文件中的分组(Sail 扩展) |
ip_asn |
13335 |
asn.mmdb 中的自治系统(Sail 扩展) |
rule_set |
geosite-cn |
所列规则集中的任意规则 |
port、port_range |
443、1000:2000 |
目标端口或闭区间;:1024、8000: 为单侧开放区间 |
source_ip_cidr、source_port |
192.168.0.0/16 |
连接来源 |
network |
tcp, udp |
传输协议 |
protocol |
tls, quic |
sniff 规则识别出的协议 |
inbound |
tun-in |
来源入站标签 |
auth_user |
alice |
入站认证用户 |
process_name、process_path |
curl |
可用时的来源进程 |
package_name |
com.example.app |
宿主报告的 Android 应用 |
wifi_ssid、network_type |
Home、cellular |
主机当前所在网络 |
clash_mode |
Global |
Clash API 当前模式 |
全部条件见路由参考。GeoIP、GeoSite 与 ASN 文件从数据目录读取;文件不在可执行文件旁边时,用 -D 或 --data-dir 指定。
| 动作 | 效果 |
|---|---|
route(默认) |
把连接交给 outbound,停止匹配 |
reject |
关闭连接,停止匹配;method: drop 时不作应答 |
hijack-dns |
应答连接中携带的 DNS 查询,停止匹配 |
route-options |
设置连接的承载方式,如 override_address、udp_timeout、tls_fragment,继续匹配 |
sniff |
从开头字节读取协议和域名,继续匹配 |
resolve |
解析域名,让后续规则能匹配其地址,继续匹配 |
bypass |
在 Linux auto_redirect 下由内核直接转发、不经过 Sail;其他情况下路由到其 outbound,没有出站时跳过该规则 |
direct |
接受但给出警告;与 sing-box 1.14.1 一样不起作用 |
route 规则可以带上与 route-options 相同的选项。sing-box 的 evaluate、respond、predefined 路由动作,以及 ssh、rdp、ntp 嗅探器,均未实现,会报错。
最终路由与拒绝
Section titled “最终路由与拒绝”{ "route": { "rules": [ { "ip_cidr": ["10.0.0.0/8"], "action": "route", "outbound": "direct" }, { "domain_suffix": ["ads.example"], "action": "reject" } ], "final": "secure" }}兜底路径使用 final;未设置时,未匹配任何规则的连接交给第一个出站。没有条件的 route/reject 规则会在验证阶段被拒绝,因为它会遮蔽所有后续规则。
"type": "logical" 的规则用 "mode": "and" 或 "or" 组合其他规则。内部规则只写条件,动作写在逻辑规则上:
{ "type": "logical", "mode": "and", "rules": [ { "network": ["udp"] }, { "port": [443] } ], "action": "reject"}route.rule_set 声明规则集,规则通过 rule_set 引用。规则集分为 inline(rules 直接写在配置中)、local(path 指向文件)和 remote(url 下载,每隔 update_interval 重新下载,默认一天,可经 download_detour 或 http_client 下载)。格式为 sing-box 的 source(JSON)或 binary(.srs),未设置 format 时按文件扩展名判断。
作为 Sail 扩展,规则集也可以是 Clash 的 rule-provider(mrs、clash-yaml、clash-text)或 Surge 的规则集、域名集(surge-text),并用 behavior 指定 domain、ipcidr 或 classical。
{ "route": { "rule_set": [ { "type": "remote", "tag": "geosite-cn", "url": "https://example.com/geosite-cn.srs" } ], "rules": [ { "rule_set": ["geosite-cn"], "action": "route", "outbound": "direct" } ] }}跳过规则(PASS)
Section titled “跳过规则(PASS)”pass 出站是 Sail 的扩展,对应 Mihomo 的 PASS:{ "type": "pass", "tag": "PASS" }。规则指向它,或者指向当前选中 PASS 的 selector、urltest、fallback、network 组(嵌套组逐层往下看)时,这条规则被跳过:它的路由选项不生效,由后面的规则决定。在 selector 里选 PASS,就能在不改配置的情况下关掉指向它的规则。final 经由组解析到 PASS 时,连接走直连。
final(未设置 final 时为第一个出站)直接写 pass 出站属于配置错误;PASS 出现在 load-balance 或 smart 组里(包括经由嵌套组)也是配置错误。urltest 和 fallback 从不测试 PASS,并把它视为不可用,所以永远不会选中它。这一点与 Mihomo 不同:Mihomo 的组在第一次测试之前可能选中 PASS;fallback 在全部成员不可用、且 PASS 排在第一位时也会选中它。如果连接仍然到达 PASS(例如路由之后 selector 才切到 PASS),连接会失败,错误为 “routed to PASS”。
路由前嗅探域名
Section titled “路由前嗅探域名”应用直接连接 IP 地址时,Sail 可以检查连接开头的字节,得到协议和域名:TLS 或 QUIC 的服务器名,或 HTTP Host。它还能识别 DNS、STUN、BitTorrent 和 DTLS。
{ "route": { "rules": [ { "action": "sniff", "sniffer": ["tls", "http", "quic"], "timeout": "300ms" }, { "domain_suffix": ["example.com"], "action": "route", "outbound": "secure" } ], "final": "direct" }}sniffer 列出要识别的协议,为空时全部识别;timeout 默认 300 毫秒。嗅探只获取元数据,不会解密 TLS。
Sail 另外提供三个字段:override_destination 为连接设置同名选项,见下一节;skip_rule_set 忽略所列规则集匹配到的嗅探域名,对应 Mihomo 的 skip-domain;on_demand 只预备而不立即嗅探,等后续规则需要结果时才执行。
用域名连接代理(override_destination)
Section titled “用域名连接代理(override_destination)”TUN 连接到达时只有 IP 地址,而 Sail 往往知道它背后的域名。override_destination 是 Sail 扩展:把这个域名而不是地址交给代理服务器,由服务器自己解析。本地解析结果被污染、指向远处节点,或是只有本地网络才能访问的地址时,这样做更可靠。
{ "inbound": ["tun"], "action": "route-options", "override_destination": true}- 在哪里设置。
route或route-options规则可以设置,sniff规则的同名字段也可以。规则条件决定它作用于哪些连接;后面规则的值优先。 - 规则仍然看到地址。 域名只在路由之后、建立连接时使用。
ip_cidr、ip_is_private、ip_version和规则集匹配的都是原地址,所以把局域网网段发往直连的规则照常生效。 - 取值。
true或"proxy":只有最后一跳连接代理服务器时才用域名,包括成员是代理的分组和经由detour的链式代理;direct直连保留地址。"proxy_and_direct":直连也用域名,由直连出站用自己的domain_resolver和策略解析。例如主机开启了 IPv6 但没有 IPv6 路由时,可以让已知域名的 IPv6 目标改走 IPv4:{"inbound": ["tun"], "ip_cidr": ["2000::/3"], "action": "route-options", "override_destination": "proxy_and_direct"},并在直连出站上设置"domain_resolver": {"server": "local", "strategy": "ipv4_only"}。 - 域名来源,按顺序。 先用嗅探到的域名(TLS 或 QUIC 服务器名、HTTP Host)。否则在开启
dns.reverse_mapping时,使用 Sail 自己的 DNS 为该地址给出的域名,且只在应答 TTL 内有效。IPv4 映射的 IPv6 地址按其 IPv4 地址查找。嗅探到的“域名”本身是地址,或 HTTP 请求没有 Host 时,也回退到映射。两者都没有时发送地址。多个域名共用一个地址时,以最近的应答为准。目标本来就是域名时不做改动。 - UDP。 数据报发往域名。回复看起来来自客户端原本发往的地址;设置
udp_disable_domain_unmapping时则来自域名。 - 日志与连接列表。 debug 日志记为
dial <地址> as <域名> (sniff)或(reverse mapping)。用反向映射得到的域名连接失败时(映射可能已过时),debug 日志会记下,不会改用地址重试。Clash API 中destinationIP保留地址,host显示实际连接的域名,sniffHost显示嗅探到的域名,dialDomainSource为sniff或reverse_mapping。 - 与
resolve同时使用。 两者都生效时,代理收到的是域名而不是resolve得到的地址;直连仍使用这些地址。
sing-box 没有这个选项:它的 sniff override_destination 已废弃,现在作为未知字段被拒绝。Mihomo 嗅探器的 override-destination 行为不同:它在规则之前替换目标,之后的 IP 规则解析嗅探到的域名。Clash 前端保留 Mihomo 的行为,把 sniff 规则的 override_destination 设为 "at_sniff";这个值只能用在 sniff 规则上。ip_version 匹配的仍是原地址的 IP 版本。
IP 匹配前先解析
Section titled “IP 匹配前先解析”resolve 动作解析域名,让后续 IP、CIDR 或 GeoIP 规则能匹配其地址:
{ "domain_suffix": ["example.net"], "action": "resolve"}把它放在需要解析结果的 IP 类规则之前。server 和 strategy 可为这次解析覆盖 DNS 规则与 dns.strategy。与 sing-box 一致,域名解析失败时连接失败;Sail 的 ignore_failure 改为不带地址继续匹配,Sail 的 on_demand 则只在后续规则需要地址时才解析。DNS 策略与缓存行为来自顶层 dns 配置。
两种解析在之后如何使用地址上有所不同,这关系到隐私:
- 普通的
resolve动作与 sing-box 一致:地址交给连接所走的任一出站。代理出站发给服务器的是 IP 地址而不是域名,因此由本地 DNS 决定连接去向,代理服务器看不到域名。 on_demand解析(Clash 与 Surge 的 IP 规则,如未加no-resolve的IP-CIDR、GEOIP,都会转换成它)与 Mihomo 一致:地址只用于匹配和直连出站。代理出站发送的仍是域名,由代理服务器解析。- 同时设置了
override_destination时,无论哪种解析,代理出站发送的都是域名而不是地址。
选择网络(network_strategy)
Section titled “选择网络(network_strategy)”network_strategy 决定在主机的哪些网卡之间选择。它是所有出站都有的拨号字段;写在 route 或 route-options 规则上时,只在连接经直连出站发出时生效。
default:默认网卡,或network_type所列类型的全部网卡。hybrid:同时使用全部网卡,或network_type所列类型的全部网卡。fallback:先按default;超过fallback_delay或这些网卡失败后,改用fallback_network_type所列类型的网卡,未列出时用其余全部网卡。
网络类型有 wifi、cellular、ethernet 和 other。sing-box 只在其 Android 和 Apple 客户端中处理这些字段;Sail 在 Linux、macOS 和 Windows 上也会根据自己检测到的网卡处理,这是有意的扩展。在这些系统上,other 类型的网卡(例如其他 VPN 的隧道、网桥)只有在 network_type 或 fallback_network_type 列出 other 时才会参与选择。route.default_network_strategy 为未设置策略的连接提供默认值,并且需要开启 route.auto_detect_interface。
策略组与提供者
Section titled “策略组与提供者”策略组出站为每个连接选择一个成员。除 sing-box 的 selector 和 urltest 外,Sail 还提供 fallback、load-balance、smart、network 和 tryall,各自的选择方式见协议与兼容性。规则通过标签指向策略组。
outbound_providers 是 Sail 扩展,像 Mihomo 的 proxy-providers 一样为策略组提供成员。提供者分为 remote(订阅地址)、local(文件)和 inline。订阅内容可以是带 proxies 的 Clash YAML,也可以是分享链接。filter、exclude_filter、exclude_type 和 override 决定取用哪些出站以及如何改写。
{ "outbound_providers": [ { "type": "remote", "tag": "sub", "url": "https://example.com/sub.yaml" } ], "outbounds": [ { "type": "urltest", "tag": "auto", "providers": ["sub"] } ]}selector、urltest、fallback、load-balance 和 smart 组都可以在自身 outbounds 之外使用 providers。
带有 sing-box network 字段的成员只承载 TCP 或只承载 UDP。urltest、fallback 和 load-balance 只把连接交给能承载其网络的成员。urltest 使用当前选中的成员;选中的成员不承载该网络,或上次测试失败时,改用能承载该网络的成员中延迟最低的一个,与 sing-box 的 urltest 对 TCP 和 UDP 分别选择一致。fallback 按顺序使用第一个可用且承载该网络的成员,load-balance 在可用且承载该网络的成员之间分配。测试是 HTTP 请求,所以只承载 UDP 的成员不参与测试:它没有延迟,视为可用,经它的连接失败也不会触发测试。
fallback 组在第一次测试失败时就切走,在第一次测试通过时就切回。成员时好时坏时,流量会来回切换。Sail 扩展字段 debounce 可以抑制这种切换:
{ "type": "fallback", "tag": "auto", "outbounds": ["primary", "backup"], "interval": "10s", "debounce": { "fail_after": 1, "recover_after": 3, "min_dwell": "30s" }}fail_after:连续失败多少轮测试后才离开该成员;默认 1。recover_after:曾经不可用的成员要连续通过多少轮测试才重新算作可用,排在前面时才会被切回;默认 1。min_dwell:在一个成员上至少停留多久,才会切回排在前面的成员;默认 0s。超过这个时间后的第一轮测试完成切换。
默认值与不写 debounce 时的行为完全相同。连接发现成员的服务器不可达(拨号时被拒绝、超时或不可达)时,组仍然立即离开它,不受 fail_after 和 min_dwell 影响;之后该成员要连续通过 recover_after 轮测试才会再被使用。计入 max_failed_times 的失败连接只会让下一轮测试提前,不计入上面两个计数。通过 API 手动固定的成员,只要可用或最近一次测试通过,就会被使用,不受 debounce 影响。设置 recover_after: 3 和 min_dwell: "30s" 后,第一个成员连续通过三轮测试后,组会在离开它至少 30 秒后的第一轮测试时切回。
还有可回退成员时,经 fallback 组的连接会等成员 timeout 这么久,再换下一个成员。Sail 扩展字段 dial_timeout 把这段等待与 timeout 分开:timeout 仍是一次测试的时限,也是 max_failed_times 计数的时间窗,与 Mihomo 相同。不设置时等于 timeout。拨号到成员服务器超过这个时间,成员会像被拒绝时一样立即标记为不可用;握手超过这个时间则可能是目标的原因,只计入 max_failed_times。dial_timeout 越短切换越快,但也越容易把慢链路当成断线,所以至少要 1s(TCP 重发丢失的 SYN 前就要等这么久);建议同时把 debounce.recover_after 设为大于 1:
{ "type": "fallback", "tag": "auto", "outbounds": ["primary", "backup"], "timeout": "5s", "dial_timeout": "2s", "debounce": { "recover_after": 3 }}fallback 组的 url 也可以写成列表(Sail 扩展)。每轮测试会同时用每个 URL 测试每个成员,每个 URL 各自受 timeout 限制,expected_status 对每个 URL 分别生效。url_policy 为 "any"(默认)时,任一 URL 有响应成员即通过,可以区分成员失效和测试 URL 被屏蔽;为 "all" 时,所有 URL 都要有响应。每多一个 URL,每轮测试就多一份请求。显示的延迟取第一个 URL 的(any 下取第一个有响应的 URL 的),Clash API 也把第一个 URL 作为组的 testUrl。写成单个字符串时只测一个 URL,与以前相同。
{ "type": "fallback", "tag": "auto", "outbounds": ["primary", "backup"], "url": ["https://www.gstatic.com/generate_204", "https://cp.cloudflare.com/generate_204"], "url_policy": "any"}防止 TUN 回环
Section titled “防止 TUN 回环”开了 auto_route 的 TUN 入站会接管系统流量,Sail 自己的出站套接字本会绕回 TUN。Sail 把它们绑定到物理网卡:目标所在网段的那块网卡,否则是默认网卡。网络变化时 Sail 会跟着切换,默认网卡变了就重置 TUN 上的连接。开了 auto_route 时,即使没写 route.auto_detect_interface,这个机制也会自动开启;出口必须固定时改用 route.default_interface。
在 Linux 上,auto_route 不改主路由表:TUN 的路由放在表 2022(iproute2_table_index),从优先级 9000(iproute2_rule_index)开始的 ip 规则把流量引过去。设备消失时内核会一并删掉这些路由,崩溃留下的规则会在下次启动时清掉。route_address、route_exclude_address 及其规则集形式、include_interface/exclude_interface、include_uid/exclude_uid 和 strict_route 决定接管哪些流量,含义和 sing-box 相同。配置了 route_address 或 route_exclude_address 时,列出的前缀会优先于局域网自己的路由,所以局域网需要显式排除。
在 macOS 上,auto_route 经 utun 添加比默认路由更具体的路由:1.0.0.0/8、2.0.0.0/7 … 128.0.0.0/1,IPv6 同样按这种方式对半切分;配置了 route_address 时改为添加这些前缀。route_exclude_address 及其规则集形式会在这些路由中挖出空洞。默认路由本身始终不变,utun 消失时这些路由也随之消失。strict_route、网卡列表和 uid 列表在 macOS 上不起作用,与 sing-box 相同。如果这些前缀中已有路由(其他 VPN 的、手动添加的静态路由,或内核为 utun 自身网段添加的路由),sail 会像 sing-box 一样替换它,并输出一条警告,写明被替换的是哪条路由;sail 停止时不会恢复被替换的路由。
在 Windows 上,auto_route 经 wintun 网卡以跃点数 0 添加 0.0.0.0/0 和 ::/0:它们靠跃点数优先于默认路由,但不替换默认路由。网卡的 DNS 指向 TUN 地址的下一个地址。route_address、route_exclude_address 及其规则集形式选择路由的方式与其他系统相同。strict_route 会添加防火墙规则,阻止 DNS 走其他网卡。
在内核中绕过(Linux auto_redirect)
Section titled “在内核中绕过(Linux auto_redirect)”开启 auto_redirect 后,Linux TUN 入站不改主路由表:nftables 把系统的 TCP 重定向到本地监听器,UDP 和 ICMP 通过标记路由进 TUN。Sail 自己的套接字带输出标记,不会被接管,因此不需要 auto_detect_interface。
{ "inbounds": [ { "type": "tun", "address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"], "auto_route": true, "auto_redirect": true } ]}每个连接的第一个包在重定向前会经 NFQUEUE 预匹配:命中 bypass 的连接由内核直接转发,不经过 Sail;命中 reject 的连接由内核重置或丢弃。
{ "ip_cidr": ["192.0.2.0/24"], "action": "bypass"}预匹配只能看到第一个包的信息:地址、端口和网络类型。遇到需要嗅探的规则时,预匹配结束,连接照常重定向。带 outbound 的 bypass 在连接到达 Sail 后路由到该出站;没有 auto_redirect 时,不带出站的 bypass 会被跳过。
route_address_set 和 route_exclude_address_set 只取规则集的目标地址,规则集重新下载时会同步刷新。在 OpenWrt 上,Sail 还会写入 fw4 片段,放行 TUN 的流量。