隧道没了,网络图标看起来正常,却什么都加载不出来。你断开了 VPN、卸载了它或者重启了系统,而那道本该在掉线时保护你的拦截仍然立着。客户端窗口解释不了,因为拦截你的不是客户端:这个决定是以你的名义写进内核的,而且没留下任何东西来移除它。在 Windows 上,那个状态位于 Windows Filtering Platform;在 Linux 上,它是 iptables 或 nftables 中的一组规则,通常还带有在启动时重放的快照。下面是我使用的顺序:读取状态,删掉能解释故障的最窄对象,然后找出不断把它放回去的东西。

安全开关是内核状态,不是一项设置

桌面客户端通过添加拒绝隧道外流量的过滤器来实现拦截。在 Windows 上,它们是 connect-redirect 和 transport 层的 WFP 过滤器,其中一些是持久的:它们被记录到 Base Filtering Engine 策略存储中,并在机器启动时、任何 VPN 软件运行之前被恢复。各客户端清理的时机不同。有的在断开时移除过滤器,有的只在正常退出时移除,还有的只有先关掉开关才会移除。在拦截仍然生效时卸载,或者 杀掉进程,过滤器就会留下。

Linux 上的对应物是输出路径中的 drop 或 reject 规则,有时是转发规则,有时是被改动的链策略。这些规则比添加它们的进程活得更久,因为它们是内核状态:杀掉客户端、让它崩溃或者断电,内核都会继续丢包,而没有任何东西留下来撤销它。持久化软件包会再叠加一层永久性:它给当前规则集拍快照,并在启动时恢复,于是下一次启动时拦截已经装好了。

删除任何东西之前,先读取 WFP 状态

Windows 自带你需要的工具。打开提升权限的命令提示符并转储平台状态;转储文件会以 wfpstate.xml 的形式落在工作目录里。

netsh wfp show state
netsh wfp show filters
Get-Service bfe

在 XML 中搜索已离开厂商的名字、Block 动作和层级。破坏普通流量的是 connect-redirect 与 transport 上的过滤器;receive-accept 层上的过滤器通常不是你的原因。如果 Base Filtering Engine 根本没有运行,就去别处找,因为其他任何东西都无法执行那些过滤器。

也要交叉检查普通防火墙视图,因为大多数人只查看两者之一:

netsh advfirewall show allprofiles
Get-NetFirewallProfile | Select-Object Name,Enabled,DefaultInboundAction

当内置配置文件处于开启状态,且没有规则匹配失败的流量时,拦截来自第三方提供商,而不是 Windows 防火墙。这个区别决定你用哪种工具来修复。

用你将要删除时所用的语法读取 Linux 规则

iptables -S 以添加时相同的格式打印规则,因此输出可以直接复用于删除。iptables -L -v -n 会增加数据包计数器,而这些计数器会告诉你某条规则究竟是在匹配你失败的请求,还是只是待在链里。

sudo iptables -S
sudo ip6tables -S
sudo iptables -t nat -S

要读链策略,而不仅是规则:-P OUTPUT DROP 即使没有规则匹配也会拦截流量。要单独检查 IPv6,因为同时安装两套地址族的客户端可能在 IPv4 规则消失后仍留下 IPv6 拦截,而症状就取决于应用偏好哪一套。使用 nftables 的地方还要运行 nft list ruleset;这两个工具通过兼容层共存,一份列表不一定能显示所有可能丢包的规则。

删除能解释故障的最窄对象

在 Windows 上,第一步仍然是厂商自己的移除路径:重新运行安装程序并使用 repair 或 uninstall,或者运行厂商发布的清理工具。残留的服务和计划任务是常见的,而登录时启动客户端的任务会在下次启动时重新安装它的过滤器。卸载在读取客户端注册表项时失败,通常意味着某个服务或任务仍然占着它,而不是注册表损坏了。

Get-ScheduledTask | Where-Object TaskName -like '*vpn*'
Get-Service | Where-Object DisplayName -like '*vpn*'

持久过滤器是最后手段。它们位于 HKLM\SYSTEM\CurrentControlSet\Services\BFE\Parameters\Policy\Persistent\Filter 之下,每个过滤器一个以 GUID 为键的值。先导出该键,记录你删除的 GUID,只删除属于已离开客户端的值,然后重启,让引擎重建状态。不要删除整个键,也不要禁用 Base Filtering Engine:Windows 防火墙和 Defender 依赖它,你会把一次故障换成更糟的一次。

reg export "HKLM\SYSTEM\CurrentControlSet\Services\BFE\Parameters\Policy\Persistent" C:\temp\bfe-backup.reg

在 Linux 上,取 iptables -S 打印出的那一行,把 -A 换成 -D。删除你定位到的那一条规则胜过清空整条链,因为你自己的策略会保持完整。

sudo iptables -D OUTPUT -p udp -j REJECT

如果 drop 位于用户自定义链中,先删除跳转到该链的规则,再用 -X 删除该链。在 drop 规则仍然存在时把链策略设为 ACCEPT,只对真正走到策略判断的数据包有帮助。清空所有表是最后手段:它也会删掉你出于其他原因添加的规则,而且在启用 ufw 时会让服务以为它拥有已不存在的链。

找到把它放回去的所有者

在完成即时修复之后,寻找任何会恢复该状态的东西。在 Linux 上,检查持久化单元是否启用、客户端是否有登录时恢复防火墙的设置,以及 /etc 下是否存在保存的规则文件。在 Windows 上,再次检查启动项、服务和计划任务,然后重启,并用与之前相同的命令重新读取状态。

如果拦截回来了,删除并不是修复;所有者才是。记录是哪个客户端或服务安装它,并在那里关掉该功能,而不是每次断开后都在防火墙层跟它较劲。

用真实流量验证,而不是一次 ping

测试名称解析、TCP 连接和一次新的浏览器会话,然后重启并重复同样的三项检查。HTTPS 失败而 ping 正常,指向的是 transport 层过滤器,而不是网络;只在重启后才重新出现的拦截,指向你还没找到的所有者。

保留证据:导出的 BFE 键,或者变更前抓取的 iptables -S 输出。在宣布修好之前,确认你从哪一层移除了对象、谁可能重新安装它、一次干净重启后的状态,以及在没有 VPN 进程运行时普通浏览是否正常。如果你要把清理写成脚本,iptables(8) 手册页里有确切的删除与策略语法。

相关文章