OpenVPN 的 UDP 回复为何从错误的地址发出
带多个本地地址的 UDP 服务器可能用与客户端所连地址不同的地址回复。multihome 可在受支持的平台上固定来源地址。
一个 OpenVPN 客户端拨通一个地址,服务器却从另一个地址回复,隧道始终建立不起来。服务器日志显示数据包已经到达,抓包也显示回复确实发出,于是故障被归咎于客户端。在多网卡的主机上,或在同一网卡上配有多个地址时,双方对“当前使用的是哪个地址”根本没有共识。回复在 IP 层完全合法,但许多客户端会丢弃源地址与自己拨打的地址不一致的数据包。
客户端在来源地址改变时看到什么
未绑定到具体本地地址的 UDP 套接字没有固定来源,内核在发送时才挑选一个。这个选择来自通往客户端地址的路由查找,来源就成了查找所选网卡的地址。在单网卡机器上这个选择不可见;而在同时拥有管理地址、公网地址和同一网卡上辅助地址的主机上,它取决于路由度量和策略。基于策略的路由只会让错配更常见而不是更少见,因为规则可能选中客户端从未到达的上行链路。一条从某家运营商学到的默认路由,加上一条指向另一家的来源规则,就足以造成这种局面。
会过滤回复的客户端,以及要求四元组与来源完全一致的连接跟踪,都会在没有任何有用提示的情况下丢弃该回复。随后隧道像什么都没收到一样超时,这正是它被误判为客户端侧防火墙问题的原因。只在服务器上抓包只能印证这个错误,而不能解释它,因为回复确实发出去了。在改动其他任何东西之前,先把回复的来源与客户端拨打的地址对比一下。
multihome 选项究竟做了什么
该指令在配置文件中写作 multihome,在命令行上写作 --multihome,它让 UDP 服务器从客户端数据包所到达的地址发出回复。内核在接收时把原始目的地址作为辅助数据交出,OpenVPN 就用这个值作为回复的来源。于是,监听通配地址(IPv4 下是 local 0.0.0.0,IPv6 下是等价写法)的服务器,表现得就像每个客户端都到达了一个专属监听器。从公网地址进来的客户端得到来自公网地址的回复,来自内部地址的客户端则得到该地址的回复。数据包路径上其他任何东西都不动:路由、过滤规则和隧道配置都保持原样。
正是最后这一点,使得该选项可以安全地加到一台本来就工作正常的服务器上;同样也是它无法修复只放行单一地址的过滤规则的原因。数据包仍必须在它所走的路径上被接受,因此这项来源修正改变的只是回复的出发点,别的什么都没有。它也只适用于 UDP:TCP 监听器不会有同样形式的问题,因为连接是靠四元组识别的,而不是靠回复的来源地址。
什么时候固定本地地址是错误的默认做法
在动用这个选项之前,先确认服务器是否绑定了某个具体的本地地址,因为绑定后的监听器总是从该地址回复。对单网卡部署来说这是对的,而一旦客户端合法地使用多个地址,它就错了。如果绑定是有意为之,就要决定服务器是否应当接受来自额外地址的流量。解除绑定会扩大暴露面,因此允许的网卡和来源网段必须由服务和过滤规则来落实,而不能只靠绑定地址。
OpenVPN 参考手册把该选项限定在 UDP 服务器上,说明了数据包路径上的额外处理,并记录了哪些平台支持它。在据此规划之前,请先在已安装的版本上确认。在不支持的地方,替代做法是绑定具体地址或修正回程路由,两者都是比第一眼看上去更窄的改动。监听通配地址还有回复之外的后果:监听所有地址的服务器,从主机接入的每一个网络都能访问到,而这可能并不是任何人想要的暴露面。
从外部证明回复的来源
从服务器之外测试,并在抓包之前记录客户端使用的确切地址和传输方式。下面三项检查回答三个不同的问题,顺序很重要。
tcpdump -ni any udp port 1194
ss -lunp 'sport = :1194'
ip route get 203.0.113.10 from 198.51.100.20
抓包显示回复上的源地址,整篇文章正是围绕这一点展开。套接字列表显示服务器实际绑定了什么,因为通配监听器和具体监听器在配置文件里看起来一模一样,在网络上的行为却不同。路由查找显示内核对这条流会选择的来源,它能在你动手改动之前解释抓包结果。IPv4 和 IPv6 要分别重复这三项,因为它们是各自独立的监听器和查找,针对一个地址族的通配条目并不能说明另一个地址族的情况。
路由、发现与过滤各自独立
保留回复的来源并不会安装出站路由、不会更新动态 DNS,也不会让一个地址族接受另一个。在多上行链路的服务器上,每个来源的回程路径必须有意识地达成一致,否则回复会从客户端过滤规则从未放行的网卡出去。同样,前缀变更后的新地址必须通过发现机制和受支持的重连流程送达客户端,而不是靠旧记录。
服务器身份校验和证书验证要保持原样,因为地址变了并不构成接受未验证对端的理由。如果回复现在已从预期的来源发出,而隧道仍然失败,就把来源问题视为已解决并继续往下查。下一个观察到的错误属于路由、过滤或认证,再改一次监听器也碰不到它。把回复的来源写进工单,紧挨着客户端拨打的地址,这样下一个人用一条命令就能核对同一个事实。