连接页延迟(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):

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

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