环境
- 客户端测试版本:
1.0.4
- 平台:iOS
- 场景:VPN 保持连接,设备夜间闲置
- 观测日期:2026-09-09(北京时间,UTC+8)
本次没有确认 1.0.4 安装包与当前公开源码的精确提交映射。下方源码链接用于说明与设备日志吻合的当前实现路径:Hako 35c9bb675f1cf5b0befd4290809d06dead728768,Hako-Client b05832246fcac69d13ff16df871a8d53fd394c10。
问题概述
Hako 将每次 NEPacketTunnelProvider.wake() 转发给 BoxService.Wake()。该方法在没有确认物理网络路径变化、DNS transport 失效或查询失败的情况下,无条件执行一次全局 resolver.ResetConnection()。
在本次 1.0.4 夜间测试中,iOS 高频调用 sleep/wake。通用 wake 因此可能反复关闭 DoH、DoH3、DoQ 和 DoT 等可复用 DNS transport,使后续查询重新建连或握手。该问题独立于另一个“wake 无条件触发全量健康检查”的问题:即使健康检查恢复逻辑修正,这里的 DNS Reset 仍会执行。
这里报告的是“Hako 对系统 wake 的处理方式”,不主张最初的 iOS 系统唤醒由 Hako 产生。
设备日志证据
按北京时间分析 2026-09-09 00:00–06:00:
01:01:14–05:59:57 共记录 2146 组 provider sleep/wake。
- 平均每小时约 431 次,平均每 8.35 秒一组。
wake → 下一次 sleep 中位数约 3.865 秒。
sleep → wake 中位数约 2.278 秒。
05:23–06:00 保留了完整 core 证据:279 次 wake,其中 275 次 wake 都立即触发了后台健康检查批次,说明该区间核心服务处于运行状态。
- 同一
05:23–06:00 区间没有记录 [Apple] default path -> ... 路径更新日志,但通用 wake 仍持续发生。
resolver.ResetConnection() 本身没有事件日志,因此当前不能从设备日志计算实际关闭了多少条 DNS 连接。根据确定的调用链,在服务存活时,每次上述 wake 都会发起一次全局 DNS Reset。脱敏 wake 样本见 issue-sample-ios-wake-healthcheck-storm-1.0.4.log。
issue-sample-ios-wake-healthcheck-storm-1.0.4.log
与当前源码吻合的调用链
- Hako-Client 的
PacketTunnelProvider.wake() 将系统 wake 交给 ExtensionProvider。
ExtensionProvider.wake() 调用 currentService?.wake()。
BoxService.Wake() 先执行 pause.DeviceWake(),随后无条件调用 resolver.ResetConnection()。
component/resolver.ResetConnection() 遍历已配置 resolver,并异步调用各 resolver 的 ResetConnection。
- DNS resolver 会去重同一次调用中共享的 raw transport,然后对每个 transport 执行 ResetConnection。
dnsOverHTTPS.ResetConnection() 关闭 idle HTTP 连接和 HTTP/2 连接,并清空当前 client。
dnsOverQUIC.ResetConnection() 关闭当前 QUIC 连接。
dnsOverTLS.ResetConnection() 清空并关闭可复用连接池。
当前 BoxService.Wake() 附近的源码注释已经指出:一次 sleep/wake 并不能证明连接已经失效,实际物理路径变化由独立 path monitor 处理;但方法末尾仍保留了无条件 DNS Reset。
为什么通用 wake 不足以作为 DNS Reset 条件
NEProvider.wake() 表示系统从睡眠状态恢复,不等同于 Wi-Fi、蜂窝接口、源地址、网关或 DNS 上游发生变化。短暂 sleep/wake 期间,原有 DNS transport 可能仍然有效。
当前 Hako 已有独立的 Apple 默认路径监控:
- 对应用的 path update 更新物理网络能力和绑定接口;
- 在认为旧 DNS socket 可能属于失效路径时清除 resolver cache 并重置连接;
- 对真实路径变化保留必要的 DNS 恢复能力。
因此,通用 wake 再执行一次全局 Reset 并不能提供更精确的失效判断,却会覆盖没有路径变化的短暂唤醒场景。
可能影响
实际影响取决于生效 DNS 配置和当时是否存在已建立 transport:
- DoH/HTTP2、DoH3、DoQ 或 DoT 的空闲连接被提前关闭;
- 下一次 DNS 查询需要重新建立 TCP、TLS 或 QUIC 会话;
- 证书验证、密钥交换、网络往返和无线活动增加;
- 高频异步 Reset 可能与查询或刚刚重建的 transport 交错;
- 首次查询延迟增加,弱网下更容易暴露重连或超时成本。
如果使用无连接复用的 DNS 路径,或者当时没有已建立 transport,实际影响会较小。当前证据没有测得真实关闭次数、重握手次数或单项耗电比例。
期望行为
- 通用 DeviceWake 不应自动等同于 DNS 网络路径失效。
- DNS transport 应在物理路径更新、明确连接错误、配置更新或服务关闭时重置。
- 短暂 sleep/wake 后优先复用仍然可用的 transport;若复用失败,再走现有重连逻辑。
- 高频 wake 应被合并或限频,不能重复对同一 transport 发起无条件全局 Reset。
建议的最小改进方向
- 从
BoxService.Wake() 移除无条件 resolver.ResetConnection(),由现有 Apple path monitor 负责路径变化时的失效处理。
- 如果仍需要处理长时间睡眠后的 NAT 或连接失效,可先尝试复用,在真实 I/O 错误后重建;或者仅对长睡眠设置有最小间隔的条件性 Reset。
- 增加低频诊断计数:wake 次数、wake-triggered DNS Reset 次数、实际关闭 transport 数、因失败而重建的 transport 数。不要为每条连接增加高频 info 日志。
- 避免多个异步 Reset 与 transport 重建交错;如保留 Reset,应保证同类请求合并并具有 generation 边界。
建议验证
- 分别使用 DoH/HTTP2、DoH3、DoQ、DoT 和普通 UDP DNS 测试。
- 在物理路径不变的锁屏 sleep/wake 场景中,比较当前实现与移除 wake Reset 后的连接复用、查询延迟、握手数和扩展 CPU 时间。
- 覆盖 Wi-Fi/蜂窝切换、同接口漫游、热点切换、DNS server 失效和长时间睡眠后的首次查询,确认必要重连仍可靠。
- 验证正在进行的 DNS 查询不会因 wake Reset 获得伪造 SERVFAIL,连续多次 wake 不会关闭刚重建的 transport。
- 用 sysdiagnose、网络抓包或新增的低频计数确认实际 Reset 与重握手数量,再评估整机电量差异。
边界
- 当前日志证明 wake 高频发生,并且源码在服务存活时无条件调用 DNS Reset;日志没有直接记录每次 transport 关闭。
- 当前不能确定最初是什么事件唤醒了 iOS。
- 不能把一次 Reset 调用直接换算成一次新握手或一次硬件唤醒。
- 当前没有证据承诺固定节电比例。
- 本问题建议提交到 Hako 内核项目;Hako-Client 只负责转发系统生命周期回调。
issue-sample-ios-wake-healthcheck-storm-1.0.4.log
环境
1.0.4本次没有确认 1.0.4 安装包与当前公开源码的精确提交映射。下方源码链接用于说明与设备日志吻合的当前实现路径:Hako
35c9bb675f1cf5b0befd4290809d06dead728768,Hako-Clientb05832246fcac69d13ff16df871a8d53fd394c10。问题概述
Hako 将每次
NEPacketTunnelProvider.wake()转发给BoxService.Wake()。该方法在没有确认物理网络路径变化、DNS transport 失效或查询失败的情况下,无条件执行一次全局resolver.ResetConnection()。在本次 1.0.4 夜间测试中,iOS 高频调用 sleep/wake。通用 wake 因此可能反复关闭 DoH、DoH3、DoQ 和 DoT 等可复用 DNS transport,使后续查询重新建连或握手。该问题独立于另一个“wake 无条件触发全量健康检查”的问题:即使健康检查恢复逻辑修正,这里的 DNS Reset 仍会执行。
这里报告的是“Hako 对系统 wake 的处理方式”,不主张最初的 iOS 系统唤醒由 Hako 产生。
设备日志证据
按北京时间分析
2026-09-09 00:00–06:00:01:01:14–05:59:57共记录 2146 组provider sleep/wake。wake → 下一次 sleep中位数约 3.865 秒。sleep → wake中位数约 2.278 秒。05:23–06:00保留了完整 core 证据:279 次 wake,其中 275 次 wake 都立即触发了后台健康检查批次,说明该区间核心服务处于运行状态。05:23–06:00区间没有记录[Apple] default path -> ...路径更新日志,但通用 wake 仍持续发生。resolver.ResetConnection()本身没有事件日志,因此当前不能从设备日志计算实际关闭了多少条 DNS 连接。根据确定的调用链,在服务存活时,每次上述 wake 都会发起一次全局 DNS Reset。脱敏 wake 样本见issue-sample-ios-wake-healthcheck-storm-1.0.4.log。issue-sample-ios-wake-healthcheck-storm-1.0.4.log
与当前源码吻合的调用链
PacketTunnelProvider.wake()将系统 wake 交给ExtensionProvider。ExtensionProvider.wake()调用currentService?.wake()。BoxService.Wake()先执行pause.DeviceWake(),随后无条件调用resolver.ResetConnection()。component/resolver.ResetConnection()遍历已配置 resolver,并异步调用各 resolver 的 ResetConnection。dnsOverHTTPS.ResetConnection()关闭 idle HTTP 连接和 HTTP/2 连接,并清空当前 client。dnsOverQUIC.ResetConnection()关闭当前 QUIC 连接。dnsOverTLS.ResetConnection()清空并关闭可复用连接池。当前
BoxService.Wake()附近的源码注释已经指出:一次 sleep/wake 并不能证明连接已经失效,实际物理路径变化由独立 path monitor 处理;但方法末尾仍保留了无条件 DNS Reset。为什么通用 wake 不足以作为 DNS Reset 条件
NEProvider.wake()表示系统从睡眠状态恢复,不等同于 Wi-Fi、蜂窝接口、源地址、网关或 DNS 上游发生变化。短暂 sleep/wake 期间,原有 DNS transport 可能仍然有效。当前 Hako 已有独立的 Apple 默认路径监控:
因此,通用 wake 再执行一次全局 Reset 并不能提供更精确的失效判断,却会覆盖没有路径变化的短暂唤醒场景。
可能影响
实际影响取决于生效 DNS 配置和当时是否存在已建立 transport:
如果使用无连接复用的 DNS 路径,或者当时没有已建立 transport,实际影响会较小。当前证据没有测得真实关闭次数、重握手次数或单项耗电比例。
期望行为
建议的最小改进方向
BoxService.Wake()移除无条件resolver.ResetConnection(),由现有 Apple path monitor 负责路径变化时的失效处理。建议验证
边界
issue-sample-ios-wake-healthcheck-storm-1.0.4.log