最近一段时间,对网络又开始追根溯源,最好的办法就是打开开源协议栈看一个究竟,不求写一个完整的ip协议栈,但求通达解惑!
众所周知,IP头定义如下:
struct IPHeader
{
unsigned char ver_hlen;
unsigned char tos;
unsigned short len;
unsigned short id;
unsigned short offset;
unsigned char ttl;
unsigned char type;
unsigned short cksum_header;
unsigned long ipsrc;
unsigned long ipdest;
/*
后面可能存在option数据
*/
};
IP头中的大多字段都好理解,只要一本TCP/IP入门的书就可以明明白白了,对cksum字段理解,如果只是看书,到头来很可能还不清楚怎么算它!
关于cksum_header的描述在《tcp/ip卷一》中是这样描述的:
“
首部检验和字段是根据I P 首部计算的检验和码。它不对首部后面的数据进行计算。I C M P 、I G M P 、U D P 和T C P 在它们各自的首部中均含有同时覆盖首部和数据检验和码。
为了计算一份数据报的I P 检验和,首先把检验和字段置为0 。然后,对首部中每个16 bit进行二进制反码求和(整个首部看成是由一串16 bit 的字组成),结果存在检验和字段中。当收到一份I P 数据报后,同样对首部中每个16 bit 进行二进制反码的求和。由于接收方在计算过,程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全1 。如果结果不是全1 (即检验和错误),那么I P 就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。
”
不知道有多少能人看完此描述后,写出算法或函数来!
正确的函数如下:
unsigned short CheckSum(unsigned short *szBUF,int iSize)
{
unsigned long ckSum=0;
for(;iSize>1;iSize-=sizeof(unsigned short))
ckSum+=*szBUF++;
if(iSize==1)
ckSum+=*(unsigned char *)szBUF;
ckSum=(ckSum>>16)+(ckSum&0xffff);
ckSum+=(ckSum>>16);
return(unsigned short )(~ckSum);
}
让我们假设一个IP头数据,来解cksum的惑!
IP头数据:
01000101 /*ver_hlen; */
00000000 /*tos*/
00000000 00000010/*len*/
00000000 00000000/*id*/
00000000 00000000/*offset*/
00000001 /*ttl*/
00010001 /*type*/
00000000 00000000/*cksum(0)*/
01111111 00000000 00000000 0000001/*sip*/
01111111 00000000 00000000 0000001/*dip*/
运算过程(注意是大端格式加):
01000101 00000000
01000101 00000000
00000000 00000010
01000101 00000010
00000000 00000000
01000101 00000010
00000000 00000000
01000101 00000010
00000100 00010001
01001001 00010011
00000000 00000000
01001001 00010011
01111111 00000000
11001000 00010011
00000000 00000001
11001000 00010100
01111111 00000000
101000111 00010100
00000000 00000001
和: 101000111 00010101
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
00000000 00000001 1
01000111 00010101
和: 01000111 00010110
ckSum+=(ckSum>>16)后:
01000111 00010110
00000000 00000000
和: 01000111 00010110
~: 10111000 11101000(效检和)
运算过程(注意用小端格式加):
00000000 01000101
00000000 01000101
00000010 00000000
00000010 01000101
00000000 00000000
00000010 01000101
00000000 00000000
00000010 01000101
00010001 00000100
00010011 01001001
00000000 00000000
00010011 01001001
00000000 01111111
00010011 11001000
00000001 00000000
00010100 11001000
00000000 01111111
00010101 01000111
00000001 00000000
和: 00010110 01000111
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
00000000 00000000 1
00010110 01000111
和: 00010110 01000111
ckSum+=(ckSum>>16)后:
00010110 01000111
00000000 00000000
和: 00010110 01000111
~: 11101001 10111000(效检和)
checksum2:11101000 10111000(小端)
checksum1:10111000 11101000(大端)
算法一样,说白了就是循环加,加到没有进位为止,然后在取反!
在现在《TCP/IP卷二》中的cksum实现有以下语句:
while(sum>>16)
sum=(sum&0xffff)+(sum>>16);
通过它更能说明就是在作循环加操作,现在又有一个疑问:
ckSum=(ckSum>>16)+(ckSum&0xffff);
ckSum+=(ckSum>>16);
和
while(sum>>16)
sum=(sum&0xffff)+(sum>>16);
等价吗?
等价,在一定条件下等价,大家都知道ip理论上最长是0xffff
那么
checksum最大不会超过:0xffff0000
这样>>16后为0xffff
0xffff+checksum最大0x1fffe,
0x1fffe >>16+0x1fffe=0xffff
注意了没有了进位
所以得到等价的结论!
以前每读到cksum注解时,书上只是草草曰16位反码和之云云,没有强调进位也要参加运算一点征兆,直到最近写了不少程序才看清楚这个细节!
源码之下必解惑!