在一个正常工作的 openfortivpn 会话里,四个阶段层层叠加。客户端先在网关完成认证并建立 TLS 连接,然后在这个连接里运行 PPP,接着获得地址和 DNS 信息,最后应用主机路由和解析器设置。每个阶段都可能在下一阶段尚未就绪时仍然存活,正因如此,“已连接”在支持帖里才是个如此滑溜的词。

消除大部分混乱的决定,是最后阶段归谁所有。要么 VPN 客户端自己应用主机改动,要么由别的组件来做;把两者半途混用,正是路由和解析器文件最终描述不同会话的原因。

看起来像一条连接的四个阶段

第一阶段是准入。具名认证域或一次性代码,并拿到继续所需的参数。当客户端打印出已连接到网关时,那只说明 TLS 传输已经建立。它不说明 PPP 已完成协商,更不说明某个内部应用可以访问。

第二阶段是 PPP 链路。客户端针对隧道设备启动 PPP 进程,双方协商链路选项和地址。只有在这之后,主机才知道会话使用哪些本地和远端地址,也只有这时,指向隧道的路由才有意义。

第三阶段是把这些信息应用到主机上,所有权问题就出在这里。客户端可以自行添加路由并重写解析器配置,也可以被要求退后,把这两件事交给该机器上其他管理网络的组件。该项目的 README 把这些描述为彼此独立的职责,而不是一个自动行为。

谁修改路由表

默认情况下客户端就是所有者。它会经物理接口添加一条通往网关的路由、一条针对隧道对端的路由,以及会话获准使用的各网段路由,并在进程退出时移除它们。这个默认值在只有一套网络管理器、没有其他 VPN 软件的笔记本上很方便。

只有当别的组件真正拥有路由能力时才选择交接。网络管理器集成、容器网络命名空间或全站路由代理可能已经知道如何安装和清理同样的前缀。此时传入 no-routes 选项,可以避免两个写入方争抢同一张表——这种竞争表现为某个子网间歇性丢失,而不是干净失败。

重叠是交接的第二个理由。如果推送的路由包含本地网络已在使用的网段,客户端无法替你解决冲突。必须有人决定该网段是经隧道、经物理链路还是经更具体的路由可达,这个决定属于掌握拓扑的人。

清理是第三个理由。客户端在移除路由之前崩溃,会留下一个比会话活得更久的黑洞,下一次连接可能又在其上叠加一条重复项。拥有路由的一方也拥有验证旧条目已消失的责任。要把这项检查纳入启动路径,而不是当成可选的清理步骤。

谁拥有 DNS 和解析器文件

DNS 是同一个决定,只是边角更锋利。客户端可以接受网关或 PPP 对端上报的 DNS 服务器,并把它们写入主机的解析器配置。在简单机器上这正合你意,因为内部名称只在隧道开启时才解析。

在现代 Linux 主机上,解析器可能由本地 stub 管理;绕过该 stub 改写解析器文件,会得到一个看起来正确、行为却不一致的配置。如果主机已经在运行解析器管理器,就让它拥有 DNS,并通过其支持的接口把隧道服务器喂给它。客户端的 no-dns 选项在那里才是正确选择,而不是权宜之计。

拆分 DNS 与“谁写文件”是两个问题。把所有查询都送进隧道简单但慢;只把内部区域送进隧道,需要按域配置,而多数简单客户端并不提供。在启用客户端的解析器改动之前,先决定主机需要哪种行为。这里选错,看起来就像名称解析故障,哪怕隧道本身没问题。

权限、就绪状态与交接

创建隧道设备、修改主机路由或解析器状态,都需要提升的网络权限。常见错误是据此认为所有东西都该以 root 运行,包括浏览器或使用隧道的应用。让特权部分保持最小:客户端及其配置文件归管理员所有,用户应用则保留普通账户。

就绪状态应当按消费者分别定义。监督进程可以在客户端进程运行、隧道设备存在时就认为服务已启动。备份任务则应只在存储端点能经隧道响应时才认为 VPN 可用。这是两种不同的检查,用较弱的那种去满足更强的目的,会产生启动过早、间歇失败的任务。把选定的定义写下来,因为监督进程与任务对就绪状态意见不一致,就是一张迟早要开的支持工单。

用四个观察来验证这份契约:网关阶段完成,PPP 分配地址,预期路由存在,并且对内网主机的请求成功。然后停止客户端,确认上述每一项都被移除或恢复。如果隧道关闭后主机仍能解析内部名称,或仍保留某条路由,那么所有权交接并不完整。

相关文章