两种设计承诺的是同一件事:隧道断开时,不会有任何流量明文外泄。一种在拆除时执行脚本,把网卡全部切掉。另一种保留一条防火墙规则,完全不向 VPN 客户端提出任何要求。它们的差别在于谁有权执行动作、缺口期间会发生什么,以及之后机器如何重新上线。在这三点上比较,比争论谁更严格更有用。

同一件事,两种机制

只要配置文件允许执行脚本,客户端就能在隧道建立和拆除时运行钩子。这个钩子跑在客户端的生命周期内,也就是连接正在关闭的那一刻。防火墙规则则属于系统,不关心客户端是在运行、崩溃还是被杀掉。

PropertyTeardown scriptFirewall unit
Runs when the client is killedNoYes
Needs privilege at teardown timeUsually yesConfigured once, at boot
Acts on the whole machineOften, including recovery pathsOnly on the flows it matches

那张表就是全部论点。只在正常退出时运行的脚本,覆盖的恰恰是你最不担心的情况;真正要紧的是崩溃或网络突然消失,而这两种都不会产生干净的拆除。

拆除钩子能做和不能做的事

文档里的做法是遍历网络接口,把除 loopback 之外的一切都关掉。物理设备一关,就没有东西能跑到隧道外面,这正是本意。

#!/bin/sh
for dev in /sys/class/net/*; do
  name=$(basename "$dev")
  [ "$name" = lo ] || ip link set dev "$name" down
done

它同时会关掉本地网络、机器上托管的任何服务,以及你用来修问题的那条路径。在笔记本上,无线设备事后有时还得手动切回来,于是一次短暂的故障变成一场维修。更糟的是,钩子可能根本没有权限做这些:客户端一旦放弃权限,拆除脚本就以非特权账户运行,每一次接口变更都因权限错误而失败,而这条错误恰好写在一个没人看日志的时刻。

在信任钩子之前,先确认客户端是如何启动的。以 root 手动执行时能用的脚本,并不能证明开机时启动的那个服务也能用。

防火墙规则的不同之处

只放行隧道流量的规则,在客户端进程消失后仍然有效,因为内核并不依赖那个进程。规则也更精准:它只拦它匹配的那些流,对 loopback、本地子网和恢复路径不闻不问,所以不会把机器困死。

代价是这条规则必须由拥有主机防火墙的人来编写和维护,而且它必须 指名客户端真正创建的隧道设备。这意味着规则和配置文件要一起维护;接口一改名,留下的防火墙就会什么都拦、什么都不解释。

两种机制谁也不替代谁。一条改完却从不重新加载的防火墙规则,和一个从不运行的脚本一样是空话。

有没有缺口,由 persist-tun 决定

隧道设备在断开后仍然存在时,指向它的路由也仍然存在。此时流量是被丢弃而不是被改道,所以默认路由不会回落到物理网关。正是这个行为,让泄漏在任何防火墙规则介入之前就很难发生。

没有这个设置,设备会被移除,路由随之消失,默认路由就可能重新指向物理网关。会不会这样取决于平台和客户端,这也正是必须观察而不是假设状态的原因。把这项设置当作 kill switch 的一部分,而不是性能选项。

保住重连通道,然后测试四种状态

严格封锁也可能让客户端无法重连,因为在其他一切都被拒绝时,客户端仍然需要一条经物理网关到服务器的路由。要在隧道建立之前为那一个地址装好主机路由,并先解析名字,因为等你需要它的时候,DNS 可能已经被封了。

ip route add 198.51.100.20/32 via 192.168.0.1 dev wlan0

然后验证状态,而不是验证标签。有意识地走一遍四个时刻:没有隧道的冷启动、健康会话、没人要求的断线,以及恢复之后的会话。每个时刻都读取默认路由,并发出一个能看到出口地址的请求。一个在隧道断开时放弃的应用程序,说明不了邮件客户端或更新程序的情况,所以再用一个后台程序重复最后一项检查。把规则、接口名和恢复步骤写下来,因为没人能验证的 kill switch 只是传闻。

相关文章