首部检验和字段是根据 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就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。
I C M P、I G M P、U D P和T C P都采用相同的检验和算法,尽管 T C P和U D P除了本身的首部和数据外,在 I P首部中还包含不同的字段。在 RFC 1071[Braden, Borman and Patridge 1988]中有关于如何计算 I n t e r n e t检验和的实现技术。由于路由器经常只修改 T T L字段(减 1),因此当路由器转发一份报文时可以增加它的检验和,而不需要对I P 整个首部进行重新计算。 R F C1141[Mallory and Kullberg 1990]为此给出了一个很有效的方法。但是,标准的BSD实现在转发数据报时并不是采用这种增加的办法。
二进制反码求和
先进行二进制求和,然后对和取反。对一个无符号的数,先求其反码,然后从低位到高位,按位相加,有益处则向高位进1(和一般的二进制法则一样),若最高位有进位,则向最低位进1。
关于二进制反码求和运算需要说明的一点是,先取反后相加与先相加后取反,得到的结果是一样的。
It may look awkword to use a 1's complement addition on 2's complement machines. This method however has its own benefits.
Probably
the most important is that it is endian independent. Little Endian
computers store hex numbers with the LSB last (Intel processors for
example). Big Endian computers put the LSB first (IBM mainframes for
example). When carry is added to the LSB to form the 1's complement sum
(see the example) it doesn't matter if we add 03 + 01 or 01 + 03. The
result is the same.
Other benefits include the easiness of checking
the transmission and the checksum calculation plus a variety of ways to
speed up the calculation by updating only IP fields that have changed.
a、 不依赖系统是大端小端。即无论你是发送方计算机或者接收方检查校验和时,都不要调用htons或者ntohs,直接通过上面的算法就可以得到正确的结果。这个问题你可以自己举个例子,用反码求和时,交换16位数的字节顺序,得到的结果相同,只是字节顺序相应地也交换了;而如果使用原码或者补码求和,得到的结果可能就不同。
b、 计算和验证校验和比较简单、快递。
具体方法
在发送数据时,为了计算数IP据报的校验和。应该按如下步骤:
(1)把IP数据报的首部都置为0,包括校验和字段。
(2)把首部看成以16位为单位的数字组成,依次进行二进制反码求和。
(3)把得到的结果存入校验和字段中。
在接收数据时,计算数据报的校验和相对简单,按如下步骤:
(1)把首部看成以16位为单位的数字组成,依次进行二进制反码求和,包括校验和字段。
(2)检查计算出的校验和的结果是否全为1。
(3)如果全为1,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包
接收方计算校验和时的首部与发送方计算校验和时的首部相比,多了一个发送方计算出来的校验和。因此,如果首部在传输过程中没有发生差错,那么接收方计算的结果应该为全一,因为接收方计算除校验和以外的部分得到值是校验和的反码,再加多出来的校验和当然是全一了。
计算对IP首部检验和的算法如下:
(1)把IP数据包的校验和字段置为0;
(2)把首部看成以16位为单位的数字组成,依次进行二进制求和(注意:求和时应将最高位的进位保存,所以加法应采用32位加法);
(3)将上述加法过程中产生的进位(最高位的进位)加到低16位(采用32位加法时,即为将高16位与低16位相加,之后还要把该次加法最高位产生的进位加到低16位)
(4)将上述的和取反,即得到校验和。
程序
unsigned short cal_chksum(unsigned short *addr, int len) { int nleft = len; int sum = 0; unsigned short *w=addr; unsigned short answer = 0; while(nleft > 1){ // 16bit为单位累加运算 sum += *w++; nleft -= 2; } if(nleft == 1){ //若addr奇数个字节,会剩下最后一字节. sum + =*(unsigned char *)w; } sum = (sum>>16) + (sum&0xffff); sum += (sum>>16); answer = ~sum; return answer; }
示例
IP/UDP/TCP/ICMP校验和异同
这里还要提一点,UDP的校验和是可选的,当校验和字段为0时,表明该UDP报文未使用校验和,接收方就不需要校验和检查了!那如果UDP校验和的计算结果是0时怎么办呢?书上有这么一句话:“如果校验和的计算结果为0,则存入的值为全1(65535),这在二进制反码计算中是等效的。”
struct { unsigned long saddr; //源地址 unsigned long daddr; //目的地址 char mbz; //强制置空 char ptcl; //协议类型 unsigned short tcpl; //TCP长度 }psd_header;
然后我们将这两个字段复制到同一个缓冲区SendBuf中并计算TCP校验和:
memcpy(SendBuf, &psd_header, sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header), &tcp_header, sizeof(tcp_header));
tcp_header.th_sum=checksum((unsigned short *)SendBuf, sizeof(psd_header)+sizeof(tcp_header));
参考:
图解TCP/IP
TCP/IP详解卷一