发往 10.77.20.0/24 的数据包在 OpenVPN 服务器上要经过两个彼此独立的决策:操作系统必须把它交给 VPN 接口,OpenVPN 则必须知道哪个已连接的客户端拥有该前缀。缺少内核路由,数据包在进入隧道之前就被丢弃;缺少对端路由,数据包在通过隧道之后被丢弃。从远端 LAN 看,这两种故障一模一样,而要判断自己遇到的是哪一种,两边各需要一条命令。

在经典的用户态数据路径里,内核路由把数据包送进隧道进程,iroute 则告诉该进程应由哪个对端接收。两者互不替代,只配置其中一项的设置看起来一直合情合理,直到第一次 ping。

两个决策,而非一条路由

设想一条站点到站点的链路,10.77.20.0/24 位于单个客户端之后。服务器的内核路由(例如 ip route add 10.77.20.0/24 dev tun0)把匹配的数据包送往隧道设备。随后 OpenVPN 查询自己的内部表,该表由客户端配置目录中的 iroute 条目构建,以此决定由哪个对端连接接收这些包。没有内核路由,数据包根本到不了该进程;没有对端路由,数据包到了进程却无处可去。

这种分工容易被遗忘,因为客户端配置文件里的一行路由看起来能同时干这两件事。并非如此:推送路由告诉客户端该通过隧道发送什么,而 iroute 告诉服务器某个客户端背后的地址段归谁所有。它们位于不同的文件,由不同的两侧读取,失败的方向也正好相反。

iroute 放在哪里,它说明了什么

该条目写在以客户端 common_name 命名的单客户端文件里,位于 client-config-dir 之下,形式为 iroute 10.77.20.0/24。文件名匹配绝不是装饰:OpenVPN 会查找认证后的身份,并读取名字完全相同的文件,所以名为 site-a 的文件只对 common name 为 site-a 的客户端生效。如果希望直接拒绝没有此类文件的客户端,请加上 ccd-exclusive,当每个客户端都是站点网关时这正是正确的设置。每个网关保留一个文件,并按其真正签发的证书 subject 命名。

先从容服务器一侧测试该条目,再去指责远端 LAN。状态文件会列出已连接的客户端及其 common name,而以 --verb 4 运行的服务器会在对端连接时记录 iroute 的安装。如果连接时什么也没有出现,说明文件放错了目录,或指向了错误的身份。

陷阱:文件存在,身份不对

看起来正确的 iroute 如果挂到了错误的对端上,比缺文件更浪费时间,因为磁盘上的一切看起来都对。证书改名、重复的 common name,以及会改变生效身份的 username-as-common-name,都会造成这种情况。要检查服务器实际认证了哪个身份,而不是你期望的那个:状态输出才是权威,文件名只是一个查找键。

路由也可能比它的主人活得更久。静态路由和 iroute 条目描述的是一个前缀,而不是可用性保证,因此流量可能继续被送往当前已经断开的对端。

把对端之外的那段路径补完

对端归属只是路径的中段,而不是终点。客户端必须把来自隧道的数据包转发进自己的 LAN,这意味着该主机上要开启 IP 转发,并且有一条允许中转的防火墙规则。远端 LAN 中的主机需要一条通往隧道地址的回程路径:要么是一条指回网关的路由,要么是在客户端一侧刻意设计的 NAT。请用 sysctl net.ipv4.ip_forward 在客户端上确认转发设置,而不是假定发行版的默认值。

如果其他 VPN 客户端也必须访问该子网,安排会再次扩张:服务器需要 client-to-client,而那些客户端需要把该前缀经隧道路由。这些都不是服务器自身路由表所隐含的。

要从真实源主机验证一个目的地,而不是从服务器本身验证。从一台无关的 LAN 主机 ping 客户端背后的主机,一次就能同时检验回程路径、转发规则和对端归属;而一次成功的握手只能证明身份。然后断开拥有该前缀的对端,记录路由会怎么表现,因为那个答案就是你的故障模式。

相关文章