为什么 OpenVPN 客户端背后的子网无法访问?
OpenVPN 无法猜到远端 LAN 归哪个客户端所有。客户端文件里的一条 iroute 记录、一条回程路由以及转发,才能让该子网变得可达。
发往服务器隧道地址的 ping 能通,客户端自己的路由表看起来也没问题,但它背后的 LAN 依然无法访问。客户端自己的路由表看起来正确,服务器有一条指向隧道的路由,数据包却依旧消失。这是 layer-3 隧道的经典症状:它并不知道自己的某个客户端同时还是网关。
tun 接口没有按客户端区分的下一跳。内核看到的是一个拥有多个对端的单一接口,因此“哪个客户端应当接收发往远端 LAN 的数据包”这个决定必须在 OpenVPN 进程内部完成。该决定通过 iroute 配置;没有它,数据包就无处可去。
服务器按客户端而非按接口选路
当服务器需要把数据包发往某个客户端的隧道地址时,答案很明确:该地址属于那个客户端的会话。当目的地是该客户端背后的整个子网时,该地址不属于服务器认识的任何人,内核对隧道的路由只会说“从这个接口出去”,而不会说是哪个对端。
OpenVPN 用一张内部路由表解决这个问题,它把目的前缀映射到特定客户端。这张表由 iroute 记录构成,所以服务器主机上的一条普通路由并不够用。内核路由把数据包送到隧道接口,内部映射再决定由哪个会话接收。这也是为什么修复永远不会出现在内核的路由输出里。
如果缺少映射,数据包到达服务器、匹配上隧道路由,却因为没有客户端认领该目的地而被丢弃。没有任何地方记录一条清晰的错误,因此这种故障看起来像一个静默黑洞,而不是路由环路。
编写指明客户端的 iroute 记录
iroute 位于针对单个客户端的配置文件里,而不是服务器主配置里。用 client-config-dir 选项把服务器指向某个目录,然后创建一个以客户端证书通用名为文件名的文件,并把前缀放进去。
对于 LAN 为 10.20.0.0/24、证书通用名为 branch-router 的分支,该路径下的文件里有一行类似 iroute 10.20.0.0 255.255.255.0。主配置只需要那个目录选项;前缀留在客户端这一侧。
通用名正是脆弱的部分。用不同 subject 签发的证书,或者文件按证书命名、部署却按用户名认证,都会产生一个永远不会被读取的文件。检查服务器为会话实际报告的名字,并考虑启用那个让不匹配的客户端直接失败、而不是静默地不带路由连上的选项。把文件名改成与证书一致,是五分钟就能做完、却能避免一个漫长夜晚的修复。
一个客户端可以拥有多个前缀。为每个网段加一行 iroute,并把清单放在该客户端其余设置的旁边,让映射保持可审计。
在双向上建立回程路径
把数据包送达远端 LAN 只是一半工作。客户端还必须把该 LAN 的流量送进隧道,通常通过向客户端推送一条路由完成:像 push "route 10.8.0.0 255.255.255.0" 这样的行告诉每个客户端如何到达服务器侧的网络。对分支而言,等效的路由把需要访问的服务器侧内容全部指向隧道。
回程方向需要远端有人负责。那一侧必须有人知道回程怎么走:在 LAN 路由器上加一条指向服务器的静态路由,或者在流量进入 LAN 之前于服务器上做地址转换。静态路由让原始地址在日志和访问规则里保持可见,但需要 LAN 所有者的配合;地址转换自成一体,却掩盖了究竟是谁连上来的。
服务器主机上必须开启转发。否则内核会接收来自隧道的数据包,却拒绝把它交给 LAN 接口;从客户端角度看,这与缺少 iroute 完全一样。
用正确的探针检查可达性
分层测试,而不是 ping 远端然后猜。从服务器侧确认该前缀的内部映射存在,且内核路由指向隧道。从客户端侧确认推送的路由已经安装,且其通往 LAN 地址的默认路径是隧道。
然后观察数据包。在隧道接口抓包可以看出服务器是否在朝客户端发送,在 LAN 接口抓包可以看出它是否在转发。若前者有包而后者没有,问题在转发或过滤规则上;若两边都没有,说明更早的查找就已经失败了。
两台由隧道相连的服务器在两侧重复同一条规则:各自都需要一条指向对方地址池的 iroute,也需要让路径对称的路由与转发。这里路由与策略是两回事。路由决定数据包能去哪里,FORWARD 规则决定它是否被允许,因此只要过滤器不另行规定,拥有两条 iroute 的客户端两条前缀都能到达。