网际校验和算法大端小端详解

http://www.roman10.net/how-to-calculate-iptcpudp-checksumpart-2-implementation/  网友的一个校验算法

IP只校验头,UDP,TCP都需要加12字节伪首部并和数据一起进行校验,icmp头和数据一起校验

 

IP CHECKSUM CALCULATION

RFC1071写得不够准确,没有考虑大端情况

这里需要说明的是,实际应用中,网络校验和都是通过硬件实现。

 

把数据报文,按照每16位相加起来,求得结果后 再求反。等价于书上所说的反码求和

如果是奇数个字节,那么就要补一个全0字节,凑成偶数个字节

如果需要校验的报文是偶数个字节,那么网际校验和 与大端小端没关系(Byte Order Independence)

 

如果是奇数个字节,就需要考虑大端小端的情况了。

 

AB CD 这是大端

CD AB 这是小端

如果需要加上奇数字节,那么校验和需要这么算

校验=》ABCD AB,如果

网络字节序ABCD在机器内部:

大端:AB CD + AB 00

小端:CD AB + 00 AB

本来以为应该是如上这么校验,但实际上处理最后一个奇数字节的方法是
前偶数个字节的数据流:1010101010…..(AB)
AB是最后一个奇数字节,需要处理成为机器内部 0xAB00 进行累加
这个奇数字节 在大端内部表示为AB00  在小端内部:00AB

所以在大端机器上,遇到奇数个字节,那个奇数字节要左移8位再 & 0xFF00使得00AB变为 AB00

小端机器上,奇数字节直接加就可以了,因为内部就是AB00

 

可以用大端小端checkorder程序判断一下你的机器是否小端的。一般的操作系统都是小端的,通信协议都是大端的。

 

RFC1071上的校验程序

in 6

{

/* Compute Internet Checksum for "count" bytes

            *         beginning at location "addr".

            */

registerlong sum = 0;

 

while( count > 1 )  {

/*  This is the inner loop */

sum += * (unsignedshort) addr++;

count -= 2;

}

 

/*  Add left-over byte, if any */

if( count > 0 )

sum += * (unsignedchar *) addr;//默认机器是小端

 

/*  Fold 32-bit sum to 16 bits */

while (sum>>16)

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

 

checksum = ~sum;

}

 

注意,在奇数字节情况下,处理最后一个字节(大端:0xABCD):

sum += *(unsignedchar*)addr

//一般系统都是小端的,所以可以直接加,就如上所说一个ABCD在小端系统下+AB:CD AB +  AB 00

sum += (*(unsignedchar*)addr << 8) & 0xFF00;

//可以不用把0xFF00的主机序改为网络序,网络字节序就是大端的,左移8位这个就是在大端系统条件下的,AB CD + AB 00

Q:处理这个奇数字节,能不能不考虑大端小端,直接就是 CD AB + AB(CD AB + 00 AB ),或者AB CD + AB(AB CD + AB 00),然后再按不分大端小端的算法,即用相同的算法进行校验,结果同样可以正确。

A:现在的网络设备,以太网卡路由器等,都是通过硬件进行校验的,如果校验不成功,就会丢弃你的packet,除非你是用自己的电脑发送packet给自己,否则是无法发送packe的。

 

Q:怎么写可以兼容大端小端的情况

A:

unsignedchar temp[2];

temp[0] = *(unsignedchar *)addr;

temp[1] = 0;

sum += *(unsignedshort *)temp;

 

stackOverFlow

也可以参考linux内核校验源码

 

小端算完校验和之后,不需要转换为网络字节顺序。

 

《TCP、IP详解》卷二给的基本算法:

unsignedshort

cksum (struct ip *ip, int len)

{

long sum = 0;

while ( len >1 ) {

sum += *((unsignedshort *) ip)++;

if (sum & 0x80000000)

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

len -= 2;

}

 

if ( len )

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

 

while ( sum >> 16)

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

 

return ~sum;

 

}

—————————————-

方法3RFC1071

 

/* Compute checksum for count bytes starting at addr, using one's complement of one's complement sum*/

 

