计算机网络学习--IP头校验和

604 篇文章 8 订阅
579 篇文章 5 订阅

首部长度 占4位,可表示的最大十进制数值是15。请注意,这个字段所表示数的单位是32位字长(1个32位字长是4字节),因此,当IP的首部长度为1111时(即十进制的15),首部长度就达到60字节。当IP分组的首部长度不是4字节的整数倍时,必须利用最后的填充字段加以填充。因此数据部分永远在4字节的整数倍开始,这样在实现IP协议时较为方便。首部长度限制为60字节的缺点是有时可能不够用。但这样做是希望用户尽量减少开销。最常用的首部长度就是20字节(即首部长度为0101),这时不使用任何选项。

1. 在发送数据时,为了计算数IP据报的校验和。应该按如下步骤:

    (1)把IP数据报的首部校验和置为0。

    (2)把首部看成以16位为单位的数字组成,依次进行二进制反码求和。
    (3)把得到的结果存入校验和字段中。
    在接收数据时,计算数据报的校验和相对简单,按如下步骤:
    (1)把首部看成以16位为单位的数字组成,依次进行二进制反码求和,包括校验和字段。
    (2)检查计算出的校验和的结果是否等于零。
    (3)如果等于零,说明被整除,校验是和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包

二进制反码求和:先进行二进制求和,然后对和取反。

首先,查看了Linux 2.6内核中的校验算法,使用汇编语言编写的,显然效率要高些。代码如下:
unsigned short ip_fast_csum(unsigned char * iph,
unsigned int ihl)
{
unsigned int sum;

__asm__ __volatile__(
"movl (%1), %0 ;\n"
"subl $4, %2 ;\n"
"jbe 2f ;\n"
"addl 4(%1), %0 ;\n"
"adcl 8(%1), %0 ;\n"
"adcl 12(%1), %0 ;\n"
"1: adcl 16(%1), %0 ;\n"
"lea 4(%1), %1 ;\n"
"decl %2 ;\n"
"jne 1b ;\n"
"adcl $0, %0 ;\n"
"movl %0, %2 ;\n"
"shrl $16, %0 ;\n"
"addw %w2, %w0 ;\n"
"adcl $0, %0 ;\n"
"notl %0 ;\n"
"2: ;\n"
/* Since the input registers which are loaded with iph and ihl
are modified, we must also specify them as outputs, or gcc
will assume they contain their original values. */
: "=r" (sum), "=r" (iph), "=r" (ihl)
: "1" (iph), "2" (ihl)
: "memory");
return(sum);
}
在这个函数中,第一个参数显然就是IP数据报的首地址,所有算法几乎一样。需要注意的是第二个参数,它是直接使用IP数据报头信息中的首部长度字段,不需要进行转换,因此,速度又快了(高手就是考虑的周到)。

第二种算法就非常普通了,是用C语言编写的。我看了许多实现网络协议栈的代码,这个算法是最常用的了,即使变化,也无非是先取反后取和之类的。考虑其原因,估计还是C语言的移植性更好吧。下面是该函数的实现:
unsigned short checksum(unsigned short *buf,int nword)
{
unsigned long sum;

for(sum=0;nword>0;nword--)
sum += *buf++;
sum = (sum>>16) + (sum&0xffff);//把高位的进位,加到低八位,其实是32位加法,这里千万不要把进位丢了!!
sum += (sum>>16);

/*这里的重复计算进位是为了防止刚刚加法再一次产生进位,这里是比较巧妙的,为什么不在一次计算,经过分析11111111,假设上次有进位,导致溢出,第二次再一次累加,必然会在上一次进位的地方停住,即不会产生进位。*/

return ~sum;
}

验证代码:

#include<stdio.h>
#include<stdlib.h>
unsigned short ipCksum(unsigned short *addr,int len)
{
/*    unsigned short cksum;
    unsigned int sum=0;

    while(len>1)
    {
        sum+=*addr++;
        len-=2;
    }
    if(len==1)
        sum+=*(unsigned char*)addr;
    sum=(sum>>16)+(sum&0xffff);  //把高位的进位,加到低八位,其实是32位加法
    sum+=(sum>>16);  //add carry
    cksum=~sum;   //取反
    return (cksum);
*/

unsigned long sum;
for(sum=0;len>0;len--)
sum += *addr++;
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);

return ~sum;



}

int main()
{
    unsigned short iph[]={0x4500,0x0028,0x01da,0x4000,0x4006,
        0x6f1b,0xc0a8,0xa401,0xc0a8,0xa488};
    unsigned short cksum;

    cksum=ipCksum(iph,10);
    printf("%X\n",cksum);
   // iph[5]=cksum;
   // cksum=ipCksum(iph,20);
   // if(cksum)
     //   printf("Checksum is incorrect!\n");
   // else
     //   printf("Checksum is correct!\n");
}

