VilaNet / 工程会话简报

《两个工单的根因与修复 — 2026-07-27 工作简报》

两个都是我们自己的 bug,不是用户设置问题;其中一个我们一个月前"修过",但修错了地方。

工单 18327

一、工单 A:iOS App Store 能打开但无法更新

  • iOS 18.7.8
  • 客户端 2.14.5
  • 规则(PAC)模式
  • 强制代理所有 Apple 流量 = 关
根因

proxyAllAppleTraffic 关闭时,本应让 Apple 流量直连。实际实现只覆盖 apple-cn 的 163 个精确域名,再加 5 个硬编码的 App Store 下载域名。其余 *.apple.com 全部落入带有 domain_suffix: apple.comgeolocation-!cn,最终走代理。

这是实测,不是推测

用 sing-box 直接测试客户端内置的 .srs 规则集,结果如下。

客户端内置 .srs 规则集实测
主机 作用 apple-cn geosite-cn geolocation-!cn 实际走向
buy.itunes.apple.com 购买·授权 = 点"更新"那一步 代理
su. / se. / sf-api. / client-api. / play. / init.itunes.apple.com 商店控制面 代理
amp-api.apps.apple.com 商店内容 API 代理
iosapps. / aod. / osxapps.itunes.apple.com 安装包字节 直连(硬编码)
is1-ssl.mzstatic.com 图片 直连

2026-06 修到的腿

PR #137 / #136 / #139 / #140 修的是安装包下载字节。这条腿现在会直连。

客户一直坏的腿

点"更新"时先走购买与授权。这条腿依然被代理,从未真正修好。

"应用商店可以打开但无法更新"
关键结论

客户的描述一个月没变。这不是"复发",而是从来没修好

这个问题不是用户能靠开关解决的
proxyAllAppleTraffic主机实际走向
buy.itunes.apple.com代理
buy.itunes.apple.com代理

开或关,两种情况都走代理。这个开关对该域名完全无效。

范围错误

苹果官方企业网络文档要求放行整个 *.itunes.apple.com*.apps.apple.com*.mzstatic.com 通配符。我们只挖了其中 5 个叶子。

工单 17913

二、工单 B:Windows 开着 VPN 时国内软件用不了(微信文件传输一直失败)

  • Windows 10 中国版
  • 客户端 2.12.82
  • 规则模式
  • TUN 模式
  • 域名策略 = 仅 IPv4
  • 香港节点
根因

Windows 的 TUN 地址是写死的双栈,完全不看域名策略。"仅 IPv4"名不副实:DNS 不再返回 AAAA,但 TUN 仍然接管 ::/0

HTTPDNS 本来是国产 App 用来躲运营商 DNS 劫持的。它绕开我们的 DNS 过滤,照样拿到 IPv6,然后被 TUN 收进来,丢给没有 IPv6 出口的香港节点,最后一直等待超时。

代码里早就知道

同一份代码注释其实已经写了"微信 HTTPDNS IPv6 + 节点没 IPv6",但只在苹果平台修过,Windows 一直没管。

仅 IPv4 策略下的平台对照
平台是否接管 ::/0快速拒绝用户看到的结果
iOS / macOS-TUN接管 ✓有 ✓ 立刻失败并回落 IPv4
Android / Linux不接管 ✓无需拒绝 立刻"网络不可达"
Windows接管 ✓没有任何拒绝 ✗ 卡死等超时
Windows 是唯一"接管了却不给结果"的一格。
独立链路交叉验证

三、两个 AI 吵架,靠源码判决

分歧不是噪声。分歧暴露了最容易凭直觉判断错的安全边界。

Fable 5

负责写跨平台契约

主张保留双栈,再加一条拒绝规则。它担心拿掉 IPv6 地址会让 Windows 绕过 VPN 直接走 IPv6,造成漏流量。

把握:75%

Codex

负责落地实现

主张直接把 Windows TUN 改成只有 IPv4,减少依赖层与拒绝语义的不确定性。

读取客户端实际链接版本的 sing-tun 源码后:Fable 5 错了

  1. 没有 IPv6 地址时,sing-tun 根本不会安装 ::/0 路由。
  2. strict_route 开启时,会安装 Windows WFP 防火墙规则,直接拦截 App 的 IPv6 连接。因此立刻失败,既不漏也不静默。
  3. sing-box 自己的进程被放行,代理连接不受影响;防火墙初始化失败会中止启动,不会出现"看起来连上了其实在漏"。
