“VPN 数据包恰好携带两个校验和”这一说法听起来很整齐,但错得足够频繁,值得拆开来看。数量取决于出现哪些 IP 版本、承载哪种传输协议,以及隧道采用哪种保护格式。首部校验和、上层校验和与密码学完整性标签是三种作用范围不同的机制。要数清楚,先标注每一个究竟覆盖什么。

确认链路上实际存在哪些 IP 版本

IPv4 首部带有 16 位首部校验和,IPv6 首部则没有任何类似字段。仅此一点就会改变每条隧道的数量,因为把 IPv4 包在 IPv6 里与把 IPv6 包在 IPv4 里会产生不同的报文。一条隧道可以同时承载两个协议族,此时一个方向可能含有另一个方向没有的字段。在统计任何数字之前,先确定版本。

把布局写成首部清单,而不是画成图。读抓包时顺序很重要,而校验和字段是否存在,是第一个要标注的事项。

outer IPv6 | ESP | inner IPv6 | UDP | application data
outer IPv4 | ESP | inner IPv4 | TCP | application data

第一行在其 IP 首部中任何位置都没有 IP 首部校验和,而第二行在两个首部都是 IPv4 时有两个。同一套隧道技术,数量不同,而且两行都不是完整的链路格式。

确认每个校验和覆盖什么

覆盖范围恰恰是常被跳过的一环,而它才让数字有意义。IPv4 首部校验和只保护首部本身,对后面的载荷不作任何说明。IPv6 之上的 UDP 校验和覆盖伪首部以及 UDP 首部和载荷,也就是说数据报被隧道封装时,它会深入被承载的报文内部。TCP 有自己的校验和,同样依赖伪首部。三者都叫校验和,但作用范围是三种。

RFC 791 规定了 IPv4 首部校验及其在每一跳的重算,RFC 8200 规定了 IPv6 之上包括伪首部在内的上层处理方式。两份文件都没有说 VPN 数据包含有固定数量的校验和,因为它们都没有描述隧道。

tshark -r capture.pcap -Y "ip.proto == 50" -V | grep -i checksum

单独确认完整性保护

ESP 完整性是一种密码学保护,有自己的覆盖范围,其表示形式取决于算法。它不是换了名字的 IP 或 UDP 校验和,长度随所选构造而变化。完整性检查覆盖报文中一段确定的部分,视模式不同可能包含 ESP 首部和部分载荷。把它当作已知宽度的校验和字段来计数,正是这类解释大多站不住脚的地方。

数字节之前先读清所选构造,并把结果描述为完整性保护,而不是换了标签的校验和。只要开口问,解码器就会告诉你字段名。

tshark -r capture.pcap -Y esp -V | grep -iE "integrity|icv"

计数之前先确认观测点

在内侧接口抓包与在链路上抓包可能显示完全不同的层次。在内侧接口上隧道已被剥掉,因此看到的是被承载的报文,看不到任何外层首部。在链路上看到的是外层首部和读不懂的加密载荷。两种视角都没有错,它们也不是同一对象的视角。引用任何数字之前,先说明抓包在哪里进行。

sudo tcpdump -ni eth0 -vv -c 5 esp
sudo tcpdump -ni tun0 -vv -c 5

第一条命令显示带完整性尾部的外层报文,第二条显示带自身传输校验和的被承载报文。把两次抓包的数值相加不会重复计数,但会把同一个报文解释两遍。

用真实抓包核对数量

完整解码一个报文,并标出解码器称为校验和或完整性值的每个字段。然后把每个标记与你写下的布局对照,删掉属于你并未真正计数的层次的那些。经得起这套流程的数量才站得住,凭记忆报出的数字则不行。

对回复方向再重复一遍,因为承载两个协议族的隧道可能按方向和按流不同。然后连同作用范围一起陈述数量,例如在这个观测点上这个内层报文有三个校验和,而不是把它当作 VPN 数据包的普遍属性。

tshark -r capture.pcap -T fields -e ip.checksum -e udp.checksum -e tcp.checksum

相关文章