关于校验算法的学习(学习以太网时遇到的)以太网的包头还是数据都会有校验,在FPGA上实现IP核时,用的是CRC校验,然后学习ICMP协议时,也看到一种校验算法,以此学习分析一下背后原理。
一、先看看ICMP的包格式,主要是写ping命令是用到(会有一篇专门将ping的c语言实现),所以只是展示与ping命令背后的ICMP规则。
(来自维基)
对于校验的研究暂时不需要理解其他数据的意思,维基对checksum的解释为
Checksum
Error checking data, calculated from the ICMP header and data, with value 0 substituted for this field. The Internet Checksum is used, specified in RFC 1071.
首先校验的是header+data,然后如果忽略补0,规则看RFC 1071,来看看这个文档。
二、在看这个文档之前先了解几个概念:2’s complement 和 1’s complement
其实这就是两种数值的表示方法,
先看2’s complement :这就是我们日常使用的,称为2补码系统。
看看维基大神的解释:
【This system is the most common method of representing signed integers on computers.[2] An N-bit two’s-complement numeral system can represent every integer in the range ?(2N???1) to +(2N???1 ? 1) 】
那怎么编码数据呢,看看:
For example, to find the 4-bit representation of ?5 (subscripts denote the base of the representation):
x = 510 therefore x = 01012
Hence, with N = 4:
x* = 2N ? x = 24 ? 510 = 100002 ? 01012 = 10112
The calculation can be done entirely in base 10, converting to base 2 at the end:
x* = 2N ? x = 24 ? 510 = 1110 = 10112
就是正数是其本身,如5为0101;负数最高位置一,然后将其他低位取反,再加一。
这就是常见的计算机编码方式。
这是编码图
再看看1’s complement
Positive numbers are the same simple, binary system used by two’s complement and sign-magnitude. Negative values are the bit complement of the corresponding positive value. The largest positive value is characterized by the sign (high-order) bit being off (0) and all other bits being on (1). The smallest negative value is characterized by the sign bit being 1, and all other bits being 0. The table below shows all possible values in a 4-bit system, from ?7 to +7.【wiki】
这是另外一套编码系统,正数也是最高位为0,其他位表示数值,最大就是其他位全为1。负数的表示直接对正数取反。所以这里会出现+0和-0的概念,如下编码图
三、RFC1071文档
直接看到
To generate a checksum, the checksum field itself is cleared,the 16-bit 1’s complement sum is computed over the octets concerned, and the 1’s complement of this sum is placed in thechecksum field.
首先这里的校验法是1补码系统的,不是计算机使用的2补码系统。
ICMP协议使用的校验算法IP、ICMP、UDP和TCP报文头部都有校验和字段,不过IP、TCP、UDP只针对首部计算校验和,而ICMP对首部和报文数据一起计算校验和。检验和算法可以分成两步来实现。首先在发送端,有以下三步:
1.把校验和字段置为0。
2.对需要校验的数据看成以16bit为单位的数字组成,依次进行二进制求和。
3.将上一步的求和结果取反,存入校验和字段。
其次在接收端,也有相应的三步:
1.对需要校验的数据看成以16bit为单位的数字组成,依次进行二进制求和,包括校验和字段。
2.将上一步的求和结果取反。
3.判断最终结果是否为0。如果为0,说明校验和正确。如果不为0,则协议栈会丢掉接收到的数据。
四、CRC
在FPGA上写以太网帧数据时,校验还用到CRC,是不是以太网每层使用的校验算法不一样呢?
先看看以太网帧的数据格式
看看维基大神对FCS的解释
Frame check sequence
The frame check sequence (FCS) is a four-octet cyclic redundancy check (CRC) that allows detection of corrupted data within the entire frame as received on the receiver side. The FCS value is computed as a function of the protected MAC frame fields: source and destination address, length/type field, MAC client data and padding (that is, all fields except the FCS).
Running the CRC algorithm over the received frame data including the CRC code will always result in a zero value for error-free received data, because the CRC is a remainder of the data divided by the polynomial. However, this technique can result in “false negative”, where a data with trailing zeroes will also result in the same zero remainder. To avoid this scenario, the FCS is complemented (reversed for each bit) by the sender before it is attached to the end of the payload data. This way, the algorithm result will always be a magic number or CRC32 residue of 0xC704DD7B when data has been received correctly. This allows for receiving a frame and validating the FCS without knowing where the FCS field actually starts.
可知,这里指定的校验算法就是CRC32,并且校验数据不包括FCS本身。所以来看看CRC32是个什么鬼。
维基的两篇关于crc的介绍
https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Introduction
https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks
国外的crc学习网站
http://www.repairfaq.org/filipg/LINK/F_crc_v31.html
CRC,称为冗余校验的基本思想,就是把任何信息都看作体积庞大的二进制数,为了用固定位来表示校验码(便于统一),就提出一个多项式除数的概念,如果一个冗余的多项式去除以一个固定的多项式,将余数作为校验码使用,就可以实现数据的校验。而除法的本质就是相减,而在二进制中为了减少除法的计算量,采用XOR来替代除法。
例如,要传输信息十进制219,将其转换为二进制11011011b,
规定用于校验的多项式为1010;发生XOR为
因为余数一定比除数至少少一位,所以取011作为校验码,这发送数据为2193.接收方同样用219除1010,如果余数相同,则表示数据正确。
五、为什么在网络传输中校验码需要,不同的算法
在ICMP校验中使用的是checksum的校验,在以太网帧最后使用的是CRC校验,比如在其他TCP包头校验码又是怎样的呢。
tcp/ip协议中规定了IP、ICMP、UDP和TCP报文头部都有校验和字段,并且都是基于checksum的算法。但是也有不一样,
【以下总结来源于网络】
(1)IP校验和:
IP数据报的校验和只检验IP数据报的首部。
(2)UDP校验和:
UDP数据报计算校验和的方法和IP数据报校验和的方法相似,但是UDP的校验和是将首部和数据部分一起都校验。
并且在计算UDP校验和之前需要封装一个伪首部,伪首部结构如下(具体结构定义见后面代码部分):
源IP地址 | 目的IP地址 | 全 0 | 协 议 | UDP长度
(3)TCP校验和:
TCP 的校验和计算方法同UDP一样,同样要加上一个伪头部,区别是伪头部的协议码是0x06,长度是整个TCP报文的长度(包含TCP头部)。
(4)ICMP校验和:
ICMP校验和的计算方法一样,只不过只是对ICMP包整个进行校验和,没有伪头部,也不包括IP包头部。
总结如下: 计算IP数据报和ICMP的校验和时不需要封装伪首部,他俩不同的是IP数据报只检验IP数据报的首部,而ICMP数据报校验包括ICMP头部和数据部分(整个ICMP长度),TCP和UDP的校验方法和IP、ICMP校验方法一样,但是TCP和UDP校验之前需要封装伪首部。还有一个需要注意的是校验的顺序是从上(层)到下(层),如校验ICMP时,先校验ICMP后校验IP(ICMP数据包结构=ether + IP + ICMP), 并且校验之前需要先将校验和的值初始化为0.
也就是说只有以太网使用CRC,其他包头或者数据比较少时采用checksum的算法。
暂时没想到更本质缘由,但一种解释就是,因为CRC本身就是对数据量大的校验,求和(和的容量只有16bit)对于数据量小的校验,
六、完成crc和checksum的实现
首先c实现
checksum就以ICPM为背景。看看ICMP的用于信息回显的数据格式:
Information Request or Information Reply Message
代码为
#include<stdio.h>
struct timeval
{
long int tv_sec;
long int tv_usec;
};
struct icmp{
unsigned char type;
unsigned char code;
unsigned short checksum;
unsigned short id;
unsigned short num;
struct timeval t;
};
unsigned short icmp_checksum(unsigned short* s,int len)
{
unsigned int sum=0;
unsigned short check;
int cnt=len>>1; //cnt is size of 16bit
while(cnt--)
{
sum+=*s++;
}
if((len&0x1)==0x1)
sum+=*(unsigned char*)s;
while((sum>>16)!=0)
sum=(sum&0xffff)+(sum>>16);
return ~check=sum&0xffff;
}
int main()
{
struct icmp i;
i.type=8;
i.code=0;
i.checksum=0;
i.id=10;
i.num=20;
i.t.tv_sec=100;
i.t.tv_usec=200;
i.checksum=icmp_checksum(&i,sizeof(i));
printf("checksum is %x\n",i.checksum);
return 0;
}