两台家用路由器,没有转发任何端口,而 VPN 有时仍然能连上。UDP 允许这样做,因为 防火墙会放行一个能与本机已发起的流相匹配的数据包。这是一个有条件的技巧而非功能,一旦你知道防火墙在比较什么,条件就很容易说清。它们也是相同配置在不同网络上表现不同的原因。

防火墙所做的比较

每个出站数据报都会生成一条短生命周期表项:源地址、源端口、目的地址、目的端口。任何到达并与之匹配的内容都会被当作应答放行。入站规则并不参与其中。

所以问题不是防火墙是否允许对端进来,而是对端的包看起来是否像表项在等待的应答。改动这个四元组里的一个字段,包就成了陌生人。因此只改端口号也可能让原本可用的配置失效,因此同一个端口在一台路由器上可用、在下一台上却失败。

固定本地源端口,同一个号码就会出现在通信的两端。这就是全部机制,也解释了为什么这个技巧是脆弱而不是神秘。

在两端各固定一个端口

OpenVPN 的默认 UDP 端口是 1194,而 lport 用于设置它发送时所使用的源端口。把两端都固定下来,各自出站的元组就会指向对方正在监听的端口:

# peer A
lport 1194
remote 198.51.100.20 1194
# peer B
lport 1194
remote 203.0.113.10 1194

只有在中间没有路由器改写源端口时,这才成立。当抓包里出现数据包却没有建立隧道时,改写就是第一个嫌疑对象。

元组开始对不上的地方

按目的地址映射的 NAT 会给每个对端分配不同的外部端口。告诉对端使用的地址并不是实际到达它的地址,于是入站包匹配不到任何表项,被静默丢弃。

运营商级转换、多层 NAT 和端口随机化都以同样的方式结束。任何只改写端口而保留地址的中间设备也是如此。

流式传输的处境更差。TCP 握手是逐步跟踪的,因此基于 TCP 的 VPN 根本无法用这种方式打洞,只能在路径上某处放一个监听端。

通常先失败的是其中一侧

实际上两个网络很少表现一致。一台路由器可能给出可预测的映射,另一台却按目的地址分配端口,结果就是一套在两个家庭之间能用、从移动网络却失败的配置。互相矛盾的报告通常描述的是两组不同的路由器。两位作者都没错,两个结论也都不具备普遍性。

在改动配置之前,先弄清是哪一侧丢弃了数据包。两端抓包一次就能定案:离开 A 却始终到不了 B 的数据报说明流在途中被改写;而到达 B 却没有应答返回的数据报说明防火墙没有把它们当作关联流量接受。

两个都拥有公网 IPv6 地址的对端可以绕开整个问题。没有转换就没有需要匹配的东西,因此一条指明对端的防火墙规则就够了,设计也不再依赖路由器的行为。

什么时候该停止尝试

拥有公网地址的主机是可靠的答案。它可以为两个对端终结隧道,也可以只是帮它们发现彼此当前的外部地址,两种安排无论 NAT 怎么做都能工作。如果策略允许,受管理的叠加网络能提供同样的协调。

这些方案都不需要关闭防火墙。为了赢得直连而关闭防护,等于丢弃了直连之所以有吸引力的理由,并让机器在整个测试期间保持敞开。

先尝试直连方案,然后记录你看到的现象。NAT 的行为和展示它的抓包,比最终使用的那对端口更有价值,因为下一个网络会不一样。OpenVPN 2.6 手册记录了 lport以及它所依赖的 remote 选项。

相关文章