有人报告 VPN “封了 UDP”,而这个说法掩盖了两个完全不同的判断。一个关于接入网能看到的外层传输,另一个关于应用数据包在隧道内被解密之后的表现。加密隧道可以在没有密钥的情况下从外部被过滤,而内层 UDP 应用也可能因为与过滤毫无关系的原因失败。收集证据之前,先确定你在验证哪一个判断。

两个首部,一个词

在采用 NAT-traversal 的 IPsec 部署中,外层数据包是 UDP,在两个 VPN 端点之间寻址,通常使用 4500 端口。在它内部,加密载荷承载隧道转发的一切,而这些东西本身可能是 TCP 或 UDP。因此 UDP 一词出现了两次,含义不同,可见性也不同。

中间网络只能看到外层首部,看不到内层的任何内容。它可以放行、整形或丢弃外层流,而且不需要解密任何东西。正是这种不对称让两个判断混淆在一起:同一个词既描述网络过滤的内容,也描述应用发送的内容。记录结论时把层次写在词旁边,混淆基本就会消失。

内层首部只有在接收端解密之后才可见。从那一刻起,数据包就像其他包一样被转发,受路由、策略和端点自身过滤的约束。这些是不同设备做出的不同决定。

网络仍然能看到什么

加密隐藏的是载荷和内层传输首部,而不是流本身存在这一事实。端点地址、端口、数据包大小和时序仍然可见,而这些足以对流量做启发式分类,或者封锁某个具体目的地。

sudo tcpdump -ni eth0 'udp port 4500'
sudo tcpdump -ni tun0 'udp port 5201'

第一次抓包显示物理接口上的外层流,第二次显示隧道接口上的内层流。两者同时运行可以直接回答问题:如果外层流存在而内层流不存在,说明应用根本没有往隧道里发送任何东西。如果两者都存在却没有任何回包,问题就在隧道之外,在目的地或者回程路径上。

RFC 3948 定义了外层 UDP 封装,而 ESP 规范定义了其中的受保护布局。两份文档都没有承诺接入网一定会放行外层流,所以在对内存流提出任何理论之前,值得先在外层四元组上做一次可达性测试。外层四元组被封锁,产生的是永远建立不起来的隧道,而不是建立之后丢掉某一个应用。

为什么一个 UDP 应用会在正常工作的隧道里失败

承载 Web 应用却不承载 UDP 应用的隧道有几种合理解释,而 ISP 对内层协议的过滤并不是第一种。最常见的是覆盖范围:两个应用没有同时进入同一条隧道,因为缺少通往 UDP 目的地的路由,或者客户端的拆分隧道规则把它排除了。

第二种是端点。允许协议列表的 VPN 服务器、目的地的防火墙,或者从不在预期端口上监听的服 务,都会产生与网络封锁看起来完全一样的超时。

第三种是数据包大小。UDP 应用发送的数据报往往比 Web 请求更大,而封装会降低可用 MTU,因此握手成功之后,第一个满尺寸载荷可能就没有下文。这种模式很容易被误读为封锁,因为小包过去了,大包却消失了。一次快速的 MTU 探测不到一分钟就能定性,答案通常是更小的载荷或修正过的 MSS,而不是网络过滤器。

ping -M do -s 1400 -c 2 10.60.0.20
iperf3 -u -c 10.60.0.20 -p 5201 -b 1M -t 5

第一条命令测试在关闭分片的情况下大包能否通过路径,这能迅速暴露 MTU 问题。第二条发送一个小的、有界的 UDP 流,并报告丢包和抖动。流开始之后停住,指向的是尺寸而不是封锁。

报告层次,而不是协议

用三种形式之一写下结论:外层投递失败、内层转发失败,或者应用失败。三者各有不同的负责人,混在一起就会把工单送给错误的团队。

改动本地端口并不能在对端创建出匹配的监听者,所以看起来有效的客户端绕行办法,通常恰恰证明原来的目的地是错的。同样,别人提供的备用配置只能证明可达性,证明不了应用配置是否正确。

也要把推断和观察分开。流量分析可以在不解密的情况下提示一条流是什么,这是有用的结论,但它和读取内层首部不是同一个判断。说明你手上究竟是哪一个。

相关文章