路由器 VPN 上没有按设备切换的开关;你找的那个开关,是一条替你写策略路由规则的捷径。一旦一台笔记本、一个端口段、一份地址列表或一个 LAN 网段必须走隧道,而其他一切都留在 WAN,你就是在构建策略路由,而不是在打开某个功能。这项工作有三个活动部件,每个的失败方式都不同:没有路由的标记表看起来像沉默,标记加在错误的链上看起来像什么都没发生,缺失的 NAT 规则看起来像连上了却什么都不转发的隧道。还有两个部件常被漏掉,因为它们从不出现在 mangle 规则里,那就是 DNS 和 IPv6。

迷思:隧道是客户端设置

路由器侧的 VPN 客户端只擅长一件粗暴的事:把 0.0.0.0/0 放进主路由表,并指向隧道。WireGuard 从 AllowedIPs 推导出这一点, OpenVPN 则从服务器下推的 redirect-gateway 推导,两者都会改变路由器后面每台主机的路径。收窄 AllowedIPs 或加上 route-nopull,改变的是客户端安装的内容。它并不改变这套安装作用于谁,因为客户端根本看不到设备。它只看到数据包。

按来源或目的选择是路由决策,所以属于路由器的策略层:RouterOS 上是一条 mangle 规则加第二张表,OpenWrt 或 DD-WRT 上是 MARK 加 ip rule,pfSense 上则是网关为隧道的防火墙规则。 Netgate 的 WireGuard 客户端配方 就是猜测之前该读的那份,因为在该平台上,规则的网关、接口分配和出站 NAT 条目必须先全部一致,单个客户端才能用上隧道。

迷思的另一半是第二台路由器。再买一台盒子,或把设备挪到访客 SSID,往往确实管用,原因不在硬件:它把设备放进自己的子网,从而给你固件本来就理解的来源选择器。如果能在你已有的路由器上定义那个网段,多出来的盒子只是解决了你原本没有的问题。

第一部分:一条不是默认路由的隧道

在 RouterOS 上,添加不带默认路由的客户端,并给隧道它自己的表。第一条路由是标记过的默认路由;第二条是失败那一半。

/ip route add dst-address=0.0.0.0/0 gateway=VPN_GATEWAY_IP routing-mark=vpn distance=1
/ip route add dst-address=0.0.0.0/0 type=unreachable routing-mark=vpn distance=2

不可达路由,正是标记表胜出主表里例外清单的原因。隧道掉线时,被标记的数据包立刻拿到不可达,而不是悄悄回落到 WAN,所以故障出现在应用的报错里,而不是你一小时后才去看的定位页面上。

RouterOS 7 拆开了 6.x 曾绑在一起的东西:先用 /routing table add name=vpn fib 声明表,然后在路由和 mangle 规则里按名字引用它。没定义表就直接写 routing-mark 的 6.x 片段会干净加载,却什么都匹配不到。另一个经典情形是两半 0.0.0.0/1,或同一张表里两条距离相同的默认路由:内核只会挑一条,而且行为可能在重启后改变。

第二部分:在查路由之前打标记

选择器决定哪些数据包进入那张表。RouterOS 通常从目的策略用的地址列表开始,或从设备策略用的源地址开始:

/ip firewall mangle add chain=prerouting src-address=192.168.88.50 action=mark-routing new-routing-mark=vpn
/ip firewall mangle add chain=prerouting dst-address-list=via-vpn action=mark-routing new-routing-mark=vpn
/ip firewall address-list add list=via-vpn address=203.0.113.10

Linux 固件用 netfilter 标记和一条把已标记数据包导向你填好的表的规则做同样的事:

iptables -t mangle -A PREROUTING -p tcp --dport 27000:27050 -j MARK --set-mark 0x200
ip route add default dev tun0 table 100
ip rule add fwmark 0x200 table 100

链不是风格选择。数据包到达 FORWARD 时路由查询已经发生,所以在那里的 MARK 或 DROP 只能改写或丢弃。标记必须落在 PREROUTING(转发流量)和 OUTPUT(路由器自身产生的流量),路由器的 DNS、NTP 和更新检查就住在这条路上。正是这条出站路径,让完全走隧道的一套配置在泄漏测试里仍然由 ISP 的解析器作答。

设备策略需要一个不会漂移的匹配。在 DHCP 里保留地址,或为专用网段匹配接口;后来换到另一台设备的地址,会悄悄把隧道套到错误的客户端上。为每个 SSID 各建一个带自己子网的网桥,是同一思路更耐久的版本,它也顺便给 IPv6 规则留了位置。

第三部分:NAT、DNS 和 LAN 例外

从隧道出去的流量需要一个对端会回应的源地址。把地址转换限定在隧道接口上:RouterOS 用 out-interface=VPN_NAME,Linux 用 -o tun0,pfSense 则用匹配的出站 NAT 条目。没有这个限定的 masquerade 规则会改写你本想放过的流量,症状就是没走隧道的那台设备丢掉了公网地址。

LAN 例外制造的求助帖比隧道本身还多。如果标记表里只有一条默认路由,被选中设备发往打印机或 NAS 的目的地址就会命中所说的默认路由,一头钻进隧道。把 LAN 路由加进同一张表,或在选择器里排除本地子网;两种都行,而以后添加 VLAN 时,排除更容易保持正确。

DNS 走的是另一条路。路由器转发的查询源自路由器本身,跟随 OUTPUT,而不是被转发的标记,所以完全走隧道的笔记本仍然可能经 ISP 完成解析。把客户端指向一条可经隧道抵达的解析器,或给路由器自己的 port-53 流量打标记,然后检查解析出的地址,而不是假定答案随着网页请求一起旅行。

当规则匹配了,应用却还在走 WAN

目的列表老得很快。某个域名解析到一个不在列表里的地址,于是请求走了 WAN,而你针对列表内地址做的每次测试都是通的。用客户端所用的同一个解析器刷新列表,并且当你确实想让某台设备的全部流量都走隧道时,优先用基于来源的选择器。

IPv6 是那条安静的旁路。设备有第二套地址族,带着自己的默认路由和自己的规则集,只匹配 IPv4 地址等于只做了一半策略。把同一个选择器应用到 ip6tables 或 nftables 的对应写法上,或者有意而不是顺手地关掉那个网段的 IPv6。

已建立的连接同样要紧。改动之前建立的 conntrack 条目会一直沿旧路径走到过期,所以编辑期间没关掉的浏览器,看起来就像一条从未生效的规则。判断策略之前,先开一个新连接,或者清掉状态。

用两台设备和一次重启来验证

测那台该走隧道的设备和那台不该走的设备,比较第二跳,而不是速度数字。两边各跑一次 traceroute,第二跳会显示隧道网关或 ISP 网关;如果两边看起来一样,说明标记或表没有按规则说的做。测试请用新连接。

然后重启。启动顺序正是能用的策略坏掉的地方:客户端必须在不可达路由被装上之前就起来,而一份会恢复旧规则集的存档快照会覆盖你的表。确认每条规则都在已保存的配置里,标记默认路由在开机之后仍然存在,而不只是你刚敲完的那一刻。

如果哪一步失败,先撤掉标记,看正常流量是否回来,再撤掉表。这个顺序能告诉你追的是路由问题还是防火墙问题,也能在你收尾时给家里留一台还能用的打印机。

相关文章