全隧道切断你的 SSH 会话:固定回程路由
全隧道会截获发往你自己 SSH 会话的回复。为你的管理地址通过旧网关固定一条主机路由,并在重新连接时重新安装它。
在你通过 SSH 操作的那台机器上启动 OpenVPN 客户端,命令执行到一半会话就卡住了。隧道显示已连接,主机可以通过它上网,你的按键仍在到达,但没有任何东西回来。发生变化的不是隧道,而是发往你的数据包的回程路径:它现在匹配了新的默认路由,进入了一条远端网关不会用来到达你桌面的隧道。
redirect-gateway 实际安装了什么
在 def1 形式下,redirect-gateway 并不覆盖默认路由,而是添加 0.0.0.0/1 和 128.0.0.0/1。这两半覆盖了整个地址空间,同时二者仍是比 0.0.0.0/0 更长的前缀,因此任何没有更具体匹配的目的地如今都会进入隧道。这正是需要记住的机制,因为下面每一种修复都是通过安装更具体的匹配来实现的。
你的管理地址就是这些目的地之一。
VPN 服务器把客户端流量转发到互联网。它没有通往你连接所用公网地址的路由,也没有理由把这些数据包当成客户端流量以外的东西,于是直接丢弃。与此同时,你发出的请求仍然到达了主机,这就是会话只在一侧显得还活着的原因。
ip route show table main | head -n 20
ip route get 198.51.100.23找到你的源地址和隧道前的路径
在服务器上列出已建立的 SSH 连接,记下对端地址。那条地址正是回程路由需要覆盖的,而且它未必是你以为的那个:跳板机、NAT 网关或运营商级地址转换都可能替换成别的源。
然后找出隧道启动前承载流量的物理网关和接口。在 def1 方案下,默认路由仍位于两条半默认路由之下,因此信息是现成的,只是用眼睛读一张很长的路由表容易出错。
把两个值和接口名一起记下来,因为你要添加的路由三者都需要。如果主机用策略路由规则来选择上行链路,普通路由可能根本不会被查询,这种可能性会改变修复方式。
固定一条优先级高于半默认路由的主机路由
/32 路由比任何一条半默认路由都长,因此发往你地址的回复会优先选它而不是隧道。下一跳是隧道前的网关,设备是物理接口,这样数据包就留在隧道出现之前它走过的路径上。
ip route add 198.51.100.23/32 via 203.0.113.1 dev eth0用查询而不是看表来验证:答案必须写出 eth0 和网关,绝不能是 tun0。
OpenVPN 出于同样的原因已经会为 VPN 服务器自身的地址添加一条主机路由,所以这个模式并不陌生,管理地址只是又一个例外。不要用 route-nopull 来解决,因为它会丢弃隧道需要的推送路由,包括远端网络和 DNS 选项。
让它在重连后依然有效
手工添加的路由会在客户端重连并安装自己的路由时消失,于是一次修复变成反复断线。把命令挂到安装路由之后运行的环节上。
route-up 脚本或网络管理器的 pre-up 钩子都可以,而可靠的检查是重连之后再执行同一次查询。
在 Windows 上,等价物是用 -p 添加的持久路由,它能挺过重启,但在网络变化后仍必须指向正确的网关。无论哪个平台,都要在路由旁写下理由:下一位管理员需要知道它存在是为了保护管理会话,而不是因为网络设计需要。
当固定路由仍然不够时
反向路径过滤会丢弃从一个接口进入、从另一个接口离开的数据包,而这正是固定路由造成的。在全局关闭任何东西之前,先看 rp_filter 的实际取值,并优先修掉不对称而不是关掉检查。真正生效的是按接口的设置,宽松模式往往只是记录不对称而不丢包。两者都读过再下结论说是过滤器的问题。
使用策略路由的主机既需要规则也需要路由,因为规则库先选定表,然后才查询表中的路由。主表中的路由对于匹配到指向另一张表的规则的流是不可见的。往 main 里加东西之前先读规则。如果某条规则已经覆盖管理流量,例外就应放进那条规则对应的表。
测试时保持一条带外控制台开着,并在断开你正在用的那条之前,从第二个登录再检查一遍。把自身管理流量送进隧道的路由,会切断你正在用来修它的连接。到那时再进去就意味着串口控制台或救援启动,为省掉一步验证付出的代价太差。