即使设置了 persist-tun,停止服务仍会移除接口。这是选项在按文档工作,而不是故障。该选项描述的是运行中进程内部的一条重启路径,而 systemctl stop 并不是这条路径。

把两个名字放在一起读,设计就清楚了。一个保留描述符,另一个保留密钥材料,两者存在的原因都是权限被丢弃之后会发生什么。

两种资源与它们所属的重置

OpenVPN 在两种常见情况下会发出自己的重启信号。显式的 SIGUSR1 会这样做,由对端失联触发的 ping-restart 也一样。两者都让进程继续存活并重新执行连接设置,选项的价值正体现在这里。

EventTUN/TAP deviceKey and auth filesAddresses and routesup/down hooks
Soft reset, from SIGUSR1 or from a ping-restart the daemon sends itselfsurvives only with persist-tunsurvive only with persist-keysurvive with the device, otherwise rebuiltrun again only when restart hooks are enabled
Configuration reload, SIGHUPrecreatedre-read from diskrebuilt once the device returnsrun again
Service stoplostre-read at the next startremovedrun at the next start

这两个选项改变的只是软重置那一行,而它们跳过的工作在繁忙链路上正是昂贵的那部分。重新打开设备会拆掉挂在它上面的地址和路由,重建这些又可能丢掉重置开始时仍在途中的报文。停止则会销毁同样的对象,且没有任何保留它们的选项。

这些选项究竟为什么存在

经过加固的 OpenVPN 服务器通常以 root 启动,打开密钥和设备,然后降到无特权用户。降权之后,有些操作就彻底没了。读取权限为 0600 的密钥文件、创建网络接口或修改路由,都可能被拒绝。

没有 persist 选项时,一个安静到足以触发 ping-restart 的对端就会逼出这些操作。即使配置毫无变化,重连也会失败。进程看上去像是无缘无故地死去,而日志显示的是权限错误,不是网络错误。

有了它们,软重置会复用特权阶段已经取得的东西。这里没有魔法,进程也没有真的扛过什么。资源的获取只是被推迟了。

读第一个错误,而不是最后一个

重置失败时,要读第一个错误,而不是最后一个。受保护的密钥、auth 文件、接口 ioctl 和外部钩子是四种不同的资源。保住其中一个,并不说明能否访问其他几个。调用防火墙工具的钩子仍然需要该工具要求的权限。

钩子有自己的开关。OpenVPN 手册 把 persist 选项与 up-restart 配在一起,后者决定重启后是否会再次运行 up 和 down 脚本。因此,接口仍然在线并不能告诉你 resolver 集成是否刷新过。

# which restart path just happened?
journalctl -u openvpn-client@office --since '10 min ago' | grep -E 'SIGUSR1|ping-restart|Restart pause|NOTE:'

# did the interface survive, and do its routes still exist?
ip -brief link show tun0
ip route show dev tun0

这一对命令能在几秒内区分两种故障模式。接口还在但路由缺失,是钩子或路由的问题。 接口消失,则意味着进程确实结束了。

保留下来的接口不是 kill switch

如果要求是流量绝不能离开隧道,保留设备并不能做到。另一地址族的路由、第二条默认路由,以及进程完全退出的情况,都仍在该选项的范围之外。

把两项要求分开处理。跨重连保留隧道对象,对守护进程只是便利。隧道断开时阻断出站流量,是带自己的规则和自己的测试的防火墙策略。把两者混为一谈,得到的配置看上去像在防御,行为却只是尽力重连。

先给事件命名,再作诊断。软重置、连接尝试失败和停止服务是三种行为不同的三个事件,只有第一个才是这些选项所描述的。

相关文章