最后验证:2026-10-02T15:08:38Z

sing-box 现状审计(2026-10-02,真流量实测)

对象:正在运行的 sing-box v1.14.2-reF1nd(审计当时是 TUN 模式,/etc/sing-box/config.json,22 出站 / 9 规则集)
方法:全部为实测——DNS 回显、DoH 绕行基准、Clash API chains+rule、规则集反编译、真实 IP 连接归因。

追注(2026-10-02 23:01 全篇复核,实测):本文按时间分段保留当天记录,但运行状态已经不是 TUN——以本追注为准:
inbounds[0].type = "ebpf"(local.data_plane=cgroup + shared.data_plane=packet_rewrite,interface:["virbr0"],共享附件以 TCX 挂载);
规则集 15 份(其中 6 份来自自建包 /usr/share/sing-box-rule-sets/);出站 22 未变。
各节中已过时/被推翻的结论,均在该处加了带日期的追注(原记录保留,不改写历史)。

0. 结论速览

检查项 结果
DNS 泄露 ✅ 干净:53 端口劫持彻底(8.8.8.8/路由器/223.5.5.5/1.1.1.1 全被接管);无任何明文 53 外发;海外解析走 Cloudflare DoH(回显 172.64.212.11)、国内走 阿里 DoH(223.5.5.5:443);IPv6 无黑洞规则
广告拦截 🟡 子域层面拦得不错,但有 10 个追踪家族真漏(criteo / outbrain / scorecardresearch / quantserve / adsrvr / rubiconproject / doubleverify / appsflyer / branch / CN SDK)
分流正确性 ✅ Google/GitHub/OpenAI/Reddit/Telegram/Steam → 代理;百度/B站/腾讯/Aliyun → 直连;规则命中可溯源
geoip/cn 数据 ❌ 过期:本地 8,045 条 CIDR(9/12),上游已 9,648 条;实测阿里云 8.130.222.175 因缺段被判成走代理
节点健康 ✅ 无失败节点,延迟 146–313 ms(Auto → 新加坡Z02)
配置卫生 🟡 2 条冗余路由规则;3 个 DNS 服务器无人引用;1 条 1.14 弃用告警(1.16 移除)

追注(2026-10-02 23:01 复核):本表是当天上午审计当时的结论,下列三行已过时,均已处理:

  • 广告拦截 🟡(10 个追踪家族真漏) → 已补:新增 anti-AD(99,506 条)、adblockfilters(215,248 条域名后缀)、ads-extra(12 条),
    DNS 与连接层两条规则都接上(见 §2.3 追注、§8.1 追注);现只剩 tanx.com / uc.cn 两个 apex 刻意不拦。
  • geoip/cn 过期 → 已修:route.rules 现在用 ["geoip/cn","geoip/cn-fresh","cncidr-mihomo"] 三份表。
    实测 8.130.222.175:geoip/cn(8,045 条)不命中;geoip/cn-fresh(9,648 条)命中 8.130.0.0/15;cncidr-mihomo(17,958 条)命中 8.128.0.0/10 → 现已 direct(见 §8.2)。
  • 配置卫生三项全部处理完:2 条冗余路由规则已删(现 route.rules 共 11 条,无 protocol quic + port 443、无无条件 → Proxy);
    闲置的 DNS 服务器已改成故障转移组并启用(dns-proxy / dns-direct,且 dns-local 已删除);
    external_ui* 已移除,experimental.clash_api 只剩 cache_file / clash_api——实测 journal 里那条弃用 WARN 只出现在 09:32(修复前)的一次启动。
  • (”DNS 泄露”那行的 ✅ 结论仍然成立,但两条证据细节已变:那时”返回空”其实是 REFUSED、现在是真的 NXDOMAIN;出口回显现在是 IPv6 的 Cloudflare 地址。详见 §8.4 / §10.2 追注。)

1. DNS 泄露(实测证据)

① 53 端口劫持:dig @8.8.8.8 / @192.168.31.1 / @223.5.5.5 / @1.1.1.1 doubleclick.net
   → 四个全部返回空(说明查询都进了 sing-box 的 DNS 规则,而不是真的发到那些解析器)

② 出口回显:dig TXT o-o.myaddr.l.google.com
   → 系统解析器与 @1.1.1.1 都返回 172.64.212.11(Cloudflare 的解析出口)
   → 海外域名由 Cloudflare DoH 经代理解析,ISP 看不到

③ 明文 DNS:ss -un / -tn 里没有任何指向外部 :53 的套接字
   → 没有 UDP/53 明文外发(sing-box 走 DoH over 443)
   现存 53 监听者只有本机服务:127.0.0.53/54(systemd-resolved)、192.168.122.1(libvirt)

④ 国内解析:ss 显示到 223.5.5.5:443 的 DoH 连接
   → 国内域名走阿里 DoH(不是明文 UDP,比之前那版更强)

⑤ resolvectl:tun0 = 172.19.0.2 / fdfe:dcba:9876::2,DNS Domain ~.
   → 系统解析器 100% 指向隧道
   (**追注 2026-10-02 23:01:这是 TUN 模式下的状态,现已不适用**——当前 eBPF 模式没有 tun0,
   `resolvectl` 里 wlan0 用 `192.168.31.1` 与宿主路由器的 ULA 网关(`fd00:6868:…::1`),解析器不再指向隧道;
   劫持改由 eBPF 在 53 端口完成,实测 `dig @8.8.8.8 doubleclick.net`、`dig @223.5.5.5 doubleclick.net` 都立刻返回 NXDOMAIN,拦截仍有效。)

