数清 IPsec 隧道里的一个 G.729 包
一个算过一遍的 G.729 例子说明载荷、RTP、ESP 与 Ethernet 成帧如何叠加,以及同一通电话为什么在每个边界上花不同的带宽。
IPsec 上的语音常被报成编解码器的比特率,而这个数字在除编解码器自身边界之外的每个边界上都是错的。音频是 8 kbit/s,承载它的包更大,ESP 外壳更大,Ethernet 帧又加上了隧道上抓包永远看不到的成帧。从给定格式出发数一个包,是弄清物理链路实际必须承载什么的唯一办法。下面的推演只做一个方向,这里的每个数字都是算术,不是测量。
从分组化选择开始,而不是从编解码器数字开始
编解码器表只给出载荷速率,别的什么也没有:G.729 输出 8 kbit/s,也就是每 30 ms 30 字节音频。它对首部、保护或成帧一字不提,而这三项决定结果。间隔是两端必须达成一致的政策选择,所以先定它并写明,因为后面每个数字都取决于这一个决定。
本示例使用 30 ms,所以包速率是每秒 33.3 个。它假设 12 字节的 RTP 首部、8 字节的 UDP 首部和 20 字节不带选项的内层 IPv4 首部,并且没有 SRTP、首部压缩或流量机密性填充;任何改动都会带着预算一起移动,所以这些假设要放在答案旁边。
先加内层首部,再加 ESP 外壳
应用发送载荷加 RTP 加 UDP 加 IP,在这种分组化下是 70 字节。这个内层流算出 18.7 kbit/s,已经是编解码器速率的两倍多,因为占据包大部分的是首部而不是音频。这些字节没有一个是声音,它们是编址和排序的代价。在隧道之上抓包正好显示这个数字,人们说这通电话很小时引用的就是它。
它也是永远到不了线上的那个数字。ESP 隧道模式加上 8 字节的 SPI 与序号、8 字节的显式 IV(用于 AES-GCM)、2 字节的尾部以及 16 字节的认证标签。70 字节载荷加尾部落在 72,已经满足四字节对齐,所以这里不需要额外填充。RFC 4106 规定了 GCM 的 IV 与标签长度,两者都属于算术而不是脚注:
30 payload, G.729 at 30 ms
12 RTP
8 UDP
20 inner IPv4
--
70 inner IP packet
8 ESP SPI and sequence
8 explicit IV
2 pad length and next header
16 authentication tag
--
104 ESP packet加上穿越 NAT 所必需的外层首部
ESP 外壳之外是 20 字节的外层 IPv4 首部和用于穿越 NAT 的 8 字节 UDP 首部,于是包变成 132 字节。UDP 封装之所以存在,是因为包必须穿过对端之间的转换设备,它改变外部形状而不触及受保护的载荷。在每秒 33.3 个包时,132 字节约为 35.2 kbit/s,大致是编解码器标称速率的四倍。
标签和 IV 都不能为了把估算变小而删掉,因为两者都是链路实际要承载的一部分。漏掉它们的估算描述的是一个接收端会拒绝的包。尾部同样如此,它的存在是为了标出载荷边界和受保护区域内的下一个首部。
引用数字之前先给边界命名
外层 IP 数字仍然不含 Ethernet 成帧。带标签或不带标签的接入链路加上 14 字节的 MAC 首部和 4 字节的帧校验序列,使帧成为 150 字节,这正是交换机端口所计的大小。在同样的包速率下那是 40 kbit/s,是编解码器速率的五倍,而这个差值不是浪费,而是承载的代价。
| Boundary | Packet size | Rate at 33.3 packets/s | What it counts |
|---|---|---|---|
| Codec payload | 30 bytes | 8.0 kbit/s | The audio alone |
| Inner IP packet | 70 bytes | 18.7 kbit/s | Application headers included |
| Outer IP packet | 132 bytes | 35.2 kbit/s | ESP and NAT-T included |
| Ethernet frame | 150 bytes | 40.0 kbit/s | Link framing included |
40 kbit/s 这个数字仍然不含前导码、帧间隙、VLAN 标签、运营商封装、RTCP、信令以及反向传输的音频。它是单方向估算,不是链路预留,把它当作预留来引用,会在一处高估链路必须预留的量,而在另一处低估。主机抓包也可能漏掉链路添加的成帧,所以 必须把边界与数字一起命名,也正因如此,边界表比一个标题数字更有用。对没有计数的那个方向要单独预留。
用接口核对算术
在相信估算之前,把它与接口对已知包数的报告做比较。在物理接口而不是隧道设备上抓包,这样你要核算的成帧才被包含在内,并且要预期出现小的偏差而不是完全吻合。
sudo tcpdump -ni eth0 -c 50 'udp port 4500'
ip -s link show eth0抓包确认外层包的大小,而接口计数器显示包含成帧的速率。如果两者相差大约 Ethernet 的余量,算术就是对的,只是边界叫错了名字。远大于一个帧首部的差值指向链路上的其他东西,例如重传或共享该接口的另一条流。把分组化间隔、保护套件和边界写在数字旁边,因为缺少这三项的估算会被下一个读者当成测量来引用。