Files
pangolin/app/kernel/poc
wangjia 83dd5369ab 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>
2026-06-16 00:11:09 +08:00
..

Pangolin 桌面端 PoC M1 — 接线指南

任务: tsk_SLCsjNgtmng3 · M1 首发打通(macOS TUN + REALITY

前置条件

1. 下载 sing-box 二进制

# macOS Apple Silicon
cd app/kernel
./fetch-desktop-bin.sh darwin arm64

# macOS Intel
./fetch-desktop-bin.sh darwin amd64

产物: app/kernel/dist/desktop/darwin-arm64/sing-box

2. 配置 TUN 提权(macOS PoC

sing-box TUN 模式需要创建 utun 接口,必须有 root 权限。PoC 阶段使用 sudo

# 方式 A: sudoers 免密白名单(推荐,避免每次输密码)
SINGBOX_PATH="$(pwd)/app/kernel/dist/desktop/darwin-arm64/sing-box"
echo "$(whoami) ALL=(root) NOPASSWD: ${SINGBOX_PATH}" | sudo tee /etc/sudoers.d/pangolin-singbox
sudo chmod 440 /etc/sudoers.d/pangolin-singbox

# 方式 B: 用 setuid(不推荐用于正式版)
sudo chown root "${SINGBOX_PATH}"
sudo chmod u+s "${SINGBOX_PATH}"

正式版说明: macOS 正式版应使用 SMJobBless 注册特权 Helper Daemon 并完成 Apple 公证 (notarization)。此 PoC 路径记录在 BACKLOG-11D-HELPER。

3. 获取 REALITY 服务端参数

从部署节点的 EC2 拿 REALITY 公钥和 UUID

# EC2 上 Xray REALITY 密钥对(deploy/xray/secrets/ 或 Bitwarden
ssh ec2 "cat ~/pangolin/xray/secrets/reality_keys.json 2>/dev/null || echo '不存在,需手动生成'"

# 若未生成,在 EC2 上运行:
# docker run --rm ghcr.io/xtls/xray-core x25519 | tee /tmp/reality_keys.txt

4. 渲染 PoC 配置

export SERVER_HOST="18.136.60.128"    # EC2 公网 IP
export SERVER_PORT="11443"            # Xray VLESS 端口
export REALITY_UUID="your-uuid-here"
export REALITY_PUBLIC_KEY="your-x25519-public-key"
export REALITY_SHORT_ID="your-short-id"
export REALITY_SNI="www.apple.com"

./app/kernel/poc/gen-poc-config.sh --out /tmp/pangolin-poc-config.json

验收测试

M1 验收步骤

# 1. 设置二进制路径
export PANGOLIN_SINGBOX_BIN="$(pwd)/app/kernel/dist/desktop/darwin-arm64/sing-box"

# 2. 用渲染后的 config 启动(PoC 会自动 sudo
sudo "${PANGOLIN_SINGBOX_BIN}" run -c /tmp/pangolin-poc-config.json &
KERNEL_PID=$!

# 3. 等待就绪(Clash API
sleep 3

# 4. 验证出口 IP
curl -s https://ifconfig.me   # 应显示节点 IP18.136.60.128 或接近)

# 5. DNS 泄露检测
dig @8.8.8.8 google.com       # 通过 VPN 解析
dig google.com                 # 应走 sing-box DNS(不泄露本地 ISP

# 6. 流量统计(Clash API
CLASH_PORT="$(cat /tmp/pangolin-poc-config.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['experimental']['clash_api']['external_controller'].split(':')[1])")"
CLASH_SECRET="$(cat /tmp/pangolin-poc-config.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['experimental']['clash_api']['secret'])")"

curl -s -H "Authorization: Bearer ${CLASH_SECRET}" "http://127.0.0.1:${CLASH_PORT}/connections"

# 7. 停止(还原路由/DNS
kill "${KERNEL_PID}"
sleep 2

# 8. 验证还原
netstat -rn | grep utun        # 不应出现 pangolin TUN 条目
scutil --dns | head -20       # DNS 应回到系统默认

Dart 集成测试

cd client
flutter test test/bridge/kernel_process_test.dart -v

架构说明

DesktopVpnBridge.start(configJson)
    │
    ├─ _injectClashApi()   注入随机端口+secret 到 experimental.clash_api
    ├─ _writeConfig()      写 ~/Library/Application Support/com.pangolin.vpn/kernel/
    │
    └─ DesktopKernelProcess.spawn(configPath)
           │
           ├─ _resolveBinaryPath()   PANGOLIN_SINGBOX_BIN / 开发目录 / /usr/local/bin
           ├─ Process.start(['sudo', binPath, 'run', '-c', configPath])
           ├─ _waitForClashApi()     轮询 GET /connections 直到就绪(20s 超时)
           ├─ _startStatsPoll()      每秒轮询 /connections → VpnStatsEvent
           └─ emit VpnStatus.on

已知限制(PoC 阶段)

限制 原因 正式版方案
sudo 提权 TUN 需 root SMJobBless Helper + 公证 (BACKLOG-11D-HELPER)
单 REALITY outbound 节点选优未实现 URLTest 多节点 (11G)
统计用 /connections 轮询 SSE 流实现更复杂 /traffic SSE 订阅
Windows Wintun 未测 主验收平台 macOS M1 验收后补
无 kill-switch strict_route 做基础保护 11G
Android/iOS 归 11E/11F NEPacket/VpnService