sing-box 现状审计(2026-10-02,真流量实测)
对象:正在运行的
sing-box v1.14.2-reF1nd(审计当时是 TUN 模式,/etc/sing-box/config.json,22 出站 / 9 规则集)
方法:全部为实测——DNS 回显、DoH 绕行基准、Clash APIchains+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 条缺)。两条路:
- 刷新系统包:
sudo pacman -Syu sing-geoip-rule-set(当前 20260912,落后 3 周); - 本地双保险:把上游最新文件放
/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 长期怎么吃这个仓库(两条路)
- 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正好能复用为下载出口); - (更契合你)在自己的 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(MemoryPeak108,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 判据,而是:
systemctl is-activesing-box api ebpf --url http://127.0.0.1:9091 --secret <clash密钥>→inbounds非空(附件真的挂上了)- 不设任何代理的
curl https://api.ipify.org必须等于经本地代理口的出口(这是”内核接管成功”的铁证) curl https://myip.ipip.net仍返回国内电信 IP(分流没坏)dig doubleclick.net/tr.outbrain.com仍为空(DNS 劫持没坏)- 任一失败 → 自动回滚到备份的 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 写成”只有 localcgroup一个数据面”已不符合现状(当前是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”那一步要么没走这个脚本、要么没留下这个备份,”备份链完整”这句只对前两类成立。











