首先看看TCP校验和计算原理:
TCP校验字段占两个字节如0x62,0x0d,实际上是这样计算来的:上述算出来的校验和(设为resule)假如超过0xffff的话,将超过的部分添加到result的低位去再用0xffff减去该值,如:0x54321最终应成为:0xffff-(0x4321+0x05)
其次根据抓包工具抓住的TCP报文我们看到下列图片中已经有一个包含了双方IP,TCP报文以及信息的报文,根据图片中给出的信息,依照上述原理手动计算一个TCP校验和:
本地Ip:0xc0,0xa8,0x9f,0x01;
对方IP:0xc0,0xa8,0x9f,0x82
TCP字段:0x04, 0xc6, 0x87, 0x01, 0x4b, 0xd7, 0x89, 0x9f, 0x4e, 0x3b, 0x90, 0xae, 0x50, 0x18, 0xff, 0xff, 0xeb, 0x69, 0x00, 0x00
从上述实际报文可见该TCP的校验和为eb69
将对应的校验和字段设为0x00后TCP报文实际为:
0x04, 0xc6, 0x87, 0x01, 0x4b, 0xd7, 0x89, 0x9f, 0x4e, 0x3b, 0x90, 0xae, 0x50, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00
数据字段abc,对应0x61,0x62,0x63;
TCP报文长度为20,信息长度为3,23化为16进制即0x17
所以result=(0xc0a8+0x9f01)+(0xc0a8+0x9f82)+(0x04c6+0x8701+0x4bd7+0x899f+0x4e3b+0x90ae+0x5018+0xffff+0x0000+0x0000)+(0x17+0x06)+(0x6162+0x6300)
得出result=0x7148f,将超出0xffff的位加到低位上去,reuslt=0xffff-(0x148f + 0x0007)=0xeb69正好和抓包工具显示的校验和字段是一样的.
最后就是给出一个计算校验和的函数了
#include <stdio.h> INT8U sum[2]={0}; //两个保存校验和的全局变量 INT8U Info[30]={0}; //保存实际数据的全局变量 /* *计算校验和的函数,传递过来的参数分别是:指向纯TCP报文的的指针data,指向本地IP的指针mIP,指向对方IP的指针yIP,实际要传送的数据长度 length *该函数计算TCP校验和字段的实际填充信息,通过全局变量数组sum[2]返回. */ void checksum(INT8U *data,INT8U *mIP,INT8U *yIP,INT16U length) { INT16U csum; INT16U i; INT32U dat[30]={0}; //保存TCP的的数组 INT32U shu[30]={0}; //保存信息的数组 INT16U temp; INT32U result,temp1,temp2,temp3,temp4=0,temp5=0; //将IP字段从数组中取出组合成新的数据 temp1=((INT16U)mIP[0]<<8)+(INT16U)mIP[1]+((INT16U)mIP[2]<<8)+(INT16U)mIP[3]; temp2=((INT16U)yIP[0]<<8)+(INT16U)yIP[1]+((INT16U)yIP[2]<<8)+(INT16U)yIP[3]; //数据长度+协议类型 temp3=(INT16U)(length)+0x06; for(i=0 ; i<length ; i++) { if(i%2==0) //将偶数位左移放到高位 { dat[i]=(INT16U)data[i]<<8; shu[i]=(INT16U)Info[i]<<8; } else //奇数位不变 { dat[i]=(INT16U)data[i]; shu[i]=(INT16U)Info[i]; } } dat[16]=0x00; //校验和字段初始置0 dat[17]=0x00; for(i=0 ; i<length ; i++) { temp4=dat[i]+temp4; temp5=shu[i]+temp5; } result=temp1+temp2+temp3+temp4+temp5; if(result>0xffff) { temp=result/0xffff;//得到高位 csum=(INT16U)result;//得到低位 csum=0xffff-(csum+temp); } //将计算出来的数据和高位和低位分别放到数组对应字段中 sum[0]=csum>>8; sum[1]=(INT8U)csum; } int main() { INT8U length=20; INT8U i,j=0; INT8U info[30]={'a','b','c'}; INT8U mIP[]={0xc0,0xa8,0x9f,0x01}; INT8U yIP[]={0xc0,0xa8,0x9f,0x82}; INT8U PSH_ACK[30]={0x04,0xc6,0x87,0x01,0x4b,0xd7,0x89,0x9f,0x4e,0x3b, //一个TCP数组 0x90,0xae,0x50,0x18,0xff,0xff,0x00,0x00,0x00,0x00}; for(i=0;i<30;i++) //数据放到全局变量 Info[i]=info[i]; while(Info[j]!=0) j++; //获得实际信息的长度 length=length+j; checksum(psh,mIP,yIP,length); printf("\n\nlength:%x%x\n\n",sum[0],sum[1]); //看答案是否是620d return 0; }注意其中的INT8U等类型是这样定义的
typedef unsigned char INT8U; /* 无符号8位整型变量 */ typedef unsigned short INT16U; /* 无符号16位整型变量 */ typedef unsigned int INT32U; /* 无符号32位整型变量 */该计算TCP校验和的函数最初是为"嵌入式IP_TCP协议栈"的实现而编写的,其环境是
操作系统: uCOS
网络芯片: enc28j60
硬件环境: 使用Proteus + LPC2124
开发环境: Keil V0.3
当放到嵌入式keil v0.3的时候,将相关头文件和库函数如printf和stdio.h更换掉,或干脆不用.
当然因为它最初是在vs里完成的也可以用在windows里需要计算校验和的地方.当然该函数有不尽人意的地方,比如完全可以将信息放到TCP数组的里面,这样可以减少很多累赘的语句提高运行速度,希望各位IT好友提出其他的建议和意见!