前面已经完成了协议的解析分类,但是不能对分类好的数据帧再进行相关的校验计算,白白浪费时间,为降低延时,所以需要在分类的同时完成校验计算。
校验,就是通过增加冗余位来验证数据完整性的一个操作,对于谁是真正的“国际巨星”,我们不能章口就莱,还得上来试试。
在以太网帧中有两类校验,一个是checksum(校验和),一个是利用CRC算法的FCS(帧校验序列),所以本文针对这两种校验进行说明。
Checksum计算
Checksum,校验和,从名字可以知道就是通过累加数据的方式完成的校验计算,以下所举例的checksum计算是以太网帧数据报文中使用的方法。
- 那该怎么计算呢?
举个栗子,我有一组数据:{8'hf0, 8'hf1, 8'hf2, 8'hf3, 8'hf4, 8'hf5, 8'hf6, 8'hf7, 8'hf8, 8'hf9, 8'hfa}等11字节的数据,我该怎么计算其checksum呢?
- 将数据组成16bit的一组数据,即{16'hf0f1, 16'hf2f3, 16'hf4f5, 16'hf6f7, 16'hf8f9, 16'hfa00},其中若为奇数个字节,则最后一个字节置于16bit的高八位上;
- 将这新组成的数据进行累加计算,即16'hf0f1+16'hf2f3+16'hf4f5+16'hf6f7+16'hf8f9+16'hfa00 = 32'h5c2c9;
- 若2中计算的结果高位溢出了,则将高位再累加到低位上(循环累加),即 16'h0005 + 16'hc2c9 = 16'hc2ce,如果还有溢出则再次将高位累加到低位上;
- 对3中的计算结果按位取反,即~16'hc2ce = 16'h3d31,得到的16'h3d31即checksum的值;
- 怎么验证checksum是对的呢?
比如同样告知上述的一组数据,然后告知checksum为16'h3d31,那验证的办法可以是:
- 重新计算这组数据的checksum,然后与已知的比较,看是否一致;
- 在进行16bit数据累加的计算中,把16'h3d31也累加上,即32'h5c2c9 + 16'h3d31 = 32'h5fffa,然后将溢出的高16位加到低16位上,即16'h0005 + 16'hfffa = 16'hffff,如果为16位全1,则说明校验正确,否则则校验失败;
- 那协议栈需要计算哪些Checksum呢?
目前设计的支持ARP/ICMP/UDP三个协议,那么直接从其报文格式中可以发现,ARP中没有Checksum字段,而在ICMP中含有IP首部的Checksum字段和ICMP首部的Checksum字段,同样UDP中也含有IP首部Checksum和UDP首部Checksum,所有存在三个Checksum的计算,接下来就依次说明其计算过程。
- IP首部checksum
IP首部的组成如下:
其首部的Checksum需计算首部20字节(如果有option则更长一点,本文暂时不考虑),即将上图中各字段按照16bit划分(Checksum先填16'h0),再按照之前说的进行计算。
笔者在图中填充上wireshark任意抓获了一帧数据的IP首部部分,可以计算验证,即:
- 16'4500 + 16'h0028 + 16'ha140 + 16'h4000 + 16'h6706 + 16'h0000 + 16'h0d6b + 16'h04fe + 16'hc0a8 + 16'hd07c = 32'h330fb
- 16'0003 + 16'h30fb = 16'h30fe
- ~16'h30fe = 16'hcf01 (计算正确!)
换成verilog则如下:
always
- ICMP首部checksum
对于ICMP的checksum不再举例说明计算过程了,只说明参与计算的数据构成。
ICMP的Checksum计算如上图所示,包括首部和选项数据部分,同样计算时将Checksum字段置零进行计算,即16'h0000 + 16'h0000 + 16'h0001 + 16'h0013 + 16'h6162 + ....... + 16'h6869,同样再进行循环累加和按位取反操作。
(实现RTL可参考笔者之后上传的)
- UDP首部checksum
而UDP首部中的Checksum字段计算与前面有略微区别,它与TCP首部的Checksum计算一样,需要将伪首部(pseudo header)加到计算当中,其中UDP的伪首部组成如下图所示:
其伪首部包括:源IP(32),目的IP(32),预留(8),协议(8),UDP长度(8);
同时再将UDP首部的端口号和UDP长度字段加入计算,需要注意的是伪首部中的UDP长度和UDP首部中的长度一致,所以上图的累加应该是:16'hc0a8 + 16'h0001 + 16'hc0a8 + 16'h000a + ....... + 16'h9900,(字节数为奇数,注意最后一个16bit的低八位需填0)。
(实现RTL可参考笔者之后上传的)
FCS计算
FCS,frame check sequence,帧校验序列,位于以太网帧的尾部,如下图所示,用于校验以太网数据帧是否出错。
前面说过,FCS是采用CRC32算法得到的4字节长度的校验序列,其中CRC算法入门可以参考文章:
循环冗余校验(CRC)算法入门引导_Ivan 的专栏-CSDN博客_crc校验blog.csdn.net当然,也可以直接参考Xilinx的官方手册xapp209[1],同时该文档也提供了FCS的实现结构,如下图所示,其中crc_reg[31:0]即每次计算得到结果,而crc[7:0]即计算结束后输出4字节FCS。
其中使用的CRC32的硬件实现算法可详见论文[2],具体的硬件实现代码如下图所示。
上述实现结构翻译成verilog就是:
always
- 顺手再推荐一个网站
可以直接生成CRC的verilog或者VHDL源码:
CRC Generation Toolwww.easics.com得到上图的所示的源码后,可以发现其为一个function,可以对模块添加输入输出,并调用该function即可进行CRC计算。
网页的工具还可以任意勾选多项式并生成源码,方便使用。
不过,有时候网页工具打不开,不知道为啥。。。
以上即协议栈设计中的校验部分,为确保接收的数据帧的正确,笔者在接收协议分类中即进行了校验计算,当前帧校验无误后再向后级模块传输处理,此外,对于需发送的数据帧在进行封包的过程中完成校验计算,使可进行发送。
若有不足之处还望批评指正!
十二点过九分:UDP/IP硬件协议栈设计(四):ARPzhuanlan.zhihu.com
参考
- ^Xilinx手册xapp209 https://www.xilinx.com/support/documentation/application_notes/xapp209.pdf
- ^Rajesh Nair, Gerry Ryan and Farivar Farzaneh, A Symbol Based Algorithm for Hardware Implementation of Cyclic Redundancy Check (CRC), Bay Networks. https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=623934