OpenVPN 的 local 与隧道地址:两个不同的套接字
与服务器通信的地址并非隧道内部的地址。把 local、remote 与 nobind 当作不同角色来读,能解释大多数 bind 报错。
客户端拒绝启动,抱怨无法绑定本地地址,而显而易见的修法看上去是把它指向所期望收到的隧道地址。这行不通,原因值得弄明白:隧道建立之前并不存在内部地址,而联系服务器的套接字必须先存在。这里涉及两个不同的地址,只有其中一个能在本地选择。
bind 报错说的是本地地址
当 OpenVPN 报告无法绑定本地地址时,它说的是传输套接字:承载到对端加密连接的 UDP 或 TCP 端点。
显式的 local 指令要求守护进程把该套接字绑定到某个具体的本地地址。该地址必须存在于进程所在的网络命名空间,并且在客户端启动的那一刻可以分配。
在路由器或云边缘做地址转换的公网地址,不是守护进程能绑定的本地地址,哪怕服务器看到的正是它。这一区别解释了为什么同一份配置在家里能用、在云主机上却失败。它也解释了把公网地址加到回环接口上为何无济于事:那样传输的源地址会是网络无法回送的地址。改配置之前先读接口列表。
local 与 remote 到底选择什么
remote 指明要联系的对端。local 绑定同一套接字的近端。nobind 让操作系统同时挑选本地地址和端口,因此家用路由器后面的客户端通常不需要这两条指令中的任何一条。
这两条指令都与隧道内部的地址无关。
在容器或网络命名空间里,同一条规则适用,只是边界更窄:宿主机的地址在里面并不存在,所以从宿主机配置里抄来的 local 一行会失败,原因与地址写错完全不同。
ip -brief address show
ip route get 203.0.113.10内部地址来自服务器
隧道内的地址在控制通道建立之后协商或分配。使用动态地址池时由服务器在连接时决定,使用针对单个客户端的配置时则可能按身份固定。
把 local 写成内部地址,并不会要求服务器分配该地址。最好的情况是绑定失败;最坏的情况是客户端找到一个值相同的无关主机地址,并把它用于传输。
要读的接口也不一样。ifconfig 参数与地址池配置描述内部,而 remote、local、proto 和 nobind 描述外部。把两份清单分开,几乎能解释论坛上所有令人困惑的例子。正因如此,OpenVPN 2.6 手册 把它们分节记录。
陷阱:完整隧道会把它自己的传输也捕获进去
隧道建立并承载默认路由之后,维持它的数据包不能被路由进隧道。这是路由问题而不是地址问题,守护进程的处理办法是经由原来的网关,为服务器自身地址装一条更具体的路由。
如果你把传输固定到某个本地地址,之后又改变了该地址所走的路由,外层连接可能迷路,而内层配置仍然完好。隧道会一直报告已连接,因为内部没有问题。坏的只是传输,所以日志读起来自相矛盾。
此时的症状像是隧道连上后卡死,或者在没有有用报错的情况下反复重连。在检查隧道内部之前,先检查通往对端地址的路由,并确认你绑定的本地地址仍属于预期的接口。
避免混淆的习惯
读配置里的每一个地址,在修改之前先判断它属于哪一层。
如果它指定传输的远端或绑定其近端,那就是外部地址,必须在启动前存在。如果它描述隧道内部发生的事情,那就来自服务器或拓扑,任何本地指令都无法替代。
把这两份清单写进配置旁边的部署笔记。下一次排查就会从层次开始,而不是从猜测开始。