唯一残留面:dns.servers 里那个 dns-local(type: local)现在没人引用;一旦将来被误引用,就会绕过 DoH 走系统解析器 → 建议直接删掉。
(追注 2026-10-02 23:01:已删除——当前 dns.servers 共 7 条,无 dns-local。)

一个天然限制(你已规避):hijack-dns 只覆盖 53 端口/dns 协议,浏览器自作主张的 DoH(443)不经过 sing-box 的 DNS 规则。
好在你的广告拦截同时写在 route.rules 里(连接层按 sniff 出的域名 reject),所以即使用户开浏览器 DoH,
广告连接照样被 RST —— 这个”双写”设计是对的,别只留 DNS 那一条。

2. 广告拦截(覆盖率实测)

方法:dig(走 sing-box 的 DNS 规则)对比 绕开 sing-box 的 Cloudflare DoH(基准真相,因为 443 不在 hijack-dns 范围内)。

2.1 apex 域名层:36 个测试,拦 19 / 漏 13

漏拦:criteo.com、outbrain.com、scorecardresearch.com、quantserve.com、adsrvr.org、
rubiconproject.com、doubleverify.com、branch.io、appsflyer.com、admaster.com.cn、
tanx.com、umeng.com、miaozhen.com

2.2 但 apex 不等于真实端点——子域层复测

类型 结果
✅ 拦得住 *.umeng.com(ulogs/alogs/ulog/log/r)、atm.youku.com、ad.m.iqiyi.com、t7z.cupid.iqiyi.com、dsp.miaozhen.com、app.adjust.com、cm.admaster.com.cn、jp.tanx.com、acs.tanx.com、cm.branch.io、cnzz.com.edgekey.net
❌ 真漏 dis/static/gum.criteo.com、amplify/tr/log.outbrain.com、sb/b.scorecardresearch.com、pixel/secure.quantserve.com、match/js.adsrvr.org、pixel/token.rubiconproject.com、tps/cdn.doubleverify.com、app/t/api2.appsflyer.com、api2/app.branch.io、sdkmob.com、ad.duowan.com、uc.cn、aly.admaster.co

2.3 修复方案(已验证)

现有两条规则集 geosite/category-ads-all(910 条)+ Ads_AWAvenue(合法 SRS\x02,反编译失败只因它是 AdGuard 语法编译来的)覆盖不到这些追踪家族。

候选补充表的实测覆盖(14 个漏拦域名):

候选表 规则数 覆盖 体积
anti-AD 99,506 13/14 2.2 MB
AdGuard DNS filter 177,432 12/14 4.3 MB
hagezi Pro 227,618 13/14 5.1 MB

推荐 anti-AD(国内维护、体积最小、覆盖最全)。这个 fork 自带转换器,一条命令离线生成:

curl -sL -o /tmp/antiad.txt https://anti-ad.net/adguard.txt
sing-box rule-set convert /tmp/antiad.txt -t adguard -o anti-ad.srs   # 0.3 秒,产出 770 KB

已实测:源文件 anti-ad.txt 有 99,506 条非注释行,生成的 anti-ad.srs 为 770,834 B,加入配置后 sing-box check 通过 ✅。(口径提醒:99,506 是源文件的条数;.srs 是 AdGuard 二进制格式,sing-box rule-set decompile 会直接 FATAL,所以编译后的条数无法核对 —— 别把源文件条数当成 .srs 里的条数。)

追注(2026-10-02 23:01:本方案已落地,但落地方式与下文不同):anti-AD 不是手工 sudo cp 到 /etc/sing-box/rule-set/,
而是随自建包 sing-box-rule-sets(现装 20261002-2)装到 /usr/share/sing-box-rule-sets/anti-ad.srs(770,834 B)。
实测源文件 /home/emo/.cache/sing-box-rule-sets/anti-ad.txt(2,199,016 B)= 99,506 条非注释行,其中 99,457 条 ||域名^ 规则 ⇒ 99,506 这个数字可复现。
实际还多接了两份表:adblockfilters(217heidai,反编译得 215,248 条域名后缀;与 anti-AD 精确交集 75,382 条 = 占 adblockfilters 的 35%,独有 139,866 条)
和 ads-extra(自制,12 条:ad.duowan.com / ads.wps.cn / sdkmob.com)。
两条拦截规则的 rule_set 现为 ["geosite/category-ads-all","Ads_AWAvenue","anti-AD","ads-extra","adblockfilters"],
其中 DNS 那条用的是 predefined + rcode: NXDOMAIN,不是 reject(见 §10.2)。
(另:geosite/category-ads-all 反编译实测 910 条 = 162 domain + 748 suffix + 66 regex,与 §2.3 一致。)

⚠️ branch.io / appsflyer.com 是移动归因 SDK,拦了可能影响某些 App 的深链跳转;不想影响就从表里排除。

3. 分流正确性(Clash API 证据)

每条连接都带 chains(出口链)与 rule/rulePayload(命中的规则):

目标(sniff 出的真实 host) 出口 命中规则
www.google.com / chat.openai.com Proxy → Auto → 日本Z01 [geosite/google geosite/category-ai-!cn] => route(Proxy)
github.com / www.reddit.com / t.me / www.microsoft.com / shared.valve.map.fastly.net Proxy → Auto geosite/geolocation-!cn => route(Proxy)
www.a.shifen.com(百度) / a.w.bilicdn1.com(B站) / ins-r23tsuuf.ias.tencent-cloud.net(腾讯) direct geosite/cn => route(direct)
123.57.116.48 / 183.2.172.177 / 121.14.77.201 / 223.5.5.5 / 47.98.147.100 direct geoip/cn => route(direct)
8.8.8.8 / 104.16.132.229 Proxy route(Proxy)(final)
8.130.222.175(阿里云杭州) ❌ Proxy final(geoip/cn 缺这个段)

