Skip to content

macOS 睡眠唤醒后网络代理卡死���题深度复盘与自动化脚本修复 ​

一、TL;DR:先把结论摆上桌 ​

关于「Mac 合盖再打开,代理就废了」这件事,社区里流传的解释大多跑偏。基于我们过去两年在几十台不同机型的复现记录,有三条结论可以直接拿走:

第一,这几乎从来不是节点的问题。 你换十个机场,症状会一模一样——Safari 转圈、curl 卡在 TLS 握手、Telegram 显示"Connecting…"。因为根因在本地:代理进程持有的 socket 句柄、TUN 接口的路由表项、系统 DNS 的 resolver 状态,这三者在睡眠期间被打散了,唤醒后没有原子性地重建。

第二,最高效的修复动作是「重启代理进程」,而不是「重连 Wi-Fi」或「刷 DNS」。 重连 Wi-Fi 只会重建物理层,代理进程里的死连接依旧挂在进程内存里;dscacheutil -flushcache 只解决 DNS 层,对 TCP 黑洞毫无作用。真正能一次性清干净所有状态的,只有让进程重新走一遍启动流程。

第三,要根治,需要三件套: pmset 电源策略调优(减少 DarkWake 与深层待机)+ 一个幂等的守护脚本(不依赖唤醒事件,靠健康探测驱动)+ 一个服务端 NAT 表项足够长、路径足够稳的机场。前两者是自己能控制的,第三者取决于你选谁。

下文会按「物理机理 → 量化矩阵 → 客户端实操 → 自动化脚本 → 抓包排障 → 避坑 → FAQ」的顺序展开。只想抄脚本的,可以直接跳到第六章;想搞明白为什么的,从第二章开始。


二、底层机理:唤醒瞬间到底发生了什么 ​

2.1 macOS 的睡眠不是一个状态,是四个 ​

很多人把「睡眠」当成一个开关,其实 Darwin 的电源管理是一套分层状态机,用 pmset -g 可以直接看到当前策略:

  • Sleep(普通睡眠):内存供电维持,CPU 停转。默认 hibernatemode 3(笔记本)会把内存镜像写一份到磁盘作为保险。
  • Safe Sleep:内存镜像落盘后彻底断内存电,靠磁盘镜像恢复。恢复过程要重新初始化所有驱动。
  • Power Nap / DarkWake:屏幕黑着,但 SoC 周期性短暂上电,跑 Time Machine、邮件、Find My、iCloud 同步。这是代理卡死的高发区——每次 DarkWake 都会让网卡上下电一次。
  • Standby / Autopoweroff:更深一层的低功耗,进入后唤醒延迟明显变长。

关键点在于:DarkWake 期间系统不会通知用户态应用做网络重连。你的代理进程可能被 SIGSTOP 挂起,网卡被关掉,几秒后网卡回来了,但进程里那批 TCP 连接还"以为"自己活着。

2.2 唤醒后的几百毫秒里,内核做了这些事 ​

按时间顺序:

  1. Wi-Fi 驱动重新关联 AP。如果换了 BSSID(比如从 5G 频段落到 2.4G),会走一次完整的 4 次握手 + WPA 密钥协商。
  2. DHCP 续租或重新申请。多数情况续租到同一个 IP,但漫游到另一个 AP 时可能拿到新 IP。
  3. IPv6 重新下发 RA。RFC 4941 的隐私扩展临时地址会变,如果你的代理绑定过源地址,直接失效。
  4. 路由表重建。内核会清掉所有非持久化路由,包括代理注入的 0.0.0.0/1 和 128.0.0.0/1 这两条经典的 TUN 劫持路由。
  5. SCDynamicStore 重新广播系统代理配置,但广播是异步的,GUI 客户端如果响应慢了就错过窗口。
  6. DNS resolver 被重写。scutil --dns 会看到 resolver 列表按接口优先级重排,DHCP 下发的 DNS 可能重新排到最前。

做完这一套,快则 300ms,慢则 2s。而代理进程的 utun 接口,如果是直接操作的(比如 Clash 系用 tun + 系统路由注入),这六步里没有任何一步会通知它。

2.3 三类失效路径,症状完全不同 ​

把复现记录归类,卡死基本逃不出这三种:

A 类:socket 僵死(最常见,约 60%) 进程活着、端口在听、utun 在跑,但所有 TCP 连接处于"已建连但黑洞"状态。表现是 curl 一直等到超时才失败,不报 Connection Refused。根因是睡眠期间对端或中间设备(NAT、防火墙)已经回收了会话,而本机没收到 RST/FIN,socket 仍处于 ESTABLISHED。

B 类:路由丢失(约 25%) utun 接口还在 ifconfig 里,但 netstat -rn 里指向它的默认路由没了。表现是 ping 公网 IP 通、ping 域名失败,或者干脆全不通。根因是内核重建路由表时没把 TUN 的路由恢复回来。

C 类:DNS 回落(约 15%) TCP 层是通的,但域名解析走了 ISP DNS,导致被污染或解析到错误 IP。表现是"国内站飞快、国外站超时",或者 fake-ip 段(198.18.0.0/16)失效后返回真实 IP 被墙。

分辨这三类,靠的是第七章那张判定表,不要凭感觉猜。

2.4 服务端视角:IEPL、BBRv3 与 NAT 超时 ​

本地修完了,还有一半变量在服务端。

NAT 表项老化时间是硬伤。一台中转机上的 conntrack 表,UDP 默认 30 秒、TCP ESTABLISHED 默认 86400 秒(5 天)在 Linux 上是常见配置,但云厂商的安全组、SLB、iptables 往往把 TCP 空闲超时压到 300~900 秒。你睡 8 小时,醒来时服务端早把你的会话清了。

拥塞控制算法在这里起作用。CUBIC 对"连接已死但无 RST"的判定依赖 RTO 退避,可能要重传两三轮(几百毫秒到数秒)才放弃;BBRv3 的探测机制更快收敛,且在高丢包路径上能保住带宽利用率。但注意——BBR 只能加速"识别失败",不能"复活"一个已经被中间设备回收的五元组。

IEPL / IPLC 专线的价值在这里体现得最明显:二层/三层专线路径上没有第三方 NAT 设备,不存在中途被静默回收会话的情况,重连时也不需要重新穿越公网 BGP 收敛。这就是为什么企业级专线用户很少抱怨"唤醒卡死"——他们的路径本身就是干净的。

双 ISP / 多线 BGP 入口解决的是另一个问题:唤醒后你的公网 IP 变了(Wi-Fi 漫游、运营商 CGNAT 重分配),如果服务端只有单一入口,某些运营商的回程路由可能变差。多线入口能让客户端自动匹配到延迟更低的路径。


三、量化对照矩阵:三张表看清全貌 ​

表 1:三类失效路径的量化特征 ​

判定维度A 类 socket 僵死B 类 路由丢失C 类 DNS 回落
占复现样本比例约 60%约 25%约 15%
curl 直连超时阈值10~30 s 才失败立即失败或直接不通3~8 s 解析失败
netstat -rn 默认路由正常指向 utun丢失或指向 en0正常
`scutil --d

数据仅供参考,请以机场官网实时信息为准。遵守法律法规,文明合规出海。