最近在研究网络协议,在用raw socket编程时遇到校验和的问题,发现校验和用了如下函数:
1 USHORT checksum(USHORT *buffer, int size)
2 {
3 unsigned long cksum=0;
4
5 while(size > 1)
6 {
7 cksum += *buffer++;
8 size -= sizeof(USHORT);
9 }
10
11 if(size)
12 {
13 cksum += *(UCHAR *)buffer;
14 }
15
16 cksum=(cksum >> 16) + (cksum & 0xffff);
17 cksum+=(cksum >> 16);
18 return(USHORT) (~cksum);
19 }
马上google,才发现区区的校验和也如此复杂(参考RFC1071)。马上翻RFC1071,这个也太专业了,不过勉强可以看懂,按照自己的理解记录一下,发现我记性越来越差了-_-!。
ICMP(包括IP等)校验和操作:
一、计算校验和:按2个字节(16位)对齐进行
反码加运算,然后放入校验和字段(16位)。
二、检验校验和:与计算校验和一样再计算一遍,如果为全1说明正确。
上面的反码加我是自己猜的不知道叫什么好,原文中的解释是
(1's complement sum,符号为+')。这个就不懂了,只好按自己理解的写了。
具体操作是先取反再加,如何有进位,进位要加到最低位上,相当于循环加了。
一般操作为了提高性能,往往先全部相加,再加上进位,再取反,就如上面的程序里一样。至于为什么要搞那么复杂,我猜是数学的严谨吧。
下面从数学的角度看这个问题:
一般的相加肯定要溢出或进位的,那溢出的部分信息就丢掉了。为了保留溢出的信息,需要把进位信息保留下来,也就是移到低位上相加,而这个一般加法是很难实现的,所以需要别的改进后的加法(反码加)。
想考虑8位时的情况:
0xF0 + 0xF0 = 0xE0 + 溢出;
如何保留溢出位,与最低位先加就得到:
0xF0 + 0xF0 = 0xE1;
上面运算是错误的,但有什么加法可以时上式成立呢???
答案就是反码加(计算校验和):
~0xF0 + (~0xF0) = ~0xE1; ==> (0xF0 +' 0xF0 = ~0xE1 = 0x1E), 0x1E就是校验和。
检验校验和:
0xF0 +' 0xF0 +' 0x1E = 0xFF; 校验时同时计算校验字节,结果为0xFF,为正确。
这个方法而且与CPU的字节序无关,具体看那个RFC1071去。
这个也太绕了,用程序实现基本上效率很低,所以只能走捷径:) ,贴上校验代码:
1 bool validatechecksum(unsigned short *buffer, int size)
2 {
3 unsigned long cksum=0;
4
5 while(size >1)
6 {
7 cksum+=*buffer++;
8 size-=sizeof(unsigned short);
9 }
10
11 if(size)
12 cksum+=*(unsigned short*)buffer;
13
14 cksum=(cksum >> 16)+(cksum&0xffff);
15 cksum+=(cksum >>16);
16
17 return ((unsigned short)cksum == 0xFFFF);
18 }
参考:
http://blog.csdn.net/World7th/archive/2008/12/31/3669278.aspx