同一台主机上有两个管理入口、一个服务端口,以及一条本想同时帮到两者的策略规则。规则匹配源端口 22,把查找发往一张只知道 WAN 默认路由的表,于是 VPN 入口不再回应新的登录,而直连入口仍照常工作。只要正确读懂选择器,这一切都不神秘。源端口标识的是服务,而不是连接进来的路径。

两个入口,一个服务端口

主机同时拥有 LAN 侧地址和 VPN 地址,sshd 在两个地址上都监听。因此直连会话与隧道会话共用同一个服务端口,区别只在于由哪个本地地址接收连接。

这个差异足以决定回包走哪条路,而基于端口的规则恰恰丢掉了这个差异。两个会话产生的回包源端口都是 22,所以匹配该端口的规则无法把它们区分开。规则把两次查找都送进同一张表,其中一次就拿到了不匹配的答案。修正方法是去匹配真正不同的那个属性。

给每个入口分配各自的端口也可行,但把它当作硬性要求是一种设计选择,而不是 SSH 的固有属性。

先读规则,再读路由表

策略规则是一个有序选择器,第一条返回可用结果的规则就结束查找。按顺序读这份列表,注意哪条规则可能胜出。

然后读那条规则指向的表,因为能成功解析并不等于解析得有用。只有默认路由的表会把来自 VPN 的回包从 WAN 接口发出去。更靠后表中的更具体路由根本不会被查询,这就是为什么再加一张表往往毫无变化。真正要检查的是被选中的那张表。

ip -4 rule show
ip route show table 100
ip route show table main

两份输出合起来才能解释结果;只看其中一份容易得出错误结论。

把规则限定在你真正关心的流量上

区分两个入口的属性是本地源地址。匹配该地址的规则只会把隧道会话送进私有表,而让直连会话留在主表。

这条规则必须配上一张包含私有路由以及该子网回程路由的表。两者都应限定在管理流量上,而不是主机上的所有服务。一条捕获该地址全部流量的规则,也会重定向主机从该地址发出的其他所有流量。这里窄比聪明更重要,因为下一个人得读懂它。

sudo ip rule add from 10.90.0.1 lookup 100 priority 100
sudo ip route add 10.90.0.0/24 dev wg0 table 100
ip route get 10.90.5.20 from 10.90.0.1

如果两个入口被映射到同一个本地地址,这个区别就消失了,设计转而需要能感知连接的状态。

把监听、权限与回包选择分开

会话要工作,必须同时满足三件独立的事:地址存在、服务在该地址上监听、回包能找到返回的路。改路由只能修好第三件。

监听配置有它自己的文件和自己的重载,而且多个监听地址是正常的。认证又是第三个决定,路由例外并不会给任何人访问权。两个入口都要保留主机密钥校验、账户认证和来源限制,不要为了让一条管理路径可用就削弱隧道的其他控制。

保留一条不依赖你正在改动的路径的恢复通道,因为这里的失误会把修复本身锁在门外。

用新会话证明,而不是用旧会话

已经建立的终端什么也证明不了,因为它是在改动之前被放行的。请通过每个预期入口各开一个新会话。

ss -ltnp | grep ':22'
sudo sshd -T | grep -i listenaddress
ssh -v [email protected] true
sudo journalctl -u ssh -n 30

然后重复 VPN 重连并再次尝试,因为能熬过重连的路由,与加进会被重连清空的表中的路由并不是一回事。把被拒绝的来源与允许的来源分开观察,以免被阻断的尝试藏在正常会话背后。把本地地址、规则和表一并保存在结果旁边。如果你选择拆分端口,请把它记为一项运维决定,而不是 SSH 的局限。

相关文章