2009年(41)
分类: C/C++
2009-04-20 11:43:20
相信很多人看了《TCP/IP详解 卷一:协议》3.2中关于IP检验和的计算方法后,就会去试一下,但是发现计算出来的值是错误的。这是为什么呢,其实是对书中所阐述的方法理解不到位的原因。书中的原话(中文):
“为了计算一份数据报的IP检验和,首先把检验和字段置为0。然后,对首部中每个16 bit进行二进制反码求和(整个首部看成是由一串 16 bit的字组成),结果存在检验和字段中。当收到一份I P数据报后,同样对首部中每个16 bit进行二进制反码的求和”
很多人在计算的时候,采用的方法往往是这样:
·csum类型为unsigned long,并置为0
·每次取首部16 bit(即一个unsigned short),与csum相加,直到取完(若首部长度为奇数则最后一次取的时候,高8位补零)
·csum取反
·返回csum的低16位(或将csum转化为unsgined short返回)
这样的做法是错误的,因为这样做可能会丢失了一些数据————csum的高16位。下面是网络上流行的算法:
|
这个算法是正确的,关键的地方就是最后2句!(注意,这里使用了一个技巧,取反求和等于求和取反)。最后两句是为了防止数据丢失,也就是将求和的结果cksum的高16位与低16位相加。至于为什么执行两次,而不是3次或更多,看下面的例子:
·cksum计算完后(最后2句之前),值为0x3ffff
·执行cksum = (cksum >> 16) + (cksum & 0xffff)得:0x10002
·依然超过16位,因此需要cksum += (cksum >>16),不会有第3次了
看这个算法之后,我对书中描述的新理解:
·对首部中每个16 bit进行二进制反码求和
·将每次计算的结果添加到剩余的IP首部二进制序列中(以16 bit为单位),这步很重要!
·重复1、2步,直到IP首部二进制序列剩下16bit
举例,假设一个IP首部为:
#初始状态
#第1轮 45 2e be 55 00 00 7a 11 00 00 de b7 7e e3 c0 a8 12 7a #第2轮 00 01 03 83<----此为452e + be55的结果 00 00 7a 11 00 00 de b7 7e e3 c0 a8 12 7a #第3轮 03 84 00 00 7a 11 00 00 de b7 7e e3 c0 a8 12 7a #第4轮 03 84 7a 11 00 00 de b7 7e e3 c0 a8 12 7a #第5轮 7d 95 00 00 de b7 7e e3 c0 a8 12 7a #第6轮 7d 95 de b7 7e e3 c0 a8 12 7a #第7轮 00 01 5c 4c 7e e3 c0 a8 12 7a #第8轮 5c 4d 7e e3 c0 a8 12 7a #第9轮 db 30 c0 a8 12 7a #第10轮 00 01 9b d8 12 7a #第11轮 9b d9 12 7a #第12轮 ae 53 #取反 51ac |
则这个IP包的校验和就是51ac。
当然,在编程实现的时候不必那么多步骤,先将结果存入一个32位的整形数,最后再将高16位与低16为相加(2次)即可。