隧道已经建立,你关掉一个闲置终端,客户端里一个键都没按,VPN 却断了。在 Linux 版 AnyConnect 客户端里,报告的原因是会话记账而不是路由:客户端记录隧道建立时存在哪些登录会话,并把其中任何一个的结束当作拆除隧道的理由。下面的检查按顺序进行,第一个失败的检查会告诉你是否真的遇到了这种行为。客户端的常规输出里从不提到终端,所以第一反应去怪集中器,通常是白费力气。

不要从修改系统文件开始。每一步要么解释掉线,要么排除一个候选,便宜的步骤先做。

把会话掉线与底层链路掉线分开

断开后立刻重连。如果隧道在几秒内、在同一网络、不需要新凭据就恢复,那指向的是本地触发因素,比如被关闭的会话,而不是上行链路故障、Wi-Fi 漫游或 集中器的空闲超时。如果隧道卡在 Connecting 上,或者必须换网络才能连上,那是另一篇文章。

记下隧道建立时开着什么:运行客户端的终端、任何第二个 shell、图形登录,以及来自另一台机器的任何 SSH 会话。这份清单马上就会用到,而下次掉线后凭记忆重建并不可靠。

检查客户端在连接时记录了什么

客户端并不检查进程父级或终端归属。它给连接时存在的登录会话集合拍一个快照,之后与这个集合比较;一旦有成员消失,比较失败,隧道也随之断开。所以关掉你连接时用的那个窗口可能毫无影响,而关掉一个无关的备用终端却会杀掉连接:关键在于那个会话是否在最初的集合里。这是集合比较而不是计数,因此替换一个会话也可能和失去一个同样致命。

在改动任何东西之前,先列出客户端能看到什么:

who
w
last -n 10
sudo ls -l /run/utmp

who 的每一行都是 utmp 里的一个用户进程条目。如果你的会话出现两次,一次属于图形登录、一次属于终端,那么两个条目在连接时都存在,两个都是候选。

只从 utmp 中删除你自己的条目

公开的变通做法是在客户端启动前,让你即将使用的会话在 utmp 中不可见。一个小 C 程序遍历 getutent(),匹配 ut_line 等于标准输入 TTY 的那条记录,然后改写它。顺序是有意的:setutent()、读取条目、复制它的 ut_id、把 ut_type 设为 DEAD_PROCESS、清空用户和线路字段、用 pututline() 写回,最后 endutent()。

编译它,然后从将要启动客户端的那个 shell 里以 root 运行:

sudo ./utmpremove && /opt/cisco/anyconnect/bin/vpn -s connect vpn.example.edu

两种失败模式解释了大多数“工具没反应”的报告。它作用于附加在标准输入上的 TTY,所以在没有终端的脚本或 cron 任务里运行,就没有任何东西可匹配。另外 /run/utmp 对普通用户不可写,所以非 root 运行会静默失败,除非你检查退出状态。

验证隧道能否挺过关窗

去测试机制,而不是假设它。从你打算保留的会话连接,打开第二个终端,关掉第二个,然后用 vpn state 和查看接口列表确认隧道仍在。如果隧道存活,触发因素是特定类型的会话而非所有会话,你就找到了必须保持打开的那个会话。

如果它仍然掉线,被监视的会话在别处,最常见的是启动你终端的那个图形登录,删掉一个 TTY 条目帮不上忙。用第一步的完整清单重复观察,而不是做更多手术。

什么时候改写 utmp 是错误答案

utmp 是共享记账,不是私有表。改写一个仍然活着的会话条目,会让 who、w 以及一些审计工具少报它,而之后关闭同一条目的程序可能直接失败。把工具限制在自己的会话上,保留原文件副本,不要把它变成一个每次启动都清空所有条目的服务。

如果你的站点为合规收集会话记账,这个变通做法与该要求冲突,受支持的答案是更换客户端版本或访问方式,而不是打补丁的 utmp。对大多数人来说,侵入性最小的做法就是从会开一整天的会话里启动客户端,然后别去动那个窗口。

在关闭工单之前,记录隧道建立时客户端在监视哪个会话。这一行才能把神秘的断线变成下一个人在这台机器上可复现的条件。

相关文章