feat(M4/M5): URLTest 自动选线 + Kill-switch + 弹性重连 [tsk_xAQhC1xuCd8x]
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>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"_comment": "sing-box 客户端配置模板 — VLESS+REALITY+TUN(PoC tsk_SLCsjNgtmng3)",
|
||||
"_comment": "sing-box 客户端配置模板 — VLESS+REALITY+TUN(PoC tsk_SLCsjNgtmng3 / M4/M5 tsk_xAQhC1xuCd8x)",
|
||||
"_usage": "渲染脚本: app/kernel/poc/gen-poc-config.sh;占位符一律 __UPPER_SNAKE__",
|
||||
"_m4_note": "URLTest 自动选线:auto-select(urltest) → 成员节点延迟最低者。手动切换经 Clash API PUT /proxies/proxy",
|
||||
|
||||
"log": {
|
||||
"level": "info",
|
||||
@@ -13,7 +14,7 @@
|
||||
"tag": "dns-remote",
|
||||
"address": "tls://1.1.1.1",
|
||||
"address_resolver": "dns-local",
|
||||
"detour": "reality-out"
|
||||
"detour": "proxy"
|
||||
},
|
||||
{
|
||||
"tag": "dns-local",
|
||||
@@ -50,6 +51,22 @@
|
||||
],
|
||||
|
||||
"outbounds": [
|
||||
{
|
||||
"_comment": "M4: URLTest 自动选优出口组(成员:所有实际代理节点 tag)",
|
||||
"type": "urltest",
|
||||
"tag": "auto-select",
|
||||
"outbounds": ["reality-out"],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"interval": "3m",
|
||||
"tolerance": 50
|
||||
},
|
||||
{
|
||||
"_comment": "M4: Selector 出口组(Clash API 操作此组来手动覆盖 / 切回自动)",
|
||||
"type": "selector",
|
||||
"tag": "proxy",
|
||||
"outbounds": ["auto-select", "reality-out"],
|
||||
"default": "auto-select"
|
||||
},
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "reality-out",
|
||||
@@ -93,7 +110,7 @@
|
||||
{ "geoip": "cn", "outbound": "direct" },
|
||||
{ "geoip": "private", "outbound": "direct" }
|
||||
],
|
||||
"final": "reality-out",
|
||||
"final": "proxy",
|
||||
"auto_detect_interface": true
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
# Kill-switch 故障注入清单 — M5 验收
|
||||
|
||||
任务 ID:tsk_xAQhC1xuCd8x
|
||||
执行原则:**每项注入期间持续循环 `curl --max-time 2 ifconfig.me`,断言要么超时要么走节点 IP,绝无真实 IP 泄露。**
|
||||
|
||||
---
|
||||
|
||||
## 通用准备
|
||||
|
||||
```bash
|
||||
# 注入测试辅助脚本(三端通用,在 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
|
||||
|
||||
```bash
|
||||
# 找到 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 关闭态下断网
|
||||
|
||||
```bash
|
||||
# 先关闭 Kill-switch(strict_route=false)
|
||||
# 再断网
|
||||
```
|
||||
|
||||
- [ ] curl 超时(无网络时正常超时,但不应泄露路由绕过节点的 IP)
|
||||
- [ ] 注意区分:关闭 Kill-switch = 断网后流量不被隧道保护,可能走直连
|
||||
- [ ] 关闭 Kill-switch 时,断网后 curl 应超时或走直连(记录观察结果,不强制要求节点 IP)
|
||||
|
||||
### 4. Kill-switch ON ↔ OFF 切换验证
|
||||
|
||||
```bash
|
||||
# 连接后调用 setKillSwitch(on: true)
|
||||
# 查看 config 的 strict_route 字段
|
||||
```
|
||||
|
||||
- [ ] `setKillSwitch(on: true)` 触发内核重载,重载期间 curl 短暂超时
|
||||
- [ ] 重载完成后 curl 再次输出节点 IP
|
||||
- [ ] `setKillSwitch(on: false)` 同样触发重载,重载完成后 curl 输出节点 IP
|
||||
- [ ] `getActiveOutbound()` 在切换前后返回正确 tag
|
||||
|
||||
### 5. 节点端主动断连(服务器侧 reset 连接)
|
||||
|
||||
```bash
|
||||
# 在服务端执行(模拟节点故障):
|
||||
# iptables -I INPUT -p tcp --dport 443 -j REJECT
|
||||
```
|
||||
|
||||
- [ ] sing-box 内核检测到连接断开后推送 `error`
|
||||
- [ ] 自动重连定时器触发,尝试重连
|
||||
- [ ] 恢复后(服务端恢复策略),curl 再次输出节点 IP
|
||||
|
||||
### 已完成打勾记录
|
||||
|
||||
执行日期:___________
|
||||
执行人:___________
|
||||
|
||||
- [x] 测试环境: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.swift` `selectOutbound` → `sendProviderMessage` → `PacketTunnelProvider.handleAppMessage`
|
||||
- `setKillSwitch` → 通过 `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.kt` `selectOutbound` → libbox CommandClient selector 命令
|
||||
- `setKillSwitch` → `VpnService.Builder.addDisallowedApplication` 或 `BlockVpnRoute` 路由覆盖
|
||||
|
||||
### 1. PangolinVpnService 被 kill -9
|
||||
|
||||
```bash
|
||||
# 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 路由 |
|
||||
Reference in New Issue
Block a user