From e2440e44cc243c8626eb4a5b5ca32791cdce10f9 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 30 Jun 2026 22:56:10 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=20CLAUDE.md=20=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E7=AB=AF/=E8=B7=A8=E7=AB=AF=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E7=AB=A0=E8=8A=82=20+=20=E6=96=B0=E5=A2=9E=20urltest=20?= =?UTF-8?q?=E6=8E=92=E9=9A=9C=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md:加「移动端(iOS/iPad+Android)原生隧道」「跨端实时统计+urltest 延迟」两节—— libbox 构建前置(JDK 17/包名/gitignore)、clash_api 注入正解、libbox 单 client Group 坑、 Dart 共享广播流、连接态跳直连实测;附「最后在线取 last_seen」一句。 - 新增 docs/connect-latency-urltest.html(排障/Runbook):四端 urltest 实现 + 根因链 + 打点法, 登记进 docs/index.html。 Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 34 ++++++++++++ docs/connect-latency-urltest.html | 90 +++++++++++++++++++++++++++++++ docs/index.html | 5 ++ 3 files changed, 129 insertions(+) create mode 100644 docs/connect-latency-urltest.html diff --git a/CLAUDE.md b/CLAUDE.md index afdcbf7..ac3f2ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,3 +117,37 @@ cd web/website && npm run gen:tokens **已知坑** - 开发机若是 **macOS 26 (Tahoe)**:`sysextd` 报 `no policy, cannot allow apps outside /Applications`(app 在 /Applications 也报)是 **Apple 回归**,本机调试需关 SIP 后 `systemextensionsctl developer on`;真实用户(macOS 14/15)不受影响。 - #5 国内分流:客户端 `smartRoute` → `?split_cn=1` 下发远程 rule-set;**TODO 改本地 `.srs` 预取**,避免启动期下载。 + +## client/ 移动端(iOS/iPad + Android)原生隧道 + +同样内嵌 libbox,与 macOS 同「CommandServer 模型」,但形态/打包不同: + +- **iOS/iPad**:`NEPacketTunnelProvider` 扩展(`client/ios/PacketTunnel/`),经 `NETunnelProviderManager` 管理(非 sysext);App Group 用 **iOS 的 `group.` 前缀**(`group.com.pangolin.pangolinVpn`)。`ctl_info`/`sockaddr_ctl` iOS SDK 不发,靠 `PacketTunnel-Bridging-Header.h` 手写声明。 +- **Android**:`PangolinVpnService`(`client/android/.../PangolinVpnService.kt`),VpnService + libbox **同进程**;明文联调 API 需 manifest `usesCleartextTraffic`(Android 9+ 默认禁 http)。 +- iOS/Android 与 macOS 同走 Dart `VpnNativeBridge`,所以 Dart 侧修复(状态/统计流)四端共享。 + +**libbox 构建(产物 gitignore,需手动生成)**——`scripts/build-libbox.sh`: +- Apple:`bash scripts/build-libbox.sh apple macos`(只 macOS,快)/ `apple ios`(iOS+sim)→ `Libbox.xcframework` 放 `client/{macos,ios}/Frameworks/`。 +- Android:`bash scripts/build-libbox.sh android` → `Libbox.aar`,**重命名小写** `libbox.aar` 放 `app/kernel/dist/android/`。⚠️ gomobile 编 Android **强制 JDK 17**(JDK 21 直接拒);`export JAVA_HOME=/opt/homebrew/opt/openjdk@17/...` 再编。 +- ⚠️ **Android libbox Java 包名是 `io.nekohasekai.libbox`**(不是 `libbox`),Kotlin import 用前者。 + +## 跨端实时统计 + urltest 延迟(连接页"延迟"的唯一正解) + +连接页"延迟"= 内核 urltest(经 REALITY 真实出站测 RTT);**坑很深,改前必读** +`docs/connect-latency-urltest.html`。铁律: + +- **服务端下发的 sing-box 配置没有 `clash_api`** → 内核不暴露出站组/urltest 历史 → libbox 的 + Group 命令 / `/proxies` 全空。**每个原生端必须在起内核前给配置注入 `experimental.clash_api` + (127.0.0.1 本地监听)+ `cache_file`**,再经本地 HTTP 查 `/proxies`(读 history)+ + `/group//delay`(刷新)取真实延迟(与 Windows desktop bridge 同法)。 +- **libbox CommandClient 一连接只订一种命令**:`addCommand(Status)+addCommand(Group)` 同一 client + 只第一个生效(Group 永不回调)。速率走 Status,urltest 走上面的 clash HTTP,别指望单 client 的 Group。 +- **macOS 扩展是 root、容器与无 root 主 app 不同路径** → 主 app 连不上扩展的 command.sock;macOS/iOS + 统计走 `NETunnelProviderSession.sendProviderMessage`(扩展内 StatsCollector 采集、app 拉取)。Android + 同进程,直接 socket。 +- **Dart 侧**:`VpnNativeBridge.statsStream/statusStream` 必须缓存成**共享广播流**(`asBroadcastStream`)—— + 否则多订阅者(连接页速度 + ConnectionController 回写延迟)抢 EventChannel 的单一原生 sink,后订阅者赢、 + 前者变哑(表现:速度正常但延迟一直 —)。`_onStats` 在自动连到已运行隧道(`_connectedNode==null`)时回退 + `effectiveNode`;连接态 `_measure` 跳过直连实测(全局 TUN 会本地接住、返回假的几 ms)。 + +> 设备列表"最后在线"取 `last_seen`(连接/用量/~15s 会话轮询刷新),不是 `last_login`(仅登录那刻)。 diff --git a/docs/connect-latency-urltest.html b/docs/connect-latency-urltest.html new file mode 100644 index 0000000..da0f915 --- /dev/null +++ b/docs/connect-latency-urltest.html @@ -0,0 +1,90 @@ + + + + + +Pangolin 连接页延迟(urltest)· 跨端实现 + 排障 + + + +
+ +

