今天在QQ里有一个人问到ip检验和的问题,说自己抓包得到数据包,拿掉14字节的以太网头,取出20字节的IP头对其进行检验和(IP的检验和只是检验其头部,不包含数据),结果不对,问为什么?经过分析,我初步说是因为网络顺序和主机顺序的问题。最后自己测试发现不是。
1.首先理解ip检验和的算法
在tcp/ip协议卷一中说明:数据局链路层的检验算法是crc,在IP,tcp,udp层用的是相同的算法。(计算一份IP数据报的校验和,首先将检验和字段设置为0,对首部中每个16位进行二制反码求和,结果存储在检验和字段。收到一份数据报同意)。
接受到数据后验证时也可以将检验和位设置为0
0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00
0x0010: 00 1c 74 68 00 00 80 11 59 8f c0 a8 64 01 ab 46
0x0020: 9c e9 0f 3a 04 05 00 08 7f c5 00 00 00 00 00 00
0x0030: 00 00 00 00 00 00 00 00 00 00 00 00
在上面的16进制采样中,起始为Ethernet帧(DLC包)的开头。IPv4分组头从地址偏移量0x000e开始,第一个字节为0x45,最后一个字节为0xe9,即IPv4分组头到目标IP地址为止。根据以上的算法描述,我们可以作如下计算:
(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 + 0x0000(累加和位置先置0) + 0xc0a8 + 0x6401 + 0xab46 + 0x9ce9 = 0x3a66d
(2) 0xa66d + 0x3 = 0xa670 //进位
(3) 0xffff - 0xa670 = 0x598f //检验和的值
计算出结果
2.实现,网上有,这里就直接拿来
//unsigned short *buffer ip头数据
//int size ip头数据的长度20字节
//返回值为计算的检验和
unsigned short checksum(unsigned short *buffer, int size)
{
unsigned long cksum=0;
while (size > 1) //求和 0x4500 +0x05d4+.........+0x00+0x65
{
cksum += *buffer++;
size -= sizeof(unsigned short);
}
if (size)
{
cksum += *(unsigned short *)buffer;
}
//进位
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
//求反
return (unsigned short )(~cksum);
}
这里要注意的是自己抓包得到的数据类型的定义,如果直接定义为unsigned short就有问题了,因为其大小为2字节。这样的后果是有一半的数据未叠加。intel系列的底层实现一般是小序列,也就是常说的主机序。因此这里看有将得到的数据存储为unsigned char,在传入的时候转换为unsigned short,这样不仅有利于算法的实现(对首部中每个16位进行二制反码求和),也可有实现网络序列和主机序列的转换。当将unsigned char 转换为unsigned short的时候,最终将以小序列存储。
如:unsigned char a[]={0x45,0x00},经过unsigned short *p = (unsigned short *)a;*p的值为0x0045.
char a[] = { 0x45 ,0x00,0x00,0x00 };
int *b = (int *)a; *b的值为0x00000045.
注意:抓包得到的是网络序列,转换后存储为主机序列,要正确的计算检验和,必须先将网络序列转换为主机序列,这样计算时才用的是网络序列在计算。才来要得到正确的结果。
在接收方进行检验和验证的时候,检验字段已经不为0了,这个时候如果ip包在传输过程中没有发生任何错误,则每个16位的二进制反码求和的结果应该是1.
例子:
抓包的到的IP头数据 unsigned char ipDataNet[]={0x45,0x00,0x05,0xd4,0xd5,0xea,0x40,0x00,0x39,0x06,0x3b,0x0e,0x77,0x2a,0xf2,0xf3,0xc0,0ca8,0x00,0x65};
转换为主机序列 unsigned char ipDataHost[] ={0x00,0x45,0xd4,0x05,0xea,0xd5,0x00,0x40,0x06,0x39,0x0e,0x3b,0x2a,0x77,0xf3,0xf2,0xa8,0xc0,0x65,0x00};
cksum((unsigned short *)ipDataHost , 20);
结果为1
这样最终计算的结果就是检验和。
3.这个就可以用到QQ协议的解析中,如:qq的版本号为 0x15 ox00
unsigned char QQVer[] = { 0x15 , 0x00};
unsigned short *pQqver = (unsigned short *)QQVer;
这样*pQqver的为位ox0015。