IP数据报首部校验和算法

一、校验和算法

  IP校验和主要是用来保证数据(IP报头)的完整性的。它用的算法非常简单,就是反码求和校验。需要注意的是反码求和又叫1的补码(one'scomplement),而2的补码就是我们通常说的补码求和了。校验算法具体如下。

1、发送方

  i)将校验和字段置为0,然后将IP包头按16比特分成多个单元,如包头长度不是16比特的倍数,则用0比特填充到16比特的倍数;

  ii)对各个单元采用反码加法运算(即高位溢出位会加到低位,通常的补码运算是直接丢掉溢出的高位),将得到的和的反码填入校验和字段;

  iii)发送数据包。

2、接收方

  i)IP包头按16比特分成多个单元,如包头长度不是16比特的倍数,则用0比特填充到16比特的倍数;

  ii)对各个单元采用反码加法运算,检查得到的和是否符合是全1(有的实现可能对得到的和会取反码,然后判断最终值是不是全0)

iii)如果是全1则进行下步处理,否则意味着包已变化从而丢弃之。需要强调的是反码和是采用高位溢出加到低位的,如3比特的反码和运算:100b+101b=010b(因为100b+101b=1001b,高位溢出1,其应该加到低位,即001b+1b(高位溢出位)=010b)

具体细节请参考文章:http://blog.chinaunix.net/u/20/showart_438418.html

3、示例

IP 首部中的校验和为例,计算过程可分为三个步骤:

 

1.把校验和字段以全零填充;

2.对每 16 位(2 Byte)进行二进制反码求和;

(这里说的反码求和,不是说先对每 16 位求反码然后求和,而是说把每16 位当做反码求和。所有数据反码求和结束后,将最高位的进位进到最低位。)

3.对得到的结果取反即得校验和数据。

 

示例:

对如下十六进制数据求反码校验和:

0x4500,0x003C,0xCA2C,0x0000,0x8001,0x0000,0xC0A8,0x04FD,0xC0A8,0x0405

对以上数据直接相加得结果:0x0319BB

按照2中规则,对此数据的处理应该是将16位数最高位的进位0x030x19BB相加,即得到中间结果:0x19BE

按照3中规则对其取反即得校验和:0xE641

关于接收时的验证:

2中所述,将所有16位数据直接相加,并将进位数据加到最低位,此时与计算过程相比,数据中多了一个反码数据,因此如果传输途中没有差错,此时的计算结果应当是16位全1数据。

下面结合上面的示例说明计算原理,由校验和的计算过程可知,所有数据(含校验和数据)的代数和应当小于0x03FFFF,其中最高位的0x0316位数据的进位数据。因此对于发送时计算校验和的过程和接收时根据校验和校验的过程这两个过程中的求和运算,16位的进位数据是完全相同的,因此在接收校验时可直接把所有数据相加并把最终的进位数据加到最低位,若传输无误则结果必然是全1

 

 

二、校验和源码

网上流传多组实现,常见的有如下两种(如追求效率可改写为汇编代码)

1RFC1071源码

Code highlighting produced by ActiproCodeHighlighter (freeware)

http://www.CodeHighlighter.com/

 

unsigned short csum(unsigned char *addr,int count)

{

  /* ComputeInternet Checksum for "count" bytes beginning at location"addr". */

  registerlong sum = 0;

 

  while( count> 1 )

  {

        /* This is the inner loop */

        sum += * (unsigned short) addr++;

        count -= 2;

  }

 

/* Add leftover byte,if any */

if(count>0)      // 奇数字节,考虑CPU大端还是小端模式

#if BIG_ENDIAN

sum+=(*(unsigned char *)addr)<<8;

#else

sum+=*(unsigned char *)addr;

#endif

 

  /* Fold32-bit sum to 16 bits */

  while(sum>>16)

        sum = (sum & 0xffff) + (sum >>16);

 

  return ~sum;

}

 

  第一个while循环是做普通加法(2进制补码加法),因为IP包头和TCP整个报文段比较短(没达到2^17数量级),所以不可能导致4字节的sum溢出(unsigned long 一般至少为4字节)

  紧接着的一个判断语句是为了能处理输入数据是奇数个字节的这种情况。再接着的数据循环是实现反码算法(在前面的普通加法得到的数据的基础上),由反码和的高位溢出加到低位的性质,可得到“32位的数据的高位比特移位16比特,再加上原来的低16比特,不影响最终结果”这个等价运算,因为sum的最初值(刚开始循环时)可能很大,所以这个等价运算需循环进行,直到sum的高比特(16比特以上)全为0。对于32 位的 sum,事实上这个运算循环至多只有两轮,所以也有程序直接用两条“sum = (sum & 0xffff) + (sum >> 16);”代替了整个循环。最后,对和取反返回。

2、对数据长度没限制的实现

Code highlighting produced by ActiproCodeHighlighter (freeware)

http://www.CodeHighlighter.com/

 

unsigned short cksum (struct ip *ip, intlen)

{

long sum = 0; /* assume 32 bit long, 16 bitshort */

 

while ( len >1 )

  {

sum += *((unsigned short *) ip)++;

       

if (sum & 0x80000000) /* if high-orderbit set, fold */

sum = (sum & 0xFFFF) + (sum>> 16);

len -= 2;

  }

 

  if ( len )/* take care of left over byte */

sum += ( unsigned short ) * (unsignedl char*) ip;

 

  while ( sum>> 16)

sum =(sum & 0xFFFF) + (sum>> 16);

 

return ~sum;

}

 

  这个实现与前面的一个的最大的不同是对数据的长度没什么限制了,因为它在第一个循环的加法运算中实时检测sum的高位的值,一旦发现其有溢出的危险,就及时运用等价运算关系消除了这个危险。

 