staticunsignedshort compute_checksum(unsignedshort *addr, unsignedint count) {

 

registerunsignedlong sum = 0;

while (count > 1) {

sum += * addr++;

count -= 2;

}

 

//if any bytes left, pad the bytes and add

if(count > 0) {

sum += *(unsigned char*)addr//小端相加 CDAB +AB 00

   // sum += (*(unsigned char*)addr << 8) & htons(0xFF00);//可以不用把主机序改为网络序,

//RFC上都这么写 sum+=*(unsigned char*)addr,搞不清楚为什么不左移8位,这样右边8位就可以是0了,abcdabcd 00000000

//可能不左移动8位,就按照 00000000 abcdabcd来计算了,接收后用同样的方式再校验,也可以证明数据没有问题,但是无法发送给其他人

//这么理解也是有问题的,真实情况是大多数的操作系统都是小端的,所以不需要左移8 bits(left shift 8 bits),加AB,在机器中就是AB00

//校验时,补0也就是这样补 的

}

 

//Fold sum to 16 bits: add carrier to result

while (sum>>16) {

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

}

 

//one's complement

sum = ~sum;

return ((unsignedshort)sum);

}

 

 

参考实现tcp,udp,ip校验实现

 

—————————————-

方法一、

/* 校验和计算icmp_cksum网际校验和算法

参数:老师给的算法(linux程序设计上的算法)

data:数据

len:数据长度

返回值:

计算结果,short类型

*/

staticunsignedshort icmp_cksum(unsignedchar *data,  int len)

{

int sum=0;/* 计算结果 */

int odd = len & 0x01;/*是否为奇数*/

 

unsignedshort *value = (unsignedshort*)data;

/*将数据按照2字节为单位累加起来*/

while( len & 0xfffe)  {

sum += *(unsignedshort*)data;

data += 2;

len -=2;

}

/*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一字节。*/

if( odd) {

unsignedshort tmp = ((*data)<<8)&0xff00;//如果你的机器是大端的,就需要左移8位再&

sum += tmp;

}

sum = (sum >>16) + (sum & 0xffff);//可能存在进位,高16位的进位在加到低16位上

sum += (sum >>16) ;//上面操作可能又导致进位,在操作一次,不可能再出现进位情况

return ~sum;

}

 

—————————————-

方法2、

这个是http://www.netfor2.com/ipsum.htm给的校验算法,觉得有点问题

The IP Header Checksum is computed on the header fields only.

Before starting the calculation, the checksum fields (octets 11and12)

are made equal to zero.

 

In the example code,

u16 buff[] is an array containing all octets in the header with octets 11and12 equal to zero.

u16 len_ip_header is the length (number of octets) of the header.

 

/*

**************************************************************************

Function: ip_sum_calc

Description: Calculate the 16 bit IP sum.

***************************************************************************

*/

typedefunsignedshort u16;//16位

typedefunsignedlong u32;//32位

 

u16 ip_sum_calc(u16 len_ip_header, u16 buff[])

{

u16 word16;

u32 sum=0;

u16 i;

// make 16 bit words out of every two adjacent 8 bit words in the packet

// and add them up

for (i=0;i<len_ip_header;i=i+2){

 

//作者的意思应该是buff[i]左移8位,变高位,和下一个字节组成一个16位的word16

//但是,buff[i]是u16,buff[i+1]就指向了下两个字节位置,就产生错误了

 

//word16 =((buff[i]<<8)&0xFF00)+(buff[i+1]&0xFF);

//改为如下,或者直接把输入的buff改为char*

u8 *p = (u8 *)buff;

word16 =((u16)p[num] << 8 & 0xff00) + (u16)p[num + 1] & 0x00FF;

sum = sum + (u32) word16;

}

//如果是奇数个字节,还要在低位补8个0,再累加上去

if (i == len_ip_header){

sum += ((u16)p[len_ip_header-1] << 8) & 0xFF00;//按大端看,左移8位

}

// take only 16 bits out of the 32 bit sum and add up the carries可能会有进位

while (sum>>16)

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

 

// one's complement the result

sum = ~sum;

return ((u16) sum);

 

}

—————————————-

 

UDP校验和算法

UDP报文做校验的时候需要添加12字节的伪首部

12字节的伪首部包括  4字节源ip地址,4字节目的ip地址,1个字节0,1个字节17(ip数据报文中17表示UDP),2个字节的UDP长度

 

 