追注(2026-10-02 23:01 复核):上表是审计当时的抓样,其中两处已变:
① 最后一行 8.130.222.175 已修(现在命中 geoip/cn-fresh/cncidr-mihomo → direct,见 §8.2);
② 第 4 行现在命中的规则集是 ["geoip/cn","geoip/cn-fresh","cncidr-mihomo"](route.rules 第 11 条,实测确认)。
另外 Proxy → Auto 的落地节点是动态的(当前 Auto 选中的是 🇭🇰 香港Z02,不是表里的日本Z01/新加坡Z02)——
表格里的节点名只代表当时那一刻,规则命中部分(geosite/google、geosite/geolocation-!cn、geosite/cn、final=Proxy)与当前配置一致。

4. 问题清单与改法(按优先级)

P1 —— 广告覆盖缺口

加 anti-AD 规则集(~/.cache/sing-box-rule-sets/anti-ad.srs 已生成,待 sudo cp 到 /etc/sing-box/rule-set/):
(追注 2026-10-02 23:01:已落地,但路径/清单与此处不同——实际随自建包装到 /usr/share/sing-box-rule-sets/anti-ad.srs,
且 rule_set 列表还多了 ads-extra、adblockfilters;DNS 侧用的是 predefined/NXDOMAIN,不是 reject。见 §2.3 追注。)

// route.rule_set 追加
{ "type": "local", "tag": "anti-AD", "format": "binary", "path": "/etc/sing-box/rule-set/anti-ad.srs" }

// dns.rules 第 1 条 与 route.rules 第 4 条,rule_set 都追加 "anti-AD"
{ "rule_set": ["geosite/category-ads-all", "Ads_AWAvenue", "anti-AD"], "action": "reject" }

P1 —— geoip/cn 过期(实测 8.130.222.0/24 走了代理)

上游最新 geoip/cn.srs 含 9,648 条 CIDR 且包含 8.130.0.0/15(本地那份 8,045 条缺)。两条路:

  1. 刷新系统包:sudo pacman -Syu sing-geoip-rule-set(当前 20260912,落后 3 周);
  2. 本地双保险:把上游最新文件放 /etc/sing-box/rule-set/geoip-cn-fresh.srs,加第二个 local 规则集,路由里改成
    "rule_set": ["geoip/cn", "geoip/cn-fresh"]。

长期方案(很契合你已有的仓库):在 ~/pkgbuild-source 里加一个 sing-box-rule-sets 包,
用 CI 定时拉 MetaCubeX geosite/geoip + anti-AD,转成 .srs 打包安装 —— 规则集就跟系统更新一起滚动,不用手工维护。

追注(2026-10-02 23:01:以上两条都已落地)

  • 方案 2(本地双保险):/usr/share/sing-box-rule-sets/geoip-cn-fresh.srs 实测 9,648 条且含 8.130.0.0/15;路由里已用 ["geoip/cn","geoip/cn-fresh","cncidr-mihomo"]。
  • geoip/cn 本体未变:sing-geoip-rule-set 20260912-1,8,045 条,实测不含 8.130.222.175。
  • 方案 1(刷系统包)未做:pacman -Q sing-geoip-rule-set 仍是 20260912-1。
  • 长期方案也已实现:~/pkgbuild-source/packages/sing-box-rule-sets(见 §9、§6 第 6 项)。

P2 —— 1.14 弃用告警(1.16 会移除)

WARN legacy `external_ui_download_detour` Clash API option is deprecated in sing-box 1.14.0 and will be removed in 1.16.0.

正好和「去掉 MetaCubeXD」一起处理(见 singbox-panel-guide.md §9)。

追注(2026-10-02 23:01:已解决):experimental.clash_api 现在只有 cache_file(= /var/lib/sing-box/cache.db)与 clash_api 两项,
不再有 external_ui*;curl http://127.0.0.1:9090/ui/ 实测 404。
journal 里该 WARN 只在 09:32:44(修复前的那次启动)出现过一次,此后无。

P2 —— 3 个 DNS 服务器无人引用 + 可做故障转移

dns-proxy-google、dns-direct-tencent、dns-local 都没被任何规则引用。
(追注 2026-10-02 23:01:此句已过时,本方案已落地 —— 见下方代码块后的追注。)
这个 fork 实测支持 type: group(上游 1.14 不支持,fork 有),所以可以把它们变成故障转移组:

{ "type": "group", "tag": "dns-proxy",  "servers": ["dns-proxy-cf", "dns-proxy-google"] },
{ "type": "group", "tag": "dns-direct", "servers": ["dns-direct-ali", "dns-direct-tencent"] }
// dns.final 与相关 dns.rules 改指向这两个组;dns-local 直接删除(潜在泄露面)

追注(2026-10-02 23:01:已启用):配置里已有
{"type":"group","tag":"dns-proxy","servers":["dns-proxy-cf","dns-proxy-google"]} 与
{"type":"group","tag":"dns-direct","servers":["dns-direct-ali","dns-direct-tencent"]};
dns.final = "dns-proxy",dns.rules 里国内走 dns-direct、海外走 dns-proxy;
dns-local 已删除(dns.servers 共 7 条:hosts-bootstrap / dns-proxy-cf / dns-proxy-google / dns-direct-ali / dns-direct-tencent / dns-proxy / dns-direct)。

P3 —— 两条冗余路由规则

route.rules 第 11 条(protocol quic + port 443 → Proxy)与第 12 条(无条件 → Proxy)都与
route.final = Proxy 重复,可删(少两条规则匹配)。
(追注 2026-10-02 23:01:已删除 —— 当前 route.rules 共 11 条,实测无 protocol quic + port 443、也无无条件 → Proxy 的规则。)