三、几个细节问题

1、数据部分改变时的重校验

  考虑这样的应用场景:路由器转发IP报文时,有可能只更改了IP数据包头的部分内容(如更改了TTL,分片了或SNAT更改了源IP等),却需要重校验的问题。为提高转发效率,要求重校验算法尽可能快,故出现了如下所示的重校验算法(只是一个简单的示例):

 

Code highlighting produced by ActiproCodeHighlighter (freeware)

http://www.CodeHighlighter.com/

 

UpdateTTL(struct ip_hdr *ipptr, unsignedchar n)

{

unsigned long sum;

unsigned short old;

 

old = ntohs(*(unsigned short*)&ipptr->ttl);

ipptr->ttl -= n;

sum = old + (~ntohs(*(unsigned short*)&ipptr->ttl) & 0xffff);

sum += ntohs(ipptr->Checksum);

sum = (sum & 0xffff) + (sum>>16);

ipptr->Checksum = htons(sum +(sum>>16));

}

  算法的实现依据是这样的。假设包头原校验和为~C,改变的字段的原始值是m,更改后的值是m',设~C'为重校验和,则有 ~C' = ~(C+(-m)+m') = ~C+(m-m') = ~C+m+~m'等价关系的成立基于反码的运算性质:取反运算满足结合律,按位取反运算与符号取反(及相反数)是等价的(即~C=-C)。

  如果有多个字段改变,只是上面的公式中的mm'有多个而已,直接用反码加法搞定即可。

2、为什么采用反码和运算

  IP数据包校验要求速度快,所以只采用了简单的和校验,为什么采用反码和而不是补码和呢?

  i)反码和的溢出有后效性(蔓延性)

  反码和将高位溢出加到低位,导致这个溢出会对后面操作有永久影响,有后效性;而补码和直接将高位和溢出,导致这个溢出对后面的操作再无影响,因此无后效性。

  ii)反码校验无需考虑字节序

  正因为反码和的溢出有后效性,导致大端字节序(big-endian)和小端字节序(little-endian)对同一数据序列(如两个16比特的序列)产生的校验和也只是字节序相反,而补码和因为将溢出丢掉了,不同字节序之间的校验和大不相同且没什么联系。

  基于以上的理由,校验和运算既可选择在数据被转换成网络字节序前,也可选择在之后,只要保证被校验的字段和填写的校验和字段的字节序保持一致就可以了。(这其实可以看作是负负得正,计算校验和与字节序有关,然后写校验和字段与字节序有关,然后直接计算校验和再写校验和字段则与字节序无关。)

四、参考文章

http://blog.chinaunix.net/u/20/showart_438512.html,关于IP校验和的

http://blog.chinaunix.net/u/12313/showart_176114.html,关于网络校验和的

http://www.wesoho.com/article/Delphi/2143.htm,关于IP校验和的

http://blog.chinaunix.net/u/20/showart_438418.html,关于补码和反码的


  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
IP数据校验和算法是一种很重要的数据包完整性校验手段,该算法IP数据的发送和接收过程中都得到广泛应用。 该算法的实现步骤如下: 1. 首先将整个IP数据包的首部按照16bit一组进行分组,不足16bit且不是末尾则在末尾添加0 2. 将这些16bit的数字从第一个到最后一个相加,得到一个数值 3. 以32位为单位将这个数值进行反码运算,得到的结果即为校验和 参考样例代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFERSIZE 32 /* * 计算IP数据首部校验和 * @param buf 待计算数据首部 "struct ipheader" 的指针 * @param len 数据首部长度 (单位:bytes) * return 计算得到的校验和 */ unsigned short checksum(unsigned short *buf, int len){ unsigned long sum = 0; while(len > 1){ sum += *buf++; len -= 2; } if(len){ sum += *(unsigned char*)buf; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } struct ipheader{ unsigned char ver_ihl; /* 版本 (4 bits) + 首部长度 (4 bits) */ unsigned char tos; /* 服务类型(Type of Service) */ unsigned short total_len; /* 总长(Total Length) */ unsigned short ident; /* 标识(Identification) */ unsigned short frag_and_flags; /* 标志位(Flags) (3 bits) + 片偏移(Fragment offset) (13 bits) */ unsigned char ttl; /* 存活时间(Time to live) */ unsigned char protocol; /* 协议(Protocol) */ unsigned short checksum; /* 首部校验和(Header checksum) */ unsigned int sourceIP; /* 源IP地址(Source IP address) */ unsigned int destIP; /* 目的IP地址(Destination IP address) */ }; int main(){ unsigned char buffer[BUFFERSIZE]; memset(buffer,0,BUFFERSIZE); struct ipheader *ip = (struct ipheader*) buffer; ip->ver_ihl = 0x45; ip->tos = 0; ip->total_len = htons(sizeof(struct ipheader)); ip->ident = htons(54321); ip->frag_and_flags = 0; ip->ttl = 128; ip->protocol = 6; ip->checksum = 0; ip->sourceIP = inet_addr("192.168.1.101"); ip->destIP = inet_addr("192.168.1.1"); unsigned short check_sum = checksum((unsigned short*)ip,sizeof(struct ipheader)); printf("check sum=%d\n",check_sum); ip->checksum = check_sum; return 0; } 该程序首先定义了一个结构体ipheader,该结构体包含了一个完整的IP首部,然后通过调用checksum函数计算该IP数据首部校验和。最后将计算得到的校验和存入IP首部中的checksum字段。运行该程序后即可得到IP数据首部校验和
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值