覆盖 FORWARD、OUTPUT 与 IPv6 的路由器断网开关
只在 FORWARD 里做的断网开关,会让路由器自身照常外泄;只写 IPv4 规则,则 IPv6 一直敞开。把链的顺序排好,并在隧道断开时实测。
你登录路由器,从论坛帖子里粘贴一套断网开关规则,隧道一断,局域网立刻上不了网。两天后,路由器仍在通过普通 WAN 更新它的动态 DNS 记录,访客笔记本也仍能访问 IPv6 站点。这两种行为都正常,因为粘贴的规则只覆盖了一条链和一个地址族,而这两者都不是整台路由器。按顺序走一遍数据包路径,就能看清究竟漏了什么。
从路由器出发,而不是从笔记本出发
路由器级别的策略对小办公室很有吸引力:它身后的每台设备都继承同样的行为,也没人会忘记开启客户端开关。每次固件更新后,它也只给你一个地方去测试。但它不会自动覆盖路由器自身的流量,也不会覆盖第二个地址族,而大多数失败部署背后的两个缺口正是这两处。
动手之前,先在纸上记下三件事:WAN 接口名、LAN 网桥名、VPN 端点地址和端口。规则里三样都要用到,而靠猜接口名,正是看起来正确的规则集却什么都不做的最常见原因。
走一遍数据包可能经过的三条路径
从笔记本发往互联网的流量是被转发的,因此会经过 FORWARD 链。路由器自己产生的流量,比如 DNS 转发、NTP 查询、DDNS 更新和 VPN 控制通道,会经过 OUTPUT。发往路由器本身的管理流量则经 INPUT 到达。
所以只在 FORWARD 里搭建的断网开关只保护客户端,别的什么都不保护。这张表说明了开头那幕泄漏为何是可预料的,而不是什么谜团。
| Packet path | Example | Chain that sees it |
|---|---|---|
| LAN client to WAN or tunnel | Browser request | FORWARD |
| Router-originated | DDNS update, NTP | OUTPUT |
| Addressed to the router | SSH from the LAN | INPUT |
大多数设计里不需要在 INPUT 上做断网开关,但隧道断开期间,你必须让管理访问保持可用。否则 VPN 第一次出故障时,你就得带着 console 线开车去办公室。
把规则排好序,让隧道仍能建立
能用的断网开关是一张以拒绝收尾的允许清单,而顺序才让它成为断网开关,而不是一块砖。先放行回环、VPN 端点、已建立连接的回包和隧道接口;最后再拒绝其余流量。
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -d 198.51.100.7 -p udp --dport 51820 -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -o eth0 -j REJECT
此时端点必须是地址,而不是主机名。一旦最后的拒绝规则挡住 DNS,写明服务商主机名的规则就再也解析不出来,重启之后隧道再也不会启动。如果服务商会轮换端点,就在应用策略之前解析好名字,并用同一个脚本刷新那条规则。
客户端流量在 FORWARD 里照同样的形状复制一份,并把一条明确的管理规则放在拒绝规则之上。这条管理规则要窄,但要够得着;关掉第一个 SSH 会话之前,先开第二个会话确认。改断网开关时把自己锁在外面太常见了,值得提前计划。在运行 WireGuard 的路由器上,对端的 allowed-ips 决定什么能进入隧道,而这些规则决定什么被允许不经过隧道就出去。
IPv6 需要自己的前门
上面说的全是 IPv4。如果你的局域网有可用的 IPv6,而隧道只承载 IPv4,那么每台被覆盖的设备仍有一条你的新规则永远看不到的直连互联网路径。双栈笔记本会欣然选它,而断网开关只在用 IPv4 测试的人眼里像是坏了。
ip6tables -A FORWARD -i br-lan -j REJECT
ip6tables -A OUTPUT -o eth0 -j REJECT
ip6tables -A OUTPUT -o lo -j ACCEPT
这里顺序同样重要:回环放行必须排在 WAN 拒绝之前,否则路由器上的本地服务会开始以奇怪的方式失败。如果网络根本不用 IPv6,在 WAN 和 LAN 上直接关掉它更简单,也不会留下什么可忘的。
有些 VPN 客户端只安装 IPv4 的默认路由,对现有的 IPv6 默认路由不闻不问。隧道连上之后要检查路由表,而不是假定客户端已经处理好了。
把隧道断开,实证一遍
从路由器上停掉隧道,然后按下面的顺序从三个地方测试:
# systemctl stop wg-quick@wg0
# ip route get 203.0.113.1
# curl -m 5 -sS http://example.com || echo blocked
# iptables -vnL OUTPUT | head
# ip6tables -vnL OUTPUT | head
路由查询告诉你数据包会去哪里,计数器告诉你规则到底有没有命中。一次失败测试之后包数为零的规则什么也没保护,而常见原因是接口名和你输入的不一样。要显式测试路由器自己发出的流量,因为局域网一侧的测试碰不到 OUTPUT。
在你需要之前就定好失败模式。fail-closed 让客户端一直离线,直到隧道恢复;fail-open 恢复普通 WAN 访问,却让你在意的那部分流量失去了意义。指向 VPN 地址池、度量值很高的 blackhole 路由,是 fail-closed 设计中有用的第二层,因为隧道路由消失时它会保留下来,而不是回落到默认路由。
只有当局域网客户端、路由器自身发出的请求和 IPv6 请求在隧道断开时全部失败,并且隧道恢复后三者又都能工作,才算这套配置合格。