连接页延迟(urltest)· 跨端实现 + 排障

+

macOS / iOS / iPad / Android / Windows 四端一致——内核 urltest 经 REALITY 真实出站测 RTT

+ +
+一句话:连接页"延迟"不是 App 直连节点测的(全局 TUN 下直连握手会被本地接住,返回假的几 ms), +而是 sing-box 内核的 urltest(经 REALITY 真实出站 GET generate_204 测 RTT)。 +但服务端下发的配置没有 clash_api → 内核不暴露出站组/urltest 历史 → 默认取不到,连接页显示 。 +正解:每个原生端在起内核前给配置注入 clash_api,再经本地 HTTP 取。这条链四端踩了一整天,逐一记此。 +
+ +

根因链(为什么默认全空)

+ + + + + + +
① 内核不暴露服务端 BuildClientConfig 渲染的配置无 experimental.clash_api → sing-box 不开出站组/urltest 历史接口原生端起内核前注入 clash_api(127.0.0.1 本地监听)+ cache_file
② libbox 取法错libbox CommandClient 一连接只订一种命令:同一 client addCommand(Status)+addCommand(Group) 只第一个生效,Group 永不回调 → urltest 恒空速率走 Status;urltest 改走 clash HTTP(/proxies 读 history + /group/<name>/delay 刷新),与 Windows desktop bridge 同法
③ Dart 抢 sinkVpnNativeBridge.statsStream 每次访问都 receiveBroadcastStream();多订阅者(连接页速度 + ConnectionController 回写延迟)抢 EventChannel 的单一原生 sink,后订阅者赢、前者变哑 → 速度正常但延迟一直 缓存成共享广播流 asBroadcastStream(),所有订阅者复用一条原生订阅
④ 直连实测覆盖连接态下 nodes_provider._measure 直连 TCP 测节点被全局 TUN 本地接住,几 ms 返回假值,覆盖 urltest 真实 RTT(显示 3ms)连接态跳过直连实测,延迟只认 urltest;_onStats 在自动连到已运行隧道(_connectedNode==null)时回退 effectiveNode
+ +

