重启之后的第一次连接是正常的。此后每一次尝试都会卡在连接阶段,最终超时,而唯一可靠的解法是再重启一次。这个规律既不是配置文件的问题,也不是凭据的问题。后台还有东西握着上一次尝试留下的状态,下一次尝试就与它相撞。

在 macOS 上,这个东西通常就是 IKE 守护进程。

规律:只连上一次,然后超时

每次启动正好只能工作一次的连接,指向的是这样一种状态:它能扛过断开,却扛不过重启。配置文件两者都能扛过,所以配置文件不是变量。凭据同样两者都能扛过,除非服务器锁了账号。重启时改变的是正在运行的守护进程集合以及它们占用的套接字。

这在你打开任何一条日志之前就缩小了排查范围。问题变成:哪个进程占着 IKE 端口,它以为自己正在做什么。

两个守护进程,一个端口

系统会运行 racoon 当连接使用 IKE 版本 1 时。当前的版本还会引入 iked 用于 IKEv2 和更新的 IPsec 协议栈。两者都可能占用 UDP 500 和 UDP 4500,也都可能在密码提示处取消连接尝试、或因网络切换而中断之后,留下一个半开的安全关联。

一旦出现这种情况,下一次尝试就不是从干净状态开始。它会在旧关联仍占据对端状态时尝试协商,结果是交换停滞,而不是给出一个明确的错误。用户看到转圈的指示,然后是超时。

动手之前先找出归属者,因为这个答案决定了你要重启哪个服务、接下来看哪份日志。

sudo lsof -i :500 -i :4500
ps -ef | grep -E "racoon|iked"

把输出读成两个事实:哪个进程占着端口,以及是否存在不止一个。两个 IKE 守护进程争抢同一个套接字,可以解释很多看起来像服务器不稳的间歇性行为。

重启任务,而不是进程

在较旧的系统上,杀掉进程、让下一次连接把它重新拉起来就够了。在当前版本里,守护进程由 launchd 管理,只给进程发信号可能让任务停在既不运行、也没有安排启动的状态。正确的做法是停止再启动这个任务。

sudo launchctl stop com.apple.racoon
sudo launchctl start com.apple.racoon

如果 launchd 里没有这个任务的记录,按名字杀掉进程仍然会在下一次尝试时强制重新协商。两种情况下检验方法一样:重新连接时不应该有任何进程占着端口。用同一条命令确认, lsof 而不是假定重启已经生效。

然后连一次,从头读客户端日志。谈判越过第 1 阶段、随后报告发送失败的,是在重复本地套接字的问题; 被对端拒绝的谈判 则完全是另一回事。

当拒绝的是服务器

并不是每第二次连接都失败在本地。服务器可以在断开后的一段固定时间窗内拒绝新的登录,而客户端的超时看起来和卡死的守护进程一模一样。区分起来很便宜:如果重启守护进程没用,但换个账号立刻能连上,那这个时间窗就在服务器侧,Mac 从来不是问题所在。

拆分隧道策略会从另一个方向造出同样的形状。要求所有流量都走隧道的服务器,会在协商期间丢弃配置为拆分隧道的客户端,而客户端报告的是超时,不是策略拒绝。把配置文件改成全部走隧道也许能连上,但那是要和网络所有者一起做的策略决定,不是可以悄悄施加的修补。

再次改动客户端之前,先读对端的日志。拒绝、策略不匹配、静默丢弃,是三种不同的原因,在 Mac 屏幕上都表现为“正在连接”。

旧客户端与 64 位内核

旧的 Cisco IPSec 客户端会加载一个为 32 位和 PowerPC 系统构建的内核扩展。在启动进 64 位内核的 Mac 上,这个扩展无法加载,客户端很早就以错误 51 失败,因为它根本够不到 VPN 子系统。再多次重启守护进程,也修不好一个架构不对的内核扩展。

重新加载扩展可能维持一段时间,然后在下次启动时再次失败。长久之计是:在仍受支持的环境里运行旧内核,或者换用系统自带的内置客户端。在为一个根本加载不了的第三方客户端耗掉一整晚之前,先确认架构。

  • 复现一次,然后在动配置文件之前确认哪个守护进程占着 UDP 500。
  • 读第 1 阶段之后的第一个失败:发送错误指向本地套接字。
  • 重启 launchd 任务,而不只是进程,并确认端口已经空出来。
  • 用第二个账号试一次,把服务器侧的重新认证时间窗和客户端故障分开。
  • 在把责任推给客户端之前,先记下内核架构。

只工作一次的 VPN 是状态问题,除非能证明不是。清理守护进程比重启更快,而且它保留了重启会丢掉的日志证据。

相关文章