应用在自己隧道地址上应答,而你为它记录的地址却哪儿也去不了。OpenVPN 日志里没有任何抱怨,对端显示已连接,集线器里也只有一个客户端。缺口在于:客户端上的第二个地址是两件独立的事——主机必须拥有它,集线器必须知道哪个对端拥有它。只顾一头而不管另一头,流量要么死在集线器,要么到了却没有回家的路。

别名是本地的事,归属是远端的事

隧道地址是协商出来的,所以两端在握手时都会知道。额外的服务地址完全不会被协商,因此单独加上它并不会改变集线器上的任何东西。客户端可以立刻拥有这个地址,而服务器仍会按默认路径转发或直接丢弃。必须创建两条记录,分别放在两个不同的地方。

在客户端,地址属于主机,而 loopback 是一个不应出现在物理链路上的服务身份最自然的家。在集线器上,对端到前缀的映射是一份单独的声明,只有服务器会读它。两份记录谁也不蕴含谁。

在笔记里也要把两个网段分开。示例中的地址是文档用值,把它们和真实局域网重叠,是让一套能用的配置看起来坏掉的最快办法。

在客户端上持久地添加地址

把服务地址放在 loopback 上并带主机前缀,让主机把它当作本地输入,而不是要到达的子网。ip address add 10.66.0.10/32 dev lo 可以为一个地址做到这一点,对第二个地址重复该命令就模拟了两个服务。

先把它当作一次性测试运行,再移入负责主机网络配置的地方,因为运行时添加会在重启后消失,重连之间还会累积重复项。排在网络之后的 systemd 单元是一种选择,发行版的网络管理器是另一种。无论选哪种,地址都必须在应用绑定之前存在,否则服务会起在错误的套接字上。要在重启之后用 ip address show dev lo 验证,而不是在手动命令之后。

绑定是与寻址分开的决定。监听所有地址的应用会接受你从未打算使用的接口上的流量,所以把它指向具体的服务地址。这样本地输入策略才有精确的匹配对象。

在集线器真正读取的 CCD 文件里声明它

在服务器上,归属声明写进按已认证的通用名选择的每客户端文件,位于 client-config-dir 指定的目录中。那一行是 iroute 10.66.0.10 255.255.255.255,每个地址一行,它告诉服务器该由哪个对端接收发往该目标的流量。

两个细节会悄悄破坏它。文件名必须与服务器实际使用的名字一致,而启用 username-as-common-name 时这个名字会不同。并且 iroute 只有在配置了客户端配置目录的 TUN 模式下才生效,所以同一行在桥接部署里不起作用。要往已有文件里追加行,而不是替换它,因为它可能已经带有固定地址或 push 选项。OpenVPN 2.6 参考记录了目录和归属指令。

集线器自己的内核也需要通往这些目标的路由,本机产生的流量才能进入隧道进程。服务器配置里的 route 10.66.0.10 255.255.255.255 覆盖服务器自己发起的流量,而其他客户端需要 push "route 10.66.0.10 255.255.255.255" 才能自己学到该目标。

路由与授权始终分开。推送的路由只告诉客户端把包发往哪里,并不授予任何权限;服务仍须按自己的条件接受调用方。

检查输入策略和回程路径

发往该别名的包会作为本地输入到达拥有它的主机,所以问题在于本地防火墙是否允许该来源和端口。只放行来自 VPN 源网段的特定服务端口,而不是打开整个地址。

应答需要回到调用方的路径。对普通隧道池内的调用方来说这条路已经存在,因为隧道设备承载了它。位于另一个被路由网络之后的调用方还需要该网络的回程路由,非对称配置丢包就发生在这里。

留意同一端口上绑定到另一个地址的第二个监听者,因为内核会交给匹配的套接字,结果看起来像是随机的。用 ss -lntp 检查,并确认绑定的地址就是服务别名。这里不需要源 NAT,加上它反而会隐藏日志所依赖的调用方身份。

分别测试地址与归属

分两步测试。先确认拥有地址的主机能从自己访问别名上的服务,这一步把绑定和防火墙问题与路由分开。再从真实 VPN 调用方使用应用自身的认证来测试。当第一步通过而第二步失败时,缺的那一块在集线器一侧。

也要检查连接结束后仍然存在的故障模式:隧道断开时别名仍然保留,对端不在时集线器仍保留路由。任何单一对象都不能证明服务可达,所以两步测试胜过从笔记本上的一次 ping。

相关文章