3. 数据包处理基础:以太网帧结构、IP头、TCP/UDP头解析,校验和计算
做硬件加速,说白了就是跟数据包打交道。你得先搞清楚数据包长什么样,才能在硬件里精准地把它拆开、分析、再组装回去。我刚开始接触硬件包处理时,总觉得软件里看包挺简单的,但一转到硬件视角,很多细节就变得特别关键。
这一节,咱们就扎进数据包的底层。我会结合自己踩过的坑,把以太网帧、IP头、TCP/UDP头这些结构掰开揉碎了讲。嗯,校验和计算这块尤其重要,硬件里算不对,整个包就废了。
3.1 以太网帧结构:最底层的“信封”
数据包在网络上传输,第一层包装就是以太网帧。你可以把它想象成一个信封,上面写着发件人和收件人的地址。
标准以太网帧(DIX v2)的结构是这样的:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 前导码(Preamble) | 7 | 同步时钟用,硬件接收时直接丢弃 |
| 帧起始定界符(SFD) | 1 | 标志帧的开始,固定值 0xD5 |
| 目的MAC地址 | 6 | 接收方网卡地址 |
| 源MAC地址 | 6 | 发送方网卡地址 |
| 类型/长度(EtherType) | 2 | 0x0800 表示IPv4,0x0806 表示ARP |
| 载荷(Payload) | 46-1500 | 上层数据,最小46字节,最大1500字节 |
| 帧校验序列(FCS) | 4 | CRC32校验,硬件自动计算 |
这里有个关键点:前导码和SFD在硬件接收时就被剥离了,你真正要处理的是从目的MAC开始的14字节头部。我在做FPGA网卡项目时,就曾因为没搞清楚这个偏移量,导致解析出来的MAC地址全是错的。你想想看,地址都错了,后面的处理还有什么意义?
硬件加速要点:
- MAC地址比较:用硬件比较器并行比对,一个时钟周期就能出结果
- EtherType提取:固定偏移12字节处,硬件直接截取2字节即可
- FCS校验:交给硬件CRC模块,别用软件算,太慢了
3.2 IP头解析:网络层的“导航图”
以太网帧拆开后,如果EtherType是0x0800,接下来就是IP头。IPv4头部标准长度是20字节,但可以带选项,最长60字节。我个人习惯在硬件里只处理20字节的标准头,遇到带选项的直接丢给软件——省心。
IPv4头部结构:
| 字段 | 长度(位) | 说明 |
|---|---|---|
| 版本(Version) | 4 | IPv4固定为4 |
| 首部长度(IHL) | 4 | 以4字节为单位,最小5,最大15 |
| 服务类型(ToS) | 8 | 现在常用DSCP字段 |
| 总长度(Total Length) | 16 | 整个IP包的长度,包括头部 |
| 标识(Identification) | 16 | 分片时用 |
| 标志(Flags) | 3 | DF位、MF位 |
| 片偏移(Fragment Offset) | 13 | 分片偏移量 |
| 生存时间(TTL) | 8 | 每经过一个路由器减1 |
| 协议(Protocol) | 8 | 6=TCP,17=UDP |
| 首部校验和(Header Checksum) | 16 | 只校验IP头部 |
| 源IP地址 | 32 | 发送方IP |
| 目的IP地址 | 32 | 接收方IP |
我的经验:硬件解析IP头时,重点关注三个字段:IHL(确定头部长度)、总长度(确定包边界)、协议(决定下一步解析TCP还是UDP)。其他字段像TTL、标识这些,除非你做路由或分片重组,否则可以先放一放。
3.3 TCP/UDP头解析:传输层的“门牌号”
IP头解析完,根据Protocol字段就知道上层是什么协议了。6是TCP,17是UDP。这两个结构差别挺大,咱们分开看。
3.3.1 UDP头:简单粗暴
UDP头只有8字节,结构非常简单:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 源端口 | 2 | 发送方端口 |
| 目的端口 | 2 | 接收方端口 |
| 长度 | 2 | UDP头+数据的总长度 |
| 校验和 | 2 | 可选,为0表示不校验 |
UDP在硬件里特别好处理。固定8字节,没有状态,没有序号。我在做视频流加速时,UDP包几乎都是直通处理的——解析出端口号,查表转发,完事。
3.3.2 TCP头:复杂但可靠
TCP头就复杂多了。标准头20字节,带选项可以到60字节。硬件里处理TCP,核心是提取五元组(源IP、目的IP、源端口、目的端口、协议)和序列号。
TCP头部关键字段:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 源端口 | 2 | 发送方端口 |
| 目的端口 | 2 | 接收方端口 |
| 序列号(SEQ) | 4 | 数据流中的位置 |
| 确认号(ACK) | 4 | 期望收到的下一个序列号 |
| 数据偏移 | 4位 | TCP头长度,以4字节为单位 |
| 标志位 | 6位 | SYN、ACK、FIN、RST等 |
| 窗口大小 | 2 | 接收窗口 |
| 校验和 | 2 | 覆盖TCP头+数据+伪头部 |
注意:TCP校验和计算时,需要包含一个伪头部(Pseudo Header)。伪头部包含源IP、目的IP、协议号、TCP长度。这意味着硬件计算TCP校验和时,必须等IP头解析完才能开始。我曾经在流水线设计时忽略了这一点,导致校验和总是算不对——后来才发现是伪头部里的IP地址还没准备好。
3.4 校验和计算:硬件加速的核心战场
校验和计算是硬件加速最能发挥优势的地方。软件里算一个包的校验和可能要几十个指令周期,硬件里一个时钟周期就能搞定。
3.4.1 IP首部校验和
IP首部校验和的计算方法很简单:
- 将校验和字段置为0
- 以16位为单位对IP头部进行累加
- 如果累加结果超过16位,将高16位加到低16位上(循环进位)
- 取反,得到校验和
硬件实现时,可以用一个16位加法树并行计算。我做过一个设计,20字节的IP头,用5个加法器级联,一个时钟周期就出结果。
// 硬件风格的伪代码
// 假设 ip_header[0:9] 是10个16位字
sum = 0;
for (i = 0; i < 10; i++) {
sum = sum + ip_header[i];
// 硬件里这是并行加法树
}
// 处理进位
sum = (sum & 0xFFFF) + (sum >> 16);
checksum = ~sum & 0xFFFF;
3.4.2 TCP/UDP校验和
TCP/UDP校验和的计算范围更大,包括:
- 伪头部(12字节)
- TCP/UDP头部
- 数据载荷
伪头部的结构:
| 字段 | 长度(字节) |
|---|---|
| 源IP地址 | 4 |
| 目的IP地址 | 4 |
| 保留(全0) | 1 |
| 协议号 | 1 |
| TCP/UDP长度 | 2 |
硬件加速技巧:
- 使用增量更新:如果只修改了TTL或端口号,不需要重新计算整个校验和。可以用公式:new_checksum = ~(~old_checksum + ~old_value + new_value)
- 对于UDP,如果校验和为0,硬件可以直接跳过计算
- TCP校验和必须计算,不能跳过
3.5 知识体系总览
说了这么多,我画了一张图帮你把整个数据包解析的流程串起来。从网线进来的比特流,到最终提取出应用层数据,每一步都有硬件可以加速的点。
这张图把整个流程分成了四层。每一层都有硬件可以加速的关键点。你想想看,从最底层的比特流对齐,到MAC地址比对,再到IP校验和计算,最后到TCP/UDP的五元组提取——每一步如果用硬件流水线来做,延迟可以控制在几十纳秒以内。
我的建议:刚开始做硬件包处理时,别想着一步到位。先把以太网帧解析做稳定,再往上加IP层,最后才是TCP/UDP。我曾经在一个项目里同时上了所有协议层,结果调试时根本分不清问题是出在MAC层还是IP层。后来学乖了,一层一层来,每层验证通过再往下走。
好了,数据包的基础结构就讲到这里。这些内容虽然基础,但却是硬件加速的基石。下一节我们会把这些知识用起来,看看怎么在FPGA里搭一个真正的数据包处理流水线。
公众号:蓝海资料掘金营,微信deep3321