P3 —— 可选性能优化

store.steampowered.com / cdn.steamstatic.com / www.microsoft.com 现在都走代理(geolocation-!cn)。
本地规则集里已有 geosite-steam@cn.srs(还有 microsoft 相关),若你下载 Steam 游戏/Office 觉得慢,
可加一条 geosite/steam@cn → direct(CDN 直连通常快数倍)。这是偏好项,不是问题。

5. 这个 fork 实测支持、上游 1.14 没有的能力

特性 结果 用途
dns.servers[].type = "group" ✅ DNS 故障转移组(上游 1.14 无)
outbound.type = "anytls" ✅ 新协议
rule_set.type = "remote" ✅ 规则集自动更新(配合 cache_file,不必每次启动联网)
dns.servers[].type = "hosts" ✅ DoH 域名预解析(你已在用)
sing-box rule-set convert -t adguard ✅ AdGuard 语法 → .srs(本报告 P1 就靠它)
sing-box rule-set decompile ✅(AdGuard 源除外) 规则集内容审计

6. 建议落地顺序(含当前状态)

已打包成脚本:apply-audit-fixes.sh(P1+P2 默认执行;--with-cncidr / --with-direct-list / --with-dns-groups 为可选增强)。
流程与安全管线已在本机干跑验证:四种开关组合产出的配置 sing-box check 全部通过。

# 动作 状态
1 anti-AD 规则集(补 10 个追踪家族):装文件 → DNS 与路由两条 reject 规则都加 anti-AD ✅ 已完成(走自建包 sing-box-rule-sets 装到 /usr/share/sing-box-rule-sets/,并多加 adblockfilters、ads-extra;DNS 侧为 predefined/NXDOMAIN)
2 刷新 geoip/cn:优先 pacman -Syu sing-geoip-rule-set;脚本默认用上游最新 .srs 做双保险(含 8.130.0.0/15) ✅ 已完成(geoip/cn-fresh + cncidr-mihomo 已生效,8.130.222.175 复测为 direct,见 §8.2;系统包仍未刷,仍是 20260912-1)
3 清理:删 dns-local、删与 final 重复的 2 条路由规则、去掉 external_ui* ✅ 全部完成(实测 dns.servers 无 dns-local、route.rules 11 条无冗余项、clash_api 无 external_ui*、/ui/ → 404)
4 DNS 故障转移组(可选):把闲置的 dns-proxy-google / dns-direct-tencent 变成组 ✅ 已启用(dns-proxy / dns-direct 组已在配置里并被 dns.final/dns.rules 引用)
5 mihomo 增强(可选):cncidr 第三份国内 IP 表 + STUN/主机/LAN cache 直连清单 ✅ 已启用(cncidr-mihomo 17,958 条 + must-direct 33 条,均在配置中)
6 长期:pkgbuild 仓库加 sing-box-rule-sets 包,CI 定时拉 MetaCubeX + anti-AD + mihomo .srs ✅ 已完成(包已发布安装:sing-box-rule-sets 20261002-2;CI 工作流 refresh-rule-sets.yml 已在仓库中,见 §9)

追注(2026-10-02 23:01 复核):上表 6 项状态由”待执行/待规划”更新为实测结果(本表标题即”含当前状态”,故直接改写单元格;原始结论见 §8/§9/§12 的时间线)。
第 5 项的 --with-cncidr --with-direct-list 两个产物都能在配置里找到对应文件;第 6 项的每日刷新已用手动触发验证过一次(§9)。

7. 外部规则源评估:HenryChiao/mihomo_yamls(用户提供)

实测结论:这个仓库能直接供 sing-box 用(不是只有 mihomo 格式),而且有两点很有价值。

7.1 它发布现成的 sing-box 规则集

ruleset 分支下 singbox/version1..version4/ 各 104 个分类(adrules / ai / cn / cncidr / gfw / games / microsoft-cn / apple-cn / private / proxy / netflix / spotify …),
每个分类同时给 .json(源)与 .srs(二进制),每天 07:30(UTC+8) 自动重建。

实测:四个 version 在你这台 sing-box 1.14.2-reF1nd 上全部能加载(version1 是较啰嗦的老格式 1821 KB,v2–v4 均 1671 KB)。

7.2 广告表对比:它的 adrules 略逊于 anti-AD

拿之前实测的真漏端点逐个比对:

目标 mihomo adrules(197,019 条) anti-AD(99,506 条)
criteo / outbrain / scorecardresearch / quantserve / adsrvr / rubicon / doubleverify / appsflyer / branch ✅ ✅
sdkmob.com / ad.duowan.com / uc.cn(国内 SDK) ❌ ✅
小计 11/14 14/14

→ 主广告表仍用 anti-AD;adrules 的互补价值在于它含 IP 类广告规则(anti-AD 只有域名),可作为第二阶段补充。

7.3 它的 cncidr 正好补上你 geoip 的洞

cncidr(version4, 79 KB)有 17,958 条 CIDR,且包含 8.128.0.0/10(覆盖你实测漏掉的 8.130.222.0/24)。
→ 已采纳为脚本的 --with-cncidr:给国内 IP 匹配加第三份独立来源(本地 geoip/cn + 上游最新 + mihomo cncidr)。

7.4 从它的 178 份 YAML 里提炼出的可借鉴点

