v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
zf6ve5
zf6ve5
3
0
2006-02-25T13:04:00Z
2006-02-25T13:05:00Z
4
504
2879
电脑工作室
23
6
3377
11.5606
Clean
7.8 磅
0
2
false
false
false
MicrosoftInternetExplorer4
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-ansi-language:#0400;
mso-fareast-language:#0400;
mso-bidi-language:#0400;}
解IP 协议中的checksum
Author:zfive5(zhaozidong)
Email :zfive5@yahoo.com.cn
最近一段时间,对网络又开始追根溯源,最好的办法就是打开开源协议栈看一个究竟,不求写一个完整的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*/
0000000000000010/*len*/
0000000000000000/*id*/
0000000000000000/*offset*/
00000001/*ttl*/
00010001/*type*/
0000000000000000/*cksum(0)*/
0111111100000000000000000000001/*sip*/
0111111100000000000000000000001/*dip*/
运算过程(注意是大端格式加):
0100010100000000
0100010100000000
0000000000000010
0100010100000010
0000000000000000
0100010100000010
0000000000000000
0100010100000010
0000010000010001
0100100100010011
0000000000000000
0100100100010011
0111111100000000
1100100000010011
0000000000000001
1100100000010100
0111111100000000
10100011100010100
0000000000000001
和:10100011100010101
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
00000000000000011
0100011100010101
和:0100011100010110
ckSum+=(ckSum>>16)后:
0100011100010110
0000000000000000
和:0100011100010110
~:1011100011101000(效检和)
运算过程(注意用小端格式加):
0000000001000101
0000000001000101
0000001000000000
0000001001000101
0000000000000000
0000001001000101
0000000000000000
0000001001000101
0001000100000100
0001001101001001
0000000000000000
0001001101001001
0000000001111111
00010011 11001000
0000000100000000
0001010011001000
0000000001111111
0001010101000111
0000000100000000
和:0001011001000111
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
00000000000000001
0001011001000111
和:0001011001000111
ckSum+=(ckSum>>16)后:
0001011001000111
0000000000000000
和:0001011001000111
~:1110100110111000(效检和)
checksum2:1110100010111000(小端)
checksum1:1011100011101000(大端)
算法一样,说白了就是循环加,加到没有进位为止,然后在取反!
在现在《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位反码和之云云,没有强调进位也要参加运算一点征兆,直到最近写了不少程序才看清楚这个细节!
源码之下必解惑!