void compute_udp_checksum(struct iphdr *pIph, unsignedshort *ipPayload) {

registerunsignedlong sum = 0;//32bits check sum  校验和

struct udphdr *udphdrp = (struct udphdr*)(ipPayload);

unsignedshort udpLen = udphdrp->len;//htons(udphdrp->len);

 

sum += (pIph->saddr>>16)&0xFFFF;//源ip地址前16位

sum += (pIph->saddr)&0xFFFF;//源ip地址后16位

//the dest ip

sum += (pIph->daddr>>16)&0xFFFF; //目的地址前16位

sum += (pIph->daddr)&0xFFFF;//目的地址后16位

//protocol and reserved: 17

sum += htons(IPPROTO_UDP);//表示udp

//the length

sum += udphdrp->len;  //udp长度

 

//add the IP payload

//initialize checksum to 0 开始校验udp

udphdrp->check = 0;

while (udpLen > 1) {

sum += * ipPayload++;

udpLen -= 2;

}

//if any bytes left, pad the bytes and add

if(udpLen > 0) {

//sum += (*(unsigned char*)ipPayload) << 8 & 0xFF00;//大端情况,不要用htons

//sum += ((*ipPayload) & htons(0xFF00));小端情况

//这样写也可以,isPayload是16位,因为遇到了奇数字节,相当于前8个字符是我们需要的,后面8个就通过0xFF00 与 去掉了,但不会修改原值,与之后的新值加给了sum,相当于一个字符左移了8位,如果是大端就 不需要调用转换为网络字节序函数

//sum += ((*ipPayload) & 0xFF00);//大端

sum += *(unsigned char*)addr//小端

 

}

//Fold sum to 16 bits: add carrier to result

//printf("add carrier\n");

while (sum>>16) {

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

}

//printf("one's complement\n");

sum = ~sum;

//set computation result

udphdrp->check = ((unsignedshort)sum == 0x0000)?0xFFFF:(unsignedshort)sum;

 

}

 

—————————————-

TCP校验

 

staticunsignedshort tcp_checksum(struct iphdr *pIph, unsignedshort *ipPayload) {

registerunsignedlong sum = 0;

unsignedshort tcpLen = ntohs(pIph->tot_len) - (pIph->ihl<<2);//ip报文总长度 - ip头=tcp报文长度

//printf("tcpLen%d,tot_len%d,headLen%d\n",tcpLen,pIph->tot_len,pIph->ihl);

struct tcphdr *tcphdrp = (struct tcphdr*)(ipPayload);

//add the pseudo header

//the source ip

sum += (pIph->saddr>>16)&0xFFFF;

sum += (pIph->saddr)&0xFFFF;

//the dest ip

sum += (pIph->daddr>>16)&0xFFFF;

sum += (pIph->daddr)&0xFFFF;

//protocol and reserved: 6

sum += htons(IPPROTO_TCP);

//the length

sum += htons(tcpLen);

 

//add the IP payload

//initialize checksum to 0

tcphdrp->check = 0;

while (tcpLen > 1) {

sum += * ipPayload++;

tcpLen -= 2;

}

//if any bytes left, pad the bytes and add

if(tcpLen > 0) {

//printf("+++++++++++padding, %d\n", tcpLen);

 

//sum += (*(unsigned char*)(ipPayload) << 8 )& htons(0xFF00);//大端

//sum += ((*ipPayload) & htons(0xFF00));//小端

//这样写也可以,也适合大端,isPayload是16位,因为遇到了奇数字节,相当于前8个字符是我们需要的,后面8个就通过0xFF00去掉了,但不会修改原值,与之后的新值加给了sum

sum += *(unsigned char*)ipPayload;//最流行的方法

}

//Fold 32-bit sum to 16 bits: add carrier to result

while (sum>>16) {

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

}

sum = ~sum;

//set computation result

return (unsignedshort)sum;

}

 

e.g.

printf("\nt1:%x+0x12<<8 & 0xff00=%x\n",t1.value,t1.value+(0x12<<8 &0xff00));//大端小端的值都是一样的,内部实现不同

printf("0xff00=>%X;\nhtons(0xff00):%x\nhtons(htons(0xff00)):%x\n",0xff00,htons(0xff00),htons(htons(0xff00)));

 

结果:

t1:1234+(0x12<<8 & 0xff00)==2434

0xff00=>FF00;

htons(0xff00):ff

htons(htons(0xff00)):ff00

 

 

 

Tagged on:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.