各端怎么取(架构差异)

+ + + + + + +
注入 clash_api采集 + app↔内核通道
macOS扩展 PacketTunnelProvider.injectClashApi扩展是 root,容器与无 root 主 app 不同路径 → 主 app 连不上 command.sock。扩展内 StatsCollector 采集(libbox Status 取速率 + clash HTTP 取 urltest),主 app 经 NETunnelProviderSession.sendProviderMessage("stats")
iOS/iPad同 macOS(扩展内注入)同 macOS:扩展 StatsCollector + sendProviderMessage。(iOS 扩展/app 同用户、容器其实共享,但为与 macOS 一致仍走此路)
AndroidPangolinVpnService.injectClashApiVpnService 与 libbox 同进程:服务内直接连 command.sock 取速率 + pollClash 取 urltest,经 VpnEventBus 推 Flutter
Windowsdesktop bridge 注入(_ensureClashApi)sing-box 子进程 + clash HTTP:kernel_process.dart/proxies history + 每 12 拍 /group/<name>/delay
+

取法细则(四端一致):显示源 = /proxies 里各 URLTest 组成员的 history.last.delay(稳定、缓存住,单次探测失败不丢);/group/<name>/delay 只为让内核重测、写进 history;跳过 GLOBAL 选择器组。

+ +

排障打点法

+

延迟为 时,层层往内定位(macOS/iOS 可读主 app NSLog,Android 用 logcat):

+
    +
  • [pangolin/stats] urltest[0]= diag=... —— app 收到的 urltest 是否空 + clash 原始响应;
  • +
  • 内核侧:注入 log.level=debug + 抓 urltest/gstatic 行,看内核 outbound reality-out available: NNNms 有没有出(分清"内核没算出"vs"外面没拿到");
  • +
  • /group/delay 返回 {}dl=0:多半是链路本身太差(探测往返跑不完),非 bug。
  • +
+ +

libbox 构建前置(否则编译都过不了)

+
    +
  • 产物 gitignore,需手动 scripts/build-libbox.sh 生成:apple macos|iosLibbox.xcframework;androidlibbox.aar(改小写放 app/kernel/dist/android/)。
  • +
  • ⚠️ gomobile 编 Android 强制 JDK 17(JDK 21 直接拒);Android libbox Java 包名是 io.nekohasekai.libbox(非 libbox)。
  • +
  • iOS pbxproj 曾有 UUID 冲突(两源文件复用同 ID)→ 文件错配 target、全模块 cannot-find,补齐 framework 后才暴露。
  • +
+ +

相关:流量记账口径 · macOS sysext realize 排障 · 代码见各端 StatsCollector / PangolinVpnService / kernel_process.dart

+ +
+ + diff --git a/docs/index.html b/docs/index.html index 7a01066..2563a4e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -140,6 +140,11 @@

排障 / Runbook

+ +
连接页延迟(urltest)· 跨端实现 + 排障 HTML
+
连接页"延迟"= 内核 urltest(非直连实测)。根因链:服务端配置无 clash_api、libbox 单 client Group 不回调、Dart 广播流抢 EventChannel sink、连接态直连实测假值。各端(macOS/iOS/Android/Windows)注入 clash_api + 本地 HTTP 取法 + 排障打点 + libbox 构建前置(Android 要 JDK 17、包名 io.nekohasekai.libbox)。
+
docs/connect-latency-urltest.html
+
CI Runner 设置记录 MD
gitea Actions self-hosted runner 记录:决定用 1 个(mac-pangolin-2,项目级·mac host·label nas·复用 jiu relay)、已删 orphan 的清理命令、launchd 持久化 + 单点韧性(runner 离线 Telegram 告警)。CI 首跑 run #298。