它的做法 对你这套的意义
域名规则在前、IP 规则在后 ✅ 你已满足(geosite 规则都在 geoip 之前)
fake-ip-filter 里塞了 lancache.steamcontent.com、*.stun.*、xbox.*、srv.nintendo.net、dns.msftncsi.com ⚠️ 能改进:这些走代理会坏功能(LAN 缓存失效、主机 NAT 类型变差)→ 已提炼成 must-direct.srs,脚本 --with-direct-list 一条直连规则搞定
proxy-server-nameserver(解析节点域名用国内 DoH) ✅ 你等价物是 route.default_domain_resolver = dns-direct-ali(且节点是 IP,本就不受影响)
nameserver-policy 按域名分流解析器 ✅ 你等价物是 dns.rules(且你用的是真 IP + DoH,比 fake-ip 更干净)
走 no-resolve 减少无谓解析 sing-box 里由 sniff + 规则顺序天然覆盖

7.5 长期怎么吃这个仓库(两条路)

  1. remote rule_set + update_interval:sing-box 支持 {"type":"remote","url":"https://raw.githubusercontent.com/HenryChiao/mihomo_yamls/ruleset/singbox/version4/xxx.srs","download_detour":"Proxy"},
    配合 cache_file 只在过期时联网(你配置里的 http_clients / route.default_http_client 正好能复用为下载出口);
  2. (更契合你)在自己的 pkgbuild 仓库里加包:CI 定时拉这些 .srs + anti-AD,转好并安装到 /etc/sing-box/rule-set/,
    规则集就跟系统更新一起滚动 —— 比你手工下载可靠,也留痕。

8. 复测结果(2026-10-02 10:12 应用后实测)

应用脚本执行成功:备份 /etc/sing-box/backups/config.20261002-101233.pre-audit.json,
check 通过,健康检查通过(经代理出口 <proxy-exit-ip>、4 个测试域名全拦)。以下为独立复测。

8.1 广告拦截:真漏追踪端点已拦住

目标 应用前 应用后
dis.criteo.com / tr.outbrain.com / sb.scorecardresearch.com / pixel.quantserve.com ❌ ✅
match.adsrvr.org / pixel.rubiconproject.com / tps.doubleverify.com ❌ ✅
app.appsflyer.com / api2.branch.io ❌ ✅
apex:criteo / outbrain / scorecardresearch / quantserve / adsrvr / rubiconproject / doubleverify / branch / appsflyer / admaster / umeng / miaozhen ❌ ✅
对照组 google / github / baidu / bilibili / qq / taobao / zhihu / weixin 正常 ✅ 仍正常(无误伤)

仍剩 4 个(性质已查清):

