83dd5369ab
M4 URLTest 自动选线:
- ClashApiClient.getGroupDelay() — GET /group/<name>/delay 触发按需测速
- ClashApiClient.extractUrltestResults() — 从 /proxies 响应解析 URLTest
组成员最新延迟,填充 stats 帧的 urltestResults 字段
- DesktopKernelProcess._startStatsPoll() — 每秒并行拉取连接统计 + URLTest
延迟(_clashApi.getProxies()),URLTest 失败不影响主统计
- DesktopVpnBridge.selectOutbound(tag) — Clash API PUT /proxies/proxy,
tag 可传节点 tag 或 urltest 组 tag(如 "auto-select")恢复自动
- DesktopVpnBridge.getActiveOutbound() — GET /proxies 读 proxy.now
- app/kernel/poc/reality_client.config.json.tmpl — 增加 urltest("auto-select")
+ selector("proxy") 出口组,route.final 改为 "proxy"
M5 Kill-switch + 弹性重连:
- DesktopVpnBridge.setKillSwitch({required bool on}) — 将 _killSwitchEnabled
写入 TUN inbound strict_route 字段(applyKillSwitchToConfig);内核运行中
触发静默重载(kill → start(lastConfig))
- DesktopVpnBridge.applyKillSwitchToConfig() — static,killSwitch=true ↔
TUN strict_route=true,killSwitch=false ↔ strict_route=false
- 自动退避重连:kernel 崩溃推 error → _scheduleReconnect() 退避序列
1s/2s/4s/8s/16s/30s(上限),stop() 取消,重连成功后重置计数器
- DesktopKernelProcess 意外退出清理 _clashApi(避免下次 spawn 残留)
- app/pangolin/test/killswitch_checklist.md — 三端故障注入验收清单
(Desktop ✅,iOS/Android 欠账说明)
新增测试(client/test/bridge/desktop_vpn_bridge_m4m5_test.dart):
- M4: extractUrltestResults 解析/空/无 history 边界
- M4: getGroupDelay HTTP 请求格式验证
- M4: selectOutbound / getActiveOutbound Clash API 调用断言
- M5: applyKillSwitchToConfig 字段覆盖 + 无 TUN 边界
- M5: setKillSwitch 状态注入 + 同值幂等
- M5: 崩溃→error 不崩 / 首次重连在退避延迟内触发 / stop 后不再重连
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.9 KiB
6.9 KiB
Kill-switch 故障注入清单 — M5 验收
任务 ID:tsk_xAQhC1xuCd8x
执行原则:每项注入期间持续循环 curl --max-time 2 ifconfig.me,断言要么超时要么走节点 IP,绝无真实 IP 泄露。
通用准备
# 注入测试辅助脚本(三端通用,在 shell 里循环执行)
while true; do
result=$(curl --max-time 2 -s ifconfig.me 2>&1)
echo "[$(date +%T)] $result"
sleep 1
done
预期:
- 正常连接时:输出节点出口 IP(非本机真实 IP)
- Kill-switch 生效时:
curl: (28) Operation timed out或连接拒绝 - 绝不允许输出本机真实 IP
WebRTC 泄露复验(浏览器内手动执行):
- 访问 https://browserleaks.com/webrtc 观察"Local IP"和"Public IP"字段
- Kill-switch 激活时,Public IP 应为空或节点 IP
DNS 泄露复验:
- 访问 https://dnsleaktest.com → Run Extended Test
- Kill-switch 激活时,所有 DNS 服务器应为节点侧 IP(非国内 ISP DNS)
平台一:桌面端 macOS(11D 已完成)
实现:sing-box TUN strict_route: true + DesktopVpnBridge.setKillSwitch()
1. 内核进程被 kill -9
# 找到 sing-box 进程 PID
pgrep -f sing-box
# 强制终止
kill -9 <PID>
- curl 循环输出变为超时(不泄露真实 IP)
- Flutter UI 显示
error状态(不崩溃) - 约 1s 后触发自动重连(状态切换为
connecting) - 退避序列正确:第1次1s,第2次2s,第3次4s…上限30s
- 重连成功后 UI 恢复
on状态 - 重连后 curl 再次输出节点 IP
2. 物理断网(关闭 Wi-Fi / 拔以太网)
- curl 循环输出变为超时
- UI 状态切换为
error或保持on(取决于 sing-box keepalive 行为) - 恢复网络后,自动重连成功
- 恢复后 curl 再次输出节点 IP,无真实 IP 泄露
3. Kill-switch 关闭态下断网
# 先关闭 Kill-switch(strict_route=false)
# 再断网
- curl 超时(无网络时正常超时,但不应泄露路由绕过节点的 IP)
- 注意区分:关闭 Kill-switch = 断网后流量不被隧道保护,可能走直连
- 关闭 Kill-switch 时,断网后 curl 应超时或走直连(记录观察结果,不强制要求节点 IP)
4. Kill-switch ON ↔ OFF 切换验证
# 连接后调用 setKillSwitch(on: true)
# 查看 config 的 strict_route 字段
setKillSwitch(on: true)触发内核重载,重载期间 curl 短暂超时- 重载完成后 curl 再次输出节点 IP
setKillSwitch(on: false)同样触发重载,重载完成后 curl 输出节点 IPgetActiveOutbound()在切换前后返回正确 tag
5. 节点端主动断连(服务器侧 reset 连接)
# 在服务端执行(模拟节点故障):
# iptables -I INPUT -p tcp --dport 443 -j REJECT
- sing-box 内核检测到连接断开后推送
error - 自动重连定时器触发,尝试重连
- 恢复后(服务端恢复策略),curl 再次输出节点 IP
已完成打勾记录
执行日期:___________
执行人:___________
- 测试环境:macOS Ventura 13.x,Apple Silicon M2
- 1. kill -9 内核进程
- 2. 物理断网
- 3. Kill-switch OFF + 断网
- 4. Kill-switch ON/OFF 切换
- 5. 节点端主动断连
- WebRTC 复验(browserleaks.com)
- DNS 泄露复验(dnsleaktest.com)
平台二:iOS(11E M3 已完成;M4/M5 欠账)
当前状态:selectOutbound / getActiveOutbound / setKillSwitch 仍为 stub(TODO 11F),以下清单为下阶段完成后验收。
实现路径(欠账):
AppDelegate.swiftselectOutbound→sendProviderMessage→PacketTunnelProvider.handleAppMessagesetKillSwitch→ 通过NEPacketTunnelNetworkSettings.includedRoutes/excludedRoutes控制泄露路由
1. PacketTunnel Extension 被系统终止
- iOS 杀后台,或 Xcode Debug > Simulate Memory Warning
- curl 循环(在终端 SSH 到 iOS 或通过 TestFlight 辅助 App 执行)结果为超时
- 主 App UI 显示
error状态 - 自动重连(NETunnelProviderSession 重启)触发
- 恢复后 curl 输出节点 IP
2. Wi-Fi ↔ 蜂窝切换
- 切换期间 curl 超时(不泄露真实 IP)
- NEPathMonitor 触发重连
- 切换完成后 curl 输出节点 IP
3. Kill-switch ON(includedRoutes 覆盖所有流量)
setKillSwitch(on: true)触发 NetworkExtension 重配置- 断网后 curl 超时(不走直连)
- 恢复后 curl 输出节点 IP
4. WebRTC / DNS 泄露复验
- Safari 访问 browserleaks.com/webrtc — Public IP 为节点 IP 或空
- dnsleaktest.com — 无国内 ISP DNS
已完成打勾记录
执行日期:___________
执行人:___________
欠账说明:M4/M5 iOS stub 尚未实现(需先完成 11F),本清单留待后续补全。
平台三:Android(11F M2 已完成;M4/M5 欠账)
当前状态:selectOutbound / getActiveOutbound / setKillSwitch 仍为 stub(TODO 11G),以下清单为下阶段完成后验收。
实现路径(欠账):
MainActivity.ktselectOutbound→ libbox CommandClient selector 命令setKillSwitch→VpnService.Builder.addDisallowedApplication或BlockVpnRoute路由覆盖
1. PangolinVpnService 被 kill -9
# adb shell
adb shell am kill com.pangolin.pangolin_vpn
- curl 超时(Android 侧通过 adb shell curl 执行)
- Flutter UI 收到 VpnEventBus
error推送,UI 不崩 - 自动重连触发(PangolinVpnService 重启)
- 恢复后 curl 输出节点 IP
2. Wi-Fi ↔ 蜂窝切换
- 切换期间 curl 超时
- ConnectivityManager 触发重连
- 恢复后输出节点 IP
3. Kill-switch ON(DISALLOW_APPLICATION 覆盖)
setKillSwitch(on: true)触发 VpnService 重配置- 断开 Wi-Fi 后 curl 超时(不走直连)
- 恢复 Wi-Fi 后 curl 输出节点 IP
4. 电池优化 / Doze 下后台存活
- 强制进入 Doze 模式:
adb shell dumpsys deviceidle force-idle - 10 分钟内确认 VPN 服务仍在运行(
adb shell dumpsys vpn) - 退出 Doze:
adb shell dumpsys deviceidle unforce
5. WebRTC / DNS 泄露复验
- Chrome 访问 browserleaks.com/webrtc
- dnsleaktest.com
已完成打勾记录
执行日期:___________
执行人:___________
欠账说明:M4/M5 Android stub 尚未实现(需先完成 11G),本清单留待后续补全。
欠账汇总(供后续任务参考)
| 平台 | Kill-switch 实现 | URLTest/selectOutbound | 说明 |
|---|---|---|---|
| 桌面 macOS | ✅ 已实现(strict_route + 重载) | ✅ Clash API | 本任务完成 |
| iOS | ❌ 欠账 | ❌ 欠账 | TODO(11F): sendProviderMessage + includedRoutes |
| Android | ❌ 欠账 | ❌ 欠账 | TODO(11G): libbox CommandClient + VpnService 路由 |