IP首部校验和、TCP首部校验和的一些实例和注意事项

首先,关于IP和TCP校验和的概念

IP首部的校验和相对简单,在不是特殊类型包的情况下,IP首部长度为20字节,IP校验和就是计算这20位的校验和;TCP校验和,TCP校验和不仅要校验20位的TCP首部与TCP首部后面的数据,还要在TCP首部前加上两个IP(每个IP四个字节)、十六位的TCP协议(为0x0006)、TCP首部与数据部分的字节数(即TCP首部和数据加起来的长度)组成的伪头。

IP校验和的计算:

发送端,

计算前,先把IP首部校验和设为0,然后将20字节的IP首部视为10个十六位的数值,从前(底层方向为前)往后每一位相加,相加的结果存入一个32位的变量;如果IP首部是特殊类型包,字节数不为20(可能21、23、27谁知道,都有可能),它剩下一个奇数位(它是一个8位的数值),扩展成一个16位的数(后面填充8位的0),把这个新生的16位数加入到前面的和里面。

得到这样一个和,再把这个和(32位的数值)的高16位与低16位相加,如果和的结果里,高16仍然不等于0,那么再把高16位与低16位相加,直到高16位为0。

这样一个数值它是一个16位的数,把它以二进制按位取反(比如一个4位的数值,1010按位取反就是0101),按位取反以后的数,就是发送端的校验和,把这个校验和填入IP首部校验和位置。

接收端,

接收端计算校验和,接收端收到的包里,已经填入了发送端填写的校验和,这个不用改。计算的规则和发送端一样,都是视为16位的数值,一一相加,奇数位扩展成16位的数值,加入和里面,再把高16位与低16位反复相加,得到一个16位的和,把这个和按位取反。

这里如果数据包的IP首部正确,那么计算结果就是0(这就是校验和的意义,发送端填入的校验和,接收端计算的时候将校验和计算在里面,结果如果为0,数据包的IP首部就是正确无误的)

 

//一个实例,以下实例原代码是从51单片机的程序里面拷过来的,在这里要感谢这位LiZhanglin前辈,他的程序给了我很多帮助。

//u_int8_t、u_int16_t 、u_int32_t分别为无符号的8位数、16位数、32位数


u_int16_t  CheckSum(u_int16_t * buff, u_long size, u_long InSum)//buff是指向IP首部的指针,InSum是一个初始值,如果是计算IP首部,它的值就为0
{
    u_int8_t *buf;//这个指针应该没什么用
    u_int32_t cksum = InSum;//把初始值赋值给用于求和的32位变量
   
    u_int16_t  * EndBuf = (u_int16_t *)buff + size / 2;//将IP首部结束位置赋值给一个16位指针,(如果IP首部长度为奇数,它指向的就是最后一个奇数位;如果IP首部长度为偶数,它指向的就是IP首部第20个字节(假设是20字节)的后一位,即实际上就是TCP头、UDP头、ICMP头的位置)
    while (buff < EndBuf)
    {
        cksum += ntohs(*(buff));//相加,这里要注意的,网络上通信用的都是大端序的存储方式,但是我们很多家用计算机里面都是小端序,我这里就必须通过ntohs将网络通信的格式转成我计算机里的格式,否则相加的和就会不正确
        buff++;
    }
    
    if ((size & 0x0001) != 0)//用一个与运算,IP首部字节数是否是奇数
    {
        cksum += ntohs((*buff)) & 0xff00;//是奇数,就将buff指针指向的数值与运算以后加入到和里面(这里buff指针是一个16位的指针,所以它所指的值,是IP首部的奇数位与后面TCP头或UDP头或ICMP头的头一个字节组合起来的16位数,需要把这个16位数的低8位置为0,变相达到将奇数位扩充为16位数的效果,这里的办法是与运算一个0xff00的十六位数;同样这里也要注意大小端序的问题)
    }
        
    cksum = (cksum >> 16) + (cksum & 0xffff);//高低16位反复相加,网上说,这样两句代码与循环将高低16位数相加的效果是一样的,最后结果都是一个16位的数,这应该是一个数学问题,有兴趣的哥哥姐姐可以去找找原因
    cksum += (cksum >> 16);
    
    return (u_int16_t)(~cksum);//将和按位取反,返回一个16位的数
}

 

TCP首部的校验和:

发送端

发送端将TCP首部的校验和位置设为0。

先计算伪首部,伪首部由发送端的IP+接收端的IP+TCP协议序号(0x0006)+TCP首部与数据的字节数组成,它并不是一个存在于数据包里的东西,而是一个我们为了方便计算而假设的一个首部,计算方式也是视为16位的数,一个个相加。

加完伪首部,加TCP首部和数据部分,同样视为16位的数值,一个个相加,如果最后TCP首部和数据部分字节数为奇数位,将奇数位扩充为低8位为0的16位数,加入和里面。

和的高低16位反复相加,得到一个16位数值,按位取反,返回。

 

接收端

计算方式同上,计算结果为0,说明伪头、TCP头、数据没有问题。

 

实例(我删改了部分注释,删掉了部分多余代码,也可能它并不多余,大家就着原理理解好了,真的缺了什么,自己补吧):

u_int16_t TCPCheckSum(struct ip_header * pIPHead, u_int16_t TCPSize)//参数:指向IP首部的指针,TCP首部与数据的字节数
{
    u_long sum = 0;
    u_int16_t  * p;
    u_int16_t i;
    u_int8_t buf[2000];
    
    
    sum = 0;

    //虽然不知道是不是我想多了,万一最后一位奇数位后面超出边界怎么办?所以我的想法是复制到一个数组里再计算
    memset(buf, 0x00, 2000);//初始化为0
    memcpy(buf,(u_int8_t*)&(pIPHead->ip_souce_address), 8);//两个IP共占据8个字节

    p = (u_int16_t *)buf;
    for (i = 0; i < sizeof(u_int32_t) / sizeof(u_int16_t) * 2; i++, p++) {
        sum += ntohs(*p);//加上两个IP,注意大小端序问题
    }
        
    //sum += (u_int16_t)pIPHead->ip_protocol;
    sum += 0x0006;//反正TCP的协议是0006,加上协议序号
  
    
    sum += (u_int16_t)TCPSize;//加上TCP首部和数据部分的字节数
    

    memset(buf, 0x00, 2000);//初始化为0
    memcpy(buf, (u_int8_t *)((u_int8_t *)pIPHead + 20), TCPSize);//复制TCP首部和数据
  
    return CheckSum((u_int16_t*)buf, TCPSize, sum);//调用前面的函数
}

另注:以上代码是在VS2017上测试成功的,如果你拿去不能用,就看看哪里需要修改。

 

你可能感兴趣的:(IP首部校验和、TCP首部校验和的一些实例和注意事项)