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:
wangjia
2026-06-16 00:11:09 +08:00
parent cadd527680
commit 83dd5369ab
5 changed files with 1123 additions and 16 deletions
+20 -3
View File
@@ -1,6 +1,7 @@
{
"_comment": "sing-box 客户端配置模板 — VLESS+REALITY+TUNPoC tsk_SLCsjNgtmng3",
"_comment": "sing-box 客户端配置模板 — VLESS+REALITY+TUNPoC 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
},