P.S. 至于为什么IP首部校验不采用循环冗余算法,有一个原因是因为当IP包在网络中传输时,其TTL字段经常会变动。考虑到这点,采用简单的叠加法,就可以避免中间路由器重新计算其校验和,而只需简单增1操作即可,提高效率。


Linux下IP数据包的描述结构体
所在头文件为/usr/src/linux/include/linux/ip.h,结构如下:
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD) //小端模式下
__u8 ihl:4,//首部长度(4位)
version:4;//ip协议版本IPv4
#elif defined (__BIG_ENDIAN_BITFIELD) //大端模式下
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;//服务类型字段(8位)
__be16 -tot_len;//16位IP数据报总长度
__be16 -id;//16位标识字段(唯一表示主机发送的每一分数据报)
__be16 -frag_off;//(3位分段标志+13位分段偏移数)
__u8 ttl;//8位数据报生存时间
__u8 protocol;//协议字段(8位)
__be16 check;//16位首部校验和
__be32 saddr; //源IP地址
__be32 daddr; //目的IP地址
};

#include <stdio.h>
#include <stdlib.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <sys/socket.h>


#define ETH_P_LENGTH 65535
#define ETHERNET_MAX_LEN 1500
#define ETHERNET_MIN_LEN 46

unsigned short ip_fast_csum(unsigned char * iph, unsigned int ihl);
unsigned short checksum(unsigned short *buf,int nword);


//--------------------------------------------------------------------
// Main function
//
// Do all if it can do
//
//--------------------------------------------------------------------

int main(int argc,char *argv[])
{
int listenfd;
int nbyte;
char buf[ETH_P_LENGTH];
struct ethhdr *eth = NULL;
struct iphdr *ip = NULL;
short chk;

//
// Print banner
//
printf("\n\tSendArp v1.0 - scan IP and MAC\n");
printf("\tNsfocus - www.nsfocus.com\n");
printf("\tby David Zhou\n");
printf("\tDate : 2006/01/19\n\n");

if ((listenfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0)
{
printf("Call socket() function error\n");
return 1;
}

for (;;)<span style="color:#FF0000;"><span style="color:#FF0000;">//这里可以修改检验数据包的数量</span>
{
if ((nbyte = recv(listenfd, buf, ETH_P_LENGTH, 0)) > 0)
{
struct ethhdr *eth = (struct ethhdr *)buf;
if(ntohs(eth->h_proto) == ETH_P_IP)
{ // EtherNet frame
// print ip sum
ip = (struct iphdr *)&buf[14];
printf("IP CheckSum = 0x%04X\n",ntohs(ip->check));
//verify ip checksum
chk = checksum((unsigned short*)ip,10);
printf("Verify CheckSum = 0x%04X\n\n",ntohs(chk));
//
// reset check to calc self
//
ip->check = 0;
// 2.6 kernel
chk = ip_fast_csum((unsigned char *)ip,ip->ihl);<span style="color:#CC0000;"><span style="color:#FF0000;">//利用内核函数实现检验和计算</span>
printf("Calc CheckSum = 0x%04X - %d\n",ntohs(chk),ip->ihl);
// coustom calc
chk = checksum((unsigned short*)ip,10);
printf("Calc CheckSum = 0x%04X\n\n",ntohs(chk));
}
}
}
return 0;
}


unsigned short checksum(unsigned short *buf,int nword)
{
unsigned long sum;

for(sum=0;nword>0;nword--)
sum += *buf++;
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);

return ~sum;
}

/*
* This is a version of ip_compute_csum() optimized for IP headers,
* which always checksum on 4 octet boundaries.
*
* By Jorge Cwik <jorge@laser.satlink.net>, adapted for linux by
* Arnt Gulbrandsen.
*/
unsigned short ip_fast_csum(unsigned char * iph,
unsigned int ihl)
{
unsigned int sum;

__asm__ __volatile__(
"movl (%1), %0 ;\n"
"subl $4, %2 ;\n"
"jbe 2f ;\n"
"addl 4(%1), %0 ;\n"
"adcl 8(%1), %0 ;\n"
"adcl 12(%1), %0 ;\n"
"1: adcl 16(%1), %0 ;\n"
"lea 4(%1), %1 ;\n"
"decl %2 ;\n"
"jne 1b ;\n"
"adcl $0, %0 ;\n"
"movl %0, %2 ;\n"
"shrl $16, %0 ;\n"
"addw %w2, %w0 ;\n"
"adcl $0, %0 ;\n"
"notl %0 ;\n"
"2: ;\n"
/* Since the input registers which are loaded with iph and ihl
are modified, we must also specify them as outputs, or gcc
will assume they contain their original values. */
: "=r" (sum), "=r" (iph), "=r" (ihl)
: "1" (iph), "2" (ihl)
: "memory");
return(sum);
}


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值