最终选择

改成只有 IPv4 更简单、更稳,而且不依赖 gvisor 是否正确实现拒绝语义。

这就是跑两条独立链路的价值——它们分歧的地方,正是最容易搞错的地方。
诚实记录

四、我自己说错又改正的地方

这些不是措辞微调。每一处都会改变根因判断、修复方案或客户建议。

  1. 一开始说数据包被"本地直接丢弃"。错。 包其实发出去了,只是发到没有 IPv6 出口的节点,然后干等超时。这个区别决定了怎么修。
  2. 说让客户改成"优先 IPv4"就能解决。错。 网卡还是双栈,而 HTTPDNS 压根不看 DNS 设置。
  3. 给客户的临时方案漏了通配符。 域名规则必须写 *.itunes.apple.com。不带 *. 就变成精确匹配,等于什么都没做,还会让我们误判"理论被推翻"。
  4. 一度提议删除 Windows 的全局模式判断。 评审证明这会让一部分用户的全局模式失效,已撤回。
  5. 客服 AI 告诉客户"正在拒绝 IPv6"。也是错的。 Windows 上根本没有任何东西在拒绝。
已完成的工程工作

五、已经做了什么

  • 两份根因文档,加一份跨平台 TUN / IPv6 契约。覆盖所有平台,避免以后再打第四个补丁。
  • 契约与方案各自经过 3 轮对抗式代码评审。
  • Windows 修复已在隔离 git worktree 实现。没碰主干,也没碰另一个会话正在修改的文件。
  • 纯函数负责策略决策与配置组装;生产代码必须调用它,测试才能证明"真的接上了"。
  • 加入可注入的管理员状态钩子,Windows 分支测试在其他操作系统上仍然确定。
  • 加入远程配置总开关,默认开;关闭时逐字节回到现在线上的配置。
  • 诊断补上"生效值"。此前只报存储值,正是它把客服 AI 和我都带偏了。
对抗式评审

问题数逐轮收敛,全部接受,没有一条被驳回。其中两条抓出我自己改错的地方。

822
验证结果(本人实际执行,不是转述)
检查项命令结果
静态分析flutter analyzeNo issues found(退出码 0)
服务层全量测试flutter test test/services/1098 通过,1 跳过(退出码 0)
本次相关测试flutter test .../config_adapter_service_test.dart52 通过
回归有效性把代码改回修复前再跑3 个测试失败,报错正是本次 bug 的特征(说明测试不是摆设)
改动范围git diff --stat6 个文件,全部可解释,无多余文件
待返回 代码评审已完成:0 个阻断、0 个严重、4 个次要、1 个提示;评审独立复核并确认了「关掉开关等于逐字节回到现在线上配置」这一条。5 条全部已修复,修复后再次跑通全部测试。其中一条修的是我加的一个字段——它声称保证某个不变量,实际上没有任何代码使用它,等于空头承诺,已删除。
不能提前关单

六、还没做的

两个根因都还没在真机上确认

  • 当前依据是读源码和实测规则集,客户侧一次抓包都还没拿到。
  • 上一轮就是"改完就关单",结果客户又坏了一个月。这次不能重蹈覆辙。
  • iOS 修复还没实现,目前只有方案。
  • Windows 需要真机确认:连接后 TUN 没有 IPv6 默认路由;IPv6 连接立刻报错而不是超时;断开后 IPv6 立刻恢复正常。
iOS:待实现 已有根因与方案,没有代码修复。
Windows:待真机 代码和自动化测试已有,设备行为未确认。
客服话术:待纠正 停止告诉 Windows 用户"打开远程规则集"。这个开关在 Windows 上基本没作用。
可直接给客户

七、给客户的临时话术

两个方案都要求完全断开再重连。Windows 方案必须同时说明保护能力下降。

工单 A(iOS)

设置 → 打开域名规则 → 添加以下域名为直连 → 完全断开重连。

*.itunes.apple.com
*.apps.apple.com
*.mzstatic.com

必须带 *.。不带就是精确匹配,等于没有覆盖子域名。

工单 B(Windows)

关闭 TUN 模式,保留"自动配置系统代理" → 保存 → 断开重连。

必须如实告诉客户:这会降低保护。只有认系统代理的软件才走 VPN,其余软件会直接绕过。