Shadowrocket iOS 配置文件精细化分流编写与本地 DNS 劫持防护实操
发布于
Shadowrocket iOS 配置文件精细化分流编写与本地 DNS 劫持防护实操
过去两年我在给几个跨境团队做网络侧顾问,被问得最多的一类问题是:“为什么同样的机场订阅,A 的 iPhone 刷视频秒开,B 的 iPhone 连微信都转圈?” 排除掉订阅本身质量差异后,剩下的大头几乎都落在两件事上:分流规则写得粗不粗,以及DNS 有没有在本地被悄悄劫持。这两个问题在 Shadowrocket(下称“小火箭”)上尤其突出,因为它的配置文件格式(.conf)是自研的、文档稀缺,很多人从网上抄一份“万能配置”就用了三年,规则里还留着 DOMAIN-SUFFIX,qq.com,DIRECT 这种把整个腾讯生态一刀切的写法,结果企业微信的海外节点、QQ 音乐的海外 CDN 全走了直连,延迟飙到 400ms+。
先把核心结论摆在前面,后面逐条拆:
- 分流精度的瓶颈不在规则数量,而在匹配顺序与
GEOIP/FINAL兜底的相对位置。实测中把FINAL,PROXY改成显式GEOIP,CN,DIRECT+FINAL,PROXY的两段式,国内直连误判率从 11.3% 降到 1.7%。 - 小火箭默认的
dns-server = system是本地 DNS 劫持的最大入口。运营商递归 DNS 返回的 A 记录被污染时,即便流量最终走了代理,TLS 握手的 SNI 阶段仍可能因为先拿到假 IP 而多出一次 RTT 级的重试。 - HTTPS 解密(MITM)在小火箭里是“可开但慎开”。实测开启后单条 TLS 连接的内存占用从约 180KB 涨到 420KB,iPhone 12 上同时挂 6 条解密连接就会出现明显掉帧。
- 一份 200 行左右、结构清晰的配置,配合
fallback-dns-server与no-resolve的精确使用,能把跨境首包时间(TTFB)稳定压到 180–260ms 区间(视落地机房而定)。
一、物理链路与配置解析机制拆解
要写精细配置,先得搞清楚小火箭拿到一份 .conf 之后到底做了什么。它不是简单地把规则丢给内核,而是分了三层:
第一层:配置解析与规则编译。 小火箭启动时把 .conf 里的 [Rule] 段编译成一张有序的匹配表。这里的关键是顺序即优先级,第一条命中的规则直接决定去向,后面的不再评估。很多人把 FINAL 写在中间,或者把 GEOIP,CN,DIRECT 放在一堆 DOMAIN-SUFFIX 之前,都会让后面的精细规则形同虚设。
第二层:DNS 解析时机。 这是最容易被忽略的一环。规则里的域名匹配分两种:带 no-resolve 的和不带的。不带 no-resolve 的 IP-CIDR 规则会强制触发一次 DNS 解析去判断目标 IP 是否落在网段内,这个解析如果走的是被污染的本地 DNS,就会拿到假 IP,导致规则误判。而 DOMAIN/DOMAIN-SUFFIX 类规则只做字符串匹配,不触发解析,所以能用域名规则就别用 IP 规则,这是分流精度的第一条铁律。
第三层:连接建立与 DNS 转发。 规则判定为 PROXY 后,小火箭把域名(而非 IP)交给代理服务器,由落地端做最终解析——这叫“远程 DNS 解析”,是防污染的核心机制。判定为 DIRECT 时,才用本地 DNS 解析。所以 dns-server 和 fallback-dns-server 的配置,直接决定了直连流量和规则判定阶段的解析质量。
链路拓扑大致是这样(以一次访问 api.github.com 为例):
[iOS App]
│ ① 域名匹配 DOMAIN-SUFFIX,github.com,PROXY → 命中,不触发本地解析
▼
[Shadowrocket 内核]
│ ② 建立到代理节点的 TCP/TLS 连接,携带 SNI=api.github.com
▼
[落地机房] ③ 由落地端递归解析 → 真实 IP → 回源
▼
[目标服务器]
对比一下走 DIRECT 的 www.baidu.com:
[iOS App]
│ ① 未命中代理规则,落到 GEOIP 段
│ ② 触发本地 DNS 解析 www.baidu.com → 运营商递归 DNS
▼
[本地 DNS] ③ 返回 A 记录(可能被污染/被 CDN 调度到远端节点)
▼
[直连 TCP 握手]
看到区别了吗?代理流量把解析权交给了落地端,天然免疫本地污染;直连流量和规则判定阶段的解析,才是本地 DNS 劫持的受害者。这就是为什么“防劫持”的重点不在代理链路,而在 dns-server 的选择和 no-resolve 的正确使用。
二、真实环境实测数据
测试环境:iPhone 13(iOS 17.4)、小火箭 2.2.19、三条不同运营商线路(电信 500M 家宽、联通 5G、移动家宽),落地节点统一为香港 CN2 GIA(同一机房同一套餐),测试时段覆盖工作日白天、晚高峰(20:00–22:00)、凌晨。每组数据为 200 次请求的中位数,剔除首尾 5% 异常值。
表 1:不同 DNS 配置下的直连解析质量(电信家宽,晚高峰)
| DNS 配置 | 平均解析耗时 | 污染命中率 | 直连 TTFB 中位数 | 备注 |
|---|---|---|---|---|
system(运营商默认) | 38ms | 9.6% | 142ms | 部分域名返回 0.0.0.0 或境外假 IP |
223.5.5.5(阿里) | 22ms | 0.4% | 96ms | 稳定,但个别 CDN 调度偏北 |
119.29.29.29(腾讯) | 24ms | 0.3% | 91ms | 与阿里接近 |
1.1.1.1(Cloudflare) | 156ms | 0.1% | 218ms | 解析干净但 RTT 高,直连反而变慢 |
fallback-dns-server 双栈 | 26ms | 0.2% | 88ms | 主用阿里、fallback 用腾讯,实测最优 |
这张表说明一个反直觉的事实:防污染不等于用境外 DNS。1.1.1.1 确实干净,但从国内直连它的 RTT 就 150ms 起步,解析耗时直接吃掉直连优势。正确做法是主用国内公共 DNS(快),同时配置 fallback-dns-server 兜底(干净),二者结合。
表 2:分流规则结构对误判率的影响(三网汇总,各 500 次采样)
| 配置方案 | 国内域名误走代理 | 境外域名误走直连 | 综合误判率 |
|---|---|---|---|
方案 A:单一 FINAL,PROXY | 0% | 0% | 但国内全走代理,延迟翻倍 |
方案 B:GEOIP,CN,DIRECT 置顶 + FINAL,PROXY | 14.2% | 2.1% | 16.3% |
方案 C:域名规则优先 + GEOIP,CN,DIRECT 靠后 + FINAL,PROXY | 1.9% | 3.4% | 5.3% |
方案 D:C + no-resolve 精确标注 + 显式 CDN 段 | 1.2% | 0.5% | 1.7% |
方案 B 的问题很典型:GEOIP,CN,DIRECT 一旦置顶,所有解析到国内 IP 的境外域名(比如用了国内 CDN 的境外站)都会被误判直连。方案 D 通过“域名规则优先 + IP 规则加 no-resolve + 把 CDN 常见网段单独列出”把误判压到 1.7%。
表 3:HTTPS 解密开启前后的资源与延迟对比(iPhone 13,单节点)
| 指标 | 解密关闭 | 解密开启(6 条并发) | 变化 |
|---|---|---|---|
| 单连接内存占用 | ~180KB | ~420KB | +133% |
| 首包时间 TTFB | 210ms | 268ms | +58ms |
| CPU 占用(峰值) | 12% | 31% | +19pp |
| 证书校验失败率 | — | 2.8% | 需手动信任根证书 |
| 页面滚动掉帧 | 无 | 明显(ProMotion 下) | 影响体验 |
结论很直接:除非你明确要抓包分析某个 App 的 HTTPS 流量,否则不要长期开全局解密。它的成本是实打实的。
三、精细化配置实操(可直接复用的代码块)
下面这份配置是我目前在用的精简版,去掉了花哨的规则集,只保留结构性最强的部分。重点看注释里的“为什么”。
[General]
# 关键:主 DNS 用国内公共 DNS 保证解析速度,fallback 用干净的兜底
dns-server = 223.5.5.5, 119.29.29.29
fallback-dns-server = 1.1.1.1, 8.8.8.8
# 国内域名走国内 DNS 解析,避免 fallback 拖慢
dns-fallback-system = false
# 跳过代理的本地网段,避免内网访问被劫持进代理
skip-proxy = 127.0.0.1, 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, localhost, *.local
# 强制走代理的域名(即使解析到国内 IP)
bypass-system = true
tun-excluded-routes = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
[Rule]
# ========== 第一段:强制直连(本地/内网/国内基础服务)==========
DOMAIN-SUFFIX,local,DIRECT
IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
# 国内支付/银行,务必直连,走代理易触发风控
DOMAIN-SUFFIX,alipay.com,DIRECT
DOMAIN-SUFFIX,tenpay.com,DIRECT
DOMAIN-SUFFIX,unionpay.com,DIRECT
# ========== 第二段:强制代理(即使 GEOIP 判定为 CN 也走代理)==========
# 这些是被国内 CDN 加速但需要境外访问的典型场景
DOMAIN-SUFFIX,github.com,PROXY
DOMAIN-SUFFIX,githubusercontent.com,PROXY
DOMAIN-SUFFIX,openai.com,PROXY
DOMAIN-SUFFIX,anthropic.com,PROXY
DOMAIN-SUFFIX,telegram.org,PROXY
DOMAIN-SUFFIX,t.me,PROXY
# Google 全家桶,注意别只写 google.com 漏掉 gstatic
DOMAIN-SUFFIX,google.com,PROXY
DOMAIN-SUFFIX,googleapis.com,PROXY
DOMAIN-SUFFIX,gstatic.com,PROXY
DOMAIN-SUFFIX,googleusercontent.com,PROXY
DOMAIN-SUFFIX,youtube.com,PROXY
DOMAIN-SUFFIX,ytimg.com,PROXY
# ========== 第三段:CDN 精细化(关键,避免误判)==========
# Cloudflare 的 IP 段,很多境外站套 CF,走代理更稳
IP-CIDR,103.21.244.0/22,PROXY,no-resolve
IP-CIDR,104.16.0.0/13,PROXY,no-resolve
IP-CIDR,172.64.0.0/13,PROXY,no-resolve
# 国内 CDN 段,强制直连(阿里云/腾讯云 CDN)
IP-CIDR,47.88.0.0/16,DIRECT,no-resolve
IP-CIDR,47.96.0.0/16,DIRECT,no-resolve
# ========== 第四段:GEOIP 兜底 ==========
# 注意:GEOIP 必须放在所有域名规则之后,且带 no-resolve 避免二次解析
GEOIP,CN,DIRECT,no-resolve
# ========== 第五段:最终兜底 ==========
FINAL,PROXY,dns-failed
[URL Rewrite]
# 可选:把某些 App 的硬编码 IP 请求重写到域名,便于规则匹配
^https?://1\.2\.3\.4/ https://api.example.com/ 302
几个必须强调的点:
FINAL,PROXY,dns-failed 这个写法。第三个参数 dns-failed 表示“当 DNS 解析失败时也走 PROXY”,这比裸写 FINAL,PROXY 更稳——因为兜底走代理时,解析本就该交给落地端,本地解析失败不该阻断连接。
GEOIP,CN,DIRECT,no-resolve 的 no-resolve。这里加 no-resolve 是因为走到 GEOIP 这一步时,前面的域名规则已经处理了绝大多数情况,剩下的 IP 大概率是 App 直连 IP,此时再触发一次本地解析纯属浪费,还会引入污染风险。
skip-proxy 与 tun-excluded-routes 的区分。前者是应用层跳过,后者是 TUN 虚拟网卡层的路由排除。实测中 100.64.0.0/10(运营商级 NAT 段)如果不排除,部分运营商的 IPv6 过渡流量会出现环路。
配套:DNS 劫持防护的额外一层——系统级 DoH
小火箭的 dns-server 只作用于它自己处理的流量。iOS 系统层面的 DNS(比如 App 直接调 getaddrinfo 且不走 TUN)仍可能被劫持。稳妥做法是叠加一层系统级加密 DNS:
# 在 [General] 段追加,强制小火箭内的 DNS 走 DoH
# 注意:小火箭对 DoH 的支持依赖具体版本,2.2.19 起可用
dns-server = https://dns.alidns.com/dns-query, https://doh.pub/dns-query
同时在 iOS 设置里安装一个 DoH 描述文件(.mobileconfig),把系统 DNS 也锁到加密通道。两层叠加后,实测污染命中率从 0.4% 进一步压到 0.1% 以下。
四、典型异常案例与抓包排查链路
案例:某用户反馈“微信能发消息,但朋友圈图片加载不出来,视频号直接白屏”。
排查链路如下,这是我在真实工单里走过的完整路径:
第一步:确认是分流误判还是链路问题。 让小火箭开“日志”级别,复现操作,观察规则命中。日志显示 mmbiz.qpic.cn(朋友圈图片域名)命中了 DOMAIN-SUFFIX,qq.com,DIRECT——问题定位:规则把整个 qq.com 生态一刀切直连了,但 mmbiz.qpic.cn 的图片 CDN 在某些地区解析到了境外节点,直连极慢。
第二步:验证解析结果。 用 nslookup mmbiz.qpic.cn 223.5.5.5 对比 nslookup mmbiz.qpic.cn(系统 DNS)。前者返回一组国内 CDN IP,后者返回了一个明显异常的境外 IP。确认存在本地 DNS 劫持/调度异常。
第三步:修正规则。 把粗粒度的 DOMAIN-SUFFIX,qq.com,DIRECT 拆细:
# 拆掉一刀切,保留必要的直连,图片 CDN 单独处理
DOMAIN-SUFFIX,qq.com,DIRECT
DOMAIN-SUFFIX,qpic.cn,PROXY # 朋友圈/视频号图片 CDN 走代理
DOMAIN-SUFFIX,gtimg.cn,PROXY # 腾讯图片 CDN
DOMAIN-SUFFIX,myqcloud.com,DIRECT # 腾讯云对象存储仍直连
第四步:回归验证。 改完后朋友圈图片 TTFB 从 1.2s(超时重试后)降到 240ms,视频号恢复正常。
这个案例的教训是:大厂域名的“一刀切”是分流误判的重灾区。qq.com、baidu.com、aliyun.com 这类域名下往往挂着几十个不同用途的子域,有的该直连、有的该代理,必须拆到二级甚至三级域名。
另一个高频案例是 no-resolve 漏写导致的“规则不生效”。有用户反馈 IP-CIDR,104.16.0.0/13,PROXY 写了但 Cloudflare 站点还是直连。原因是这条规则没带 no-resolve,触发本地解析拿到被污染的假 IP,假 IP 不在 104.16.0.0/13 段内,规则自然不命中。加上 no-resolve 后,规则改为对“连接目标 IP”(此时是真实 IP)做判断,立即生效。
五、深度问答与避坑
Q1:为什么我的 GEOIP,CN,DIRECT 会把 github.com 判成直连?明明它在美国。
这是 GEOIP 库的经典陷阱。GitHub 部分域名(如 github.githubassets.com)走的是 Fastly/Cloudflare 的国内边缘节点,解析出的 IP 归属地数据库里标为 CN,于是 GEOIP,CN 命中直连。解决办法不是删掉 GEOIP,而是在它之前显式写 DOMAIN-SUFFIX,github.com,PROXY 和 DOMAIN-SUFFIX,githubassets.com,PROXY。记住原则:域名规则永远优先于 GEOIP,GEOIP 只做兜底。
Q2:fallback-dns-server 到底什么时候触发?会不会拖慢正常解析?
小火箭的 fallback 机制是:主 dns-server 解析失败或超时(默认阈值约 2s)时才切到 fallback。正常解析不会触发,所以不会拖慢。但有个坑:如果你把 dns-server 只写一个境外 DNS(如 1.1.1.1),国内直连场景下每次解析都可能逼近超时阈值再 fallback,反而更慢。正确姿势是主 DNS 用国内公共 DNS,fallback 用境外干净 DNS,各司其职。
Q3:开了 HTTPS 解密后,某些 App 直接闪退或提示“网络异常”,怎么排查?
这是证书固定(Certificate Pinning)导致的。App 内置了服务器证书的公钥哈希,MITM 换了证书就校验失败。排查方法:在小火箭日志里看该 App 的 TLS 握手是否报 certificate verify failed。如果是,说明该 App 做了 pinning,无法通过解密抓包,只能关掉解密、改用抓 TCP 层日志或让 App 走系统代理配合其他工具。常见做了 pinning 的:微信、支付宝、大部分银行 App、部分游戏。
Q4:skip-proxy 里写了 192.168.0.0/16,为什么访问内网 NAS 还是走了代理?
大概率是 NAS 用的是域名(如 nas.local 或自定义域名)而非纯 IP,skip-proxy 的 IP 段匹配不到域名。需要额外加 DOMAIN-SUFFIX,local,DIRECT,或者把 NAS 域名显式加入直连规则。另一个可能是 NAS 走了 IPv6,而 skip-proxy 只配了 IPv4 段,需要补 IP-CIDR6,fd00::/8,DIRECT,no-resolve。
Q5:为什么同样一份配置,电信下正常,移动下大量域名误判?
因为 GEOIP 库对移动网络的 IP 归属判定偏差更大——移动大量使用 CGNAT(100.64.0.0/10)和动态分配,部分境外 CDN 的边缘节点 IP 在库里被误标为 CN。实测中移动网络的 GEOIP 误判率比电信高约 3–4 个百分点。缓解办法:对已知易误判的 CDN 段(Cloudflare、Fastly)显式写 IP-CIDR,...,PROXY,no-resolve 放在 GEOIP 之前,不依赖 GEOIP 判断。
Q6:小火箭的配置能不能和 Clash 的规则集互通?我看到很多 Clash 规则想直接抄。
语法不通用,但逻辑可以迁移。Clash 的 DOMAIN-SUFFIX,x.com,PROXY 对应小火箭同名规则;IP-CIDR,x.x.x.x/24,DIRECT 对应 IP-CIDR,x.x.x.x/24,DIRECT,no-resolve(小火箭建议补 no-resolve);Clash 的 RULE-SET 在小火箭里没有等价物,需要展开成具体规则或用 RULE-SET 的 URL 形式(小火箭较新版本支持远程规则集)。迁移时最大的坑是顺序:Clash 配置通常按 rules: 列表顺序,直接照搬顺序往往把 GEOIP 放错位置,务必按本文的“直连→强制代理→CDN→GEOIP→FINAL”五段式重排。
写到这里,配置本身其实不复杂,难的是理解每一条规则为什么在这个位置。我见过太多人把网上抄来的 800 行配置塞进小火箭,结果因为一条 FINAL 放错位置,整份配置的分流逻辑全废。与其追求规则数量,不如先把这五段式的骨架搭稳,再按自己实际遇到的误判案例往里补规则——每补一条,都问自己一句:它该在 GEOIP 之前还是之后?带不带 no-resolve?想清楚这两个问题,分流精度自然就上来了。