域名 结论
tanx.com anti-AD 只有子域规则(`
sdkmob.com anti-AD 0 条规则 → 真漏(国内 SDK 域名,可选补)——追注 2026-10-02 23:01:已补,ads-extra.srs 的 domain_suffix 含 sdkmob.com,实测 dig sdkmob.com → NXDOMAIN
ad.duowan.com anti-AD 不含 ad.duowan.com(它有的是 a.v./gt./market./other.v./playstats.v./stats.v./vupload.duowan.com 共 7 条)→ 真漏(可选补)——追注 2026-10-02 23:01:已补,ads-extra.srs 的 domain 含 ad.duowan.com,实测 → NXDOMAIN
uc.cn 我之前测量有误:源文本子串匹配把 nnuc.cn/open-uc.cn 当成了 uc.cn;按规则语义它其实从未被覆盖。而且 uc.cn 是 UC 浏览器整个 apex,拦了会误伤 → 建议只拦其广告子域,不拦 apex(追注 2026-10-02 23:01 复测:dig uc.cn → NOERROR,确实未被拦;adblockfilters 215,248 条后缀里没有** apex uc.cn,只有 19 条 *.uc.cn 子域——与”只拦广告子域、不拦 apex”的建议一致**)

更正:本报告 §2.3 里”anti-AD 覆盖 13/14”是基于源文本子串匹配的宽松估计;
按规则语义精确复测,anti-AD 覆盖的是上面列出的追踪端点集合(21/25 端点),并非全部。

追注(2026-10-02 23:01):上面”仍剩 4 个”里 sdkmob.com / ad.duowan.com 已由 ads-extra.srs 补齐(实测两者都返回 NXDOMAIN);
现在刻意不拦的只剩 tanx.com / uc.cn 两个 apex。adblockfilters(215,248 条)也已接入,但同样不含这两个 apex。

8.2 geoip 洞已修

修复前: 8.130.222.175 → Proxy(final)
修复后: 8.130.222.175 → direct  rule_set=[geoip/cn geoip/cn-fresh cncidr-mihomo] => route(direct) ✅
对照  : 8.8.8.8 → Proxy ✅(没有把国外 IP 误判成国内)

8.3 must-direct 规则生效

lancache.steamcontent.com → direct  rule_set=must-direct => route(direct) ✅
store.steampowered.com    → Proxy   rule_set=geosite/geolocation-!cn ✅(对照,未被误伤)

8.4 DNS 仍无泄露(复测)

  • @8.8.8.8 / @192.168.31.1 / @223.5.5.5 查 doubleclick.net 全部返回空(53 仍被完全劫持);
  • 出口回显 o-o.myaddr.l.google.com → 172.64.212.11(Cloudflare,仍走代理解析);
  • dig +short doubleclick.net → 空。

追注(2026-10-02 23:01 复测):拦截仍然有效,但两个细节已变:
① 那时”返回空”其实是 REFUSED(§10.2 更正过),现在 DNS 广告规则已改成 predefined/NXDOMAIN,
dig @8.8.8.8 doubleclick.net → status: NXDOMAIN(合法应答,不再是拒绝/超时);
② 出口回显现在返回的是 IPv6 地址 2400:cb00:1176:1024::a29f:610b(仍是 Cloudflare 段,结论不变)。

8.5 IPv6 与健康

  • curl -6 https://ipv6.google.com → 200,出口 <proxy-exit-ip>(走代理)✅;
    ip -6 rule 无 unreachable 黑洞 ✅。

    注:脚本里原先那条复测命令误写成 curl -4(AAAA-only 站点永远打不开),已修正为 -6。

  • 服务 active,systemd 计内存 42.9M → 50.3M(+7.4 MB,来自 4 份新增规则集),无规则集加载告警。

追注(2026-10-02 23:01 复测):内存已远高于当时。实测 ActiveState=active、NRestarts=0、已运行约 1h18m:
systemctl show sing-box -p MemoryCurrent → 104,726,528 B ≈ 99.8 MiB(MemoryPeak 108,077,056 B),/proc/<pid>/status 的 VmRSS → 118,944 kB ≈ 116 MiB。
也就是说”50 MB 级 / 75 MB 级”都已不成立,随规则集与运行时长增长。
ip -6 rule 仍只有 0 / 32766,无黑洞 ✅;ip rule 只有 0 / 32766 / 32767、tun0 不存在(当前是 eBPF 模式)。

8.6 一个未解释的观察(待留意,非配置 bug)

重启后日志有两条 74.125.250.129:443/:80 using outbound/direct: i/o timeout。
已排查:三份 CN 规则集(geoip/cn、geoip/cn-fresh、cncidr-mihomo)都不含该 IP,
google.cn / gstatic.com 也都正确走 Proxy。即不是新数据的误判;不可复现,先记录观察。

9. 收尾状态(2026-10-02 10:35,三项全部就绪/已验证)

项 状态 证据
补充规则集打包 ✅ 已推送并发布 91f52ee;CI 全绿;私有仓库已含 sing-box-rule-sets-20261002-1
规则集日更自动化 ✅ 已实测(提前用手动触发验证) 触发 refresh-rule-sets.yml → 11 秒完成 → 下载三源哈希一致 → No rule-set source changed → updated=false → 「Commit and push」步骤 skipped → 远端 main 仍是 91f52ee(无多余提交)
DNS 故障转移组(P3) ✅ 已启用(原记”🟡 待执行”) apply-audit-fixes.sh --with-dns-groups 的产物已在 /etc/sing-box/config.json:dns-proxy/dns-direct 两个组 + dns.final=dns-proxy(该 fork 支持 type: group,上游 1.14 不支持)
eBPF 切换 ✅ 已执行(原记”🟡 待执行”) switch-to-ebpf.sh;10:37 切到 eBPF,当天夜间再加挂 shared(packet_rewrite on virbr0)。当前 inbounds[0].type=ebpf,tun0 不存在
广告端点覆盖 ✅ 24/26 剩 tanx.com/uc.cn 两个 apex 刻意不拦(sdkmob.com/ad.duowan.com 后由 ads-extra 补齐,见 §8.1)
规则集加载 ✅ 15 份全通 6 份来自 /usr/share/sing-box-rule-sets(包):anti-ad / geoip-cn-fresh / cncidr-mihomo / must-direct / ads-extra / adblockfilters

追注(2026-10-02 23:01 复核):本节是 10:35 时的状态快照,上表已按实测更新三处(DNS 组、eBPF 切换、规则集计数 14→15 份、包内 5→6 份)。
另:包版本也已前进——本节写的 sing-box-rule-sets-20261002-1,当前装的是 20261002-2(pacman -Q sing-box-rule-sets);
91f52ee 这个 commit 在 ~/pkgbuild-source 里存在(git log 可见,CI 工作流 refresh-rule-sets.yml 也在)。

9.1 eBPF 切换脚本的健康判据(与 TUN 模式不同)

switch-to-ebpf.sh 不使用「等 tun0」这种 TUN 判据,而是:

  1. systemctl is-active
  2. sing-box api ebpf --url http://127.0.0.1:9091 --secret <clash密钥> → inbounds 非空(附件真的挂上了)
  3. 不设任何代理的 curl https://api.ipify.org 必须等于经本地代理口的出口(这是”内核接管成功”的铁证)
  4. curl https://myip.ipip.net 仍返回国内电信 IP(分流没坏)
  5. dig doubleclick.net / tr.outbrain.com 仍为空(DNS 劫持没坏)
  6. 任一失败 → 自动回滚到备份的 TUN 配置并重启

同理 apply-audit-fixes.sh 的健康检查也已改为模式感知:配置里是 tun 才等 tun0,是 ebpf 就改用第 3 条做判据。

10. eBPF 模式切换完成 + 测量方法更正(2026-10-02 10:38)

sudo ./switch-to-ebpf.sh 于 10:37:51 切换成功(第一次尝试因我的 DNS 判据读了过渡期缓存而误判回滚,
判据已改成”随机子域 + 直连解析器 + 对照域名 + 重试 20s”,第二次全部通过)。

10.1 eBPF 模式实测结果(全部通过)

检查项 结果
TUN 设备 tun0 已消失 ✅
路由残留 ip rule 只剩 0/32766/32767,无 9000/9001/9002 残留;table 2022 空 ✅
IPv6 黑洞 0 条 ✅
eBPF 附件 api ebpf → 1 个活动入站,attachments: cgroup @ /sys/fs/cgroup;内核程序 sb_ebpf_conn4(CGroupSockAddr)、sb_self_create/release(CGroupSock) ✅
内核接管 不设任何代理的 curl api.ipify.org == 经本地代理口的出口 ✅(铁证)
分流 国内 → 电信 IPv6 直连;海外 → 代理出口 <proxy-exit-ip> ✅
IPv6 curl -6 ipv6.google.com → 200 ✅
DNS 劫持 直连 3 个解析器(8.8.8.8 / 路由器 / 223.5.5.5)查随机广告子域 → 全部被拒 ✅;出口回显仍是 Cloudflare(172.64.214.12)✅
计数器 所有 eBPF 计数器为 0、无失败项、日志无告警 ✅
资源 内存 50→63 MB(eBPF 程序/map),可接受
面板 mode=ebpf;/api/ebpf/policy 现在 present=true(分应用可用了),应用列表 10 条 ✅

追注(2026-10-02 23:01 实测):

  • “eBPF 附件”这一行已变化:现在 sing-box api ebpf 返回 2 个附件 —— role=local mechanism=cgroup @ /sys/fs/cgroup
    与 role=shared mechanism=tcx @ virbr0(localDataPlane=cgroup、sharedDataPlane=packet_rewrite、sharedEnabled=true、state=normal、lastError="")。
    本表只记了 local 那一个,是因为加挂 shared 是当天夜间的事(见 §11、§12 追注)。
  • 计数器仍全 0 ✅(assignmentLookupFailures / tcSocketLookupFailures / rewriteFailures / recovery* 等实测均为 0)。
  • “资源 50→63 MB”已过时:现在 MemoryCurrent ≈ 99.8 MiB、VmRSS ≈ 116 MiB(见 §8.5 追注)。
  • “面板”这一行本次未能验证:面板 API 在 127.0.0.1:9096,无令牌访问 /api/ebpf/policy 返回 401;
    9091 上同名路径是 302 跳到 /dashboard/。所以 present=true 与”应用列表 10 条”未复核。

10.2 ⚠️ 测量方法更正(重要)

本文档前面所有”DNS 拦截 = ✅”的判定,用的是”dig 无输出”,而我把 dig 的 stderr 丢掉了 ——
于是”REFUSED“和”超时“看起来一模一样。带完整输出复测后真相是:

查询路径 实际结果
直连任意解析器(dig @8.8.8.8 / @223.5.5.5 / @192.168.31.1 doubleclick.net) status: REFUSED(立刻返回,是合法 DNS 响应)
经 systemd-resolved(dig doubleclick.net) communications error to 127.0.0.53#53: timed out;resolvectl 自己说 server or network returned error: REFUSED

即:sing-box 的 DNS reject 语义是 REFUSED,而 systemd-resolved 不会把 REFUSED 转告客户端
(它当作”上游故障”去重试其他上游,最终放弃),于是走系统解析器的应用每个广告域名要等约 5 秒才失败。
这不影响”拦截有效”的结论(广告域名确实拿不到解析),但影响体验,且 TUN 模式同样存在(只是以前没测出来)。

修法:DNS 广告规则由 reject(REFUSED) 改为 predefined + rcode: NXDOMAIN ——
NXDOMAIN 是正常应答,resolved 会如实转告,应用立刻失败并做负缓存。该 fork 已实测支持该写法:
apply-audit-fixes.sh --nxdomain-ads(干跑 check 通过)。

追注(2026-10-02 23:01 实测:本节问题已修复,表内 REFUSED/超时已不复现):
修法已应用 —— 当前 dns.rules[0] 就是 {"rule_set":["geosite/category-ads-all","Ads_AWAvenue","anti-AD","ads-extra","adblockfilters"],"action":"predefined","rcode":"NXDOMAIN"}。
复测(带完整输出):

  • dig @8.8.8.8 doubleclick.net → status: NXDOMAIN(@223.5.5.5、@192.168.31.1 同为 NXDOMAIN),立刻返回;
  • 经 systemd-resolved(dig doubleclick.net)→ 直接 status: NXDOMAIN,不再有 5 秒超时;
  • 对照 dig @8.8.8.8 example.com → NOERROR(劫持只命中广告规则里的域名,不是把 53 全拒)。
    本节记录的”REFUSED 被 resolved 吞掉”是修复前的历史现象,保留作记录。

11. eBPF 数据面实测(2026-10-02 13:0x 重要更正)

⚠️ 本节原标题写的是「本机只能用 local cgroup(tc / shared 均不可用)」—— 那是错的。
当时只实测了 local tc(失败),然后推断「shared 也是 TC 路径所以不可用」,
而且 shared 从未真跑过(脚本卡在「找不到 podman 网桥」就退出了)。
官方文档写明:四条数据面互相独立,一条不可用不能推断另一条。
以 root 权限重新逐条探测后的真实结果:

数据面 探测结果(sudo sing-box tools ebpf status … --json)
local cgroup preflight_passed(PASS 37 / FAIL 0 / UNKNOWN 0)
local tc ❌ 实测启动失败:register TC eBPF TCP listener: operation not supported
shared packet_rewrite ✅ preflight_passed(PASS 33 / FAIL 0 / UNKNOWN 0)
shared socket_assign ✅ preflight_passed(PASS 31 / FAIL 0 / UNKNOWN 0)

更正后的结论:本机只是 local tc 这一条不可用,两条 shared 都可用
→ eBPF 模式下容器/虚拟机可以被接管(这才是「接管本机与共享网络流量」的完整形态),
不必为了容器/VM 覆盖切回 TUN。探测必须用与服务相同的权限(普通用户跑只会得到一堆 unknown)。

11.0 原始记录(保留:怎么错的比结论更值得记)

追注(2026-10-02 晚):下面这段的前提有误 —— 「pasta 不创建宿主 socket」已被实测推翻
(ss -tnp 能看到 passt.avx2 的 ESTAB socket);当时以为「不支持的 TC 路径」后来也证明
共享数据面(shared)可用,只有 local 的 tc 模式不行。原始推理保留在此当反面教材。

为覆盖 rootless 容器(当时认为 pasta 把包 splice 进宿主栈、不创建宿主 socket,cgroup socket 钩子看不到),
试了两条路,都撞在同一处内核能力上:

FATAL start service: start inbound/ebpf[ebpf-in]:
      register TC eBPF TCP listener: operation not supported
试验 结果
local.data_plane: tc ❌ sing-box 启动即 FATAL(上面那行),API 9091 与本地代理口 7892 都连不上;脚本自动回滚成功,服务已恢复
shared(data_plane: packet_rewrite) ❌ 同一条 TC 路径,不必再试(我在 enable-container-proxy.sh 里加了守卫直接拦下)

11.1 预检为什么没提前发现(重要)

预检模式 结果 含义
--mode local(我脚本原先只跑这个) preflight_passed,PASS 37 / FAIL 0 / UNKNOWN 0 local cgroup 路径确实全支持 ✓(所以这个结论没错)
--mode all(含 shared/tc 那 28 项) inconclusive:pass 13 / warn 2 / unknown 36,required_issues 28 TC 支持无法用预检确认,只能实跑才知道

即:--mode local 全绿 不代表 TC 可用。两个脚本现在都会在请求 tc/shared 时先跑
--mode all,非 passed 就拦下并给出替代方案(--force 可强行尝试)。

11.2 容器的最终结论(已实测)

容器网络 出口 结论
rootless 默认(pasta) 失败(境外直连被墙) ❌ 无法被接管 —— 原解释「pasta 不创建宿主 socket」已被实测推翻(ss -tnp 可见 passt.avx2 的 ESTAB socket),真正卡点未确证,见 docs/09 §6
--network=host <proxy-exit-ip> = 代理出口 ✅ 可行(容器直接用宿主 socket)
rootful 网桥 + shared <proxy-exit-ip> ✅ 可行(2026-10-02 晚复测:shared=packet_rewrite on virbr0,直连与代理两条路都通过)

修正后的结论:本机(kernel 7.2.8-1-cachyos-bore-lto)上,桥接进 VM/容器的流量可以靠 eBPF shared 接管(实测通过);
只有 pasta 这种用户态转发仍要靠 --network=host。

(TUN 模式没有这个限制,因为它靠 auto_route 管转发流量;这是切到 eBPF 的唯一实际回退项。)

12. 决策:切回 TUN 模式(2026-10-02 11:1x)

追注(2026-10-02 23:01 复核:本节的决策与理由都已被推翻,结论不再成立)

  • 本节的前提是错的:当时以为”本机内核不支持 TC 路径”,实际只有 local 的 tc 模式不可用,
    两条 shared 数据面实测都可用(§11,shared=packet_rewrite 以 TCX 挂在 virbr0 上);
  • “pasta 不创建宿主 socket”这个机制解释也是错的(ss -tnp 可见 passt.avx2 的 ESTAB 宿主 socket,真正卡点未确证,见 §11.0 追注与 docs/09 §6);
  • 因此当天夜间又切回了 eBPF:当前 inbounds[0].type="ebpf",local.data_plane=cgroup + shared.data_plane=packet_rewrite(interface:["virbr0"])。
    所以下表把 eBPF 写成”只有 local cgroup 一个数据面”已不符合现状(当前是 cgroup + shared 两个);scripts/switch-to-tun.sh 保留作回退手段。

§11 一度被读成”本机内核不支持 TC 路径”,据此推断 eBPF 的 local cgroup 数据面覆盖不到 rootless 容器
(当时以为是”pasta 不创建宿主 socket”)。这两个前提现已被推翻(见上追注),
而 TUN 靠 auto_route 覆盖转发流量,容器/虚拟机天然被代理。
权衡”eBPF 的新特性”(分应用 UID 策略)与”容器+VM 的完整覆盖”后,当时选择回到 TUN:

能力 TUN eBPF (cgroup)
本机流量接管 ✅ ✅
容器(rootless pasta) ✅ auto_route 覆盖(本次未复测) ❌ 看不到(除非 --network=host;2026-10-02 晚复测仍是境外失败、国内正常)
虚拟机 / 共享网络 ✅ ✅ 实测通过(用 shared=packet_rewrite,不是 local 的 tc 模式;见 docs/09 §6)
分应用 UID 策略(面板) ❌ ✅
内核依赖 需 TUN 设备 需 BPF/cgroup(本机满足)

切换工具:scripts/switch-to-tun.sh(只替换 inbound,保留 DNS 故障转移组、predefined/NXDOMAIN
广告规则、15 份规则集与包路径;check → 备份 → 替换 → 重启 → 健康检查 → 失败自动回滚)。
干跑已验证:生成的配置 check 通过、7 个 DNS 服务器 / 15 份规则集 / 11 条路由规则全部保留。
(追注 2026-10-02 23:01:这两行的”14 份”当时是对的,现已改成实测的 15 份;7 个 DNS 服务器、11 条路由规则两项实测一致。)

备份链完整:config.*.pre-ebpf.json(TUN 定义来源)、config.*.pre-dataplane.json、
config.*.pre-tun.json(切回 TUN 时自动生成),任何一步都能回退。

追注(2026-10-02 23:01 实测):/etc/sing-box/backups/ 里现有 pre-audit(8 个)、pre-ebpf(2 个)、pre-dataplane(3 个),
没有 config.*.pre-tun.json(最新一个是 config.20261002-214131.pre-audit.json,21:41)。
也就是说”切回 TUN”那一步要么没走这个脚本、要么没留下这个备份,”备份链完整”这句只对前两类成立。