Chinaunix首页 | 论坛 | 博客
  • 博客访问: 791346
  • 博文数量: 83
  • 博客积分: 7030
  • 博客等级: 少将
  • 技术积分: 1097
  • 用 户 组: 普通用户
  • 注册时间: 2007-08-06 15:50
文章分类

全部博文(83)

文章存档

2011年(2)

2010年(9)

2009年(56)

2008年(16)

我的朋友

分类: 网络与安全

2009-10-22 16:36:53

问题:

这几天在写dhcp client的代码,碰到个问题,就是UDP的校验和(CRC)怎么算都不正确,经过多番努力,终于找到问题所在了.

描述:

RFC xxxx里面有讲到,U D P检验和包括U D P首部和U D P数据,所以当我调用CRC校验和的函数时就从UDP的头部开始,长度 = UDP的头长+UDP数据长度,结果不正确(通过Wirshark 工具抓包看到的)。

原因:

找了许久,最终在《TCP-IP详解卷1》里面找到了答案。

一句话概括:如下图,在计算UDP校验和的时候要多加个伪UDP头的值。

具体描述如下:

U D P检验和覆盖U D P首部和U D P数据。回想I P首部的检验和,它只覆盖I P的首部—并不

覆盖I P数据报中的任何数据。

U D PT C P在首部中都有覆盖它们首部和数据的检验和。U D P的检验和是可选的,而T C P

的检验和是必需的

尽管U D P检验和的基本计算方法与我们在3 . 2节中描述的I P首部检验和计算方法相类似

16 bit字的二进制反码和),但是它们之间存在不同的地方。首先, U D P数据报的长度可以为

奇数字节,但是检验和算法是把若干个16 bit字相加。解决方法是必要时在最后增加填充字节

0,这只是为了检验和的计算(也就是说,可能增加的填充字节不被传送)。

其次, U D P数据报和T C P段都包含一个1 2字节长的伪首部,它是为了计算检验和而设置

的。伪首部包含I P首部一些字段。其目的是让U D P两次检查数据是否已经正确到达目的地

(例如, I P没有接受地址不是本主机的数据报,以及I P没有把应传给另一高层的数据报传给

U D P)。U D P数据报中的伪首部格式如图11 - 3所示。

11-3 UDP检验和计算过程中使用的各个字段

在该图中,我们特地举了一个奇数长度的数据报例子,因而在计算检验和时需要加上填

充字节。注意,U D P数据报的长度在检验和计算过程中出现两次。

如果检验和的计算结果为0,则存入的值为全16 5 5 3 5),这在二进制反码计算中是等效

的。如果传送的检验和为0,说明发送端没有计算检验和。

如果发送端没有计算检验和而接收端检测到检验和有差错,那么U D P数据报就要被悄悄

地丢弃。不产生任何差错报文(当I P层检测到I P首部检验和有差错时也这样做)。

U D P检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为

了发现U D P首部和数据在发送端到接收端之间发生的任何改动。

尽管U D P检验和是可选的,但是它们应该总是在用。在8 0年代,一些计算机产商在默认

条件下关闭U D P检验和的功能,以提高使用U D P协议的N F SNetwork File System)的速度。

在单个局域网中这可能是可以接受的,但是在数据报通过路由器时,通过对链路层数据帧进

行循环冗余检验(如以太网或令牌环数据帧)可以检测到大多数的差错,导致传输失败。不

管相信与否,路由器中也存在软件和硬件差错,以致于修改数据报中的数据。如果关闭端到

端的U D P检验和功能,那么这些差错在U D P数据报中就不能被检测出来。另外,一些数据链

路层协议(如S L I P)没有任何形式的数据链路检验和。

Host Requirements RFC声明,U D P检验和选项在默认条件下是打开的。它还声明,

如果发送端已经计算了检验和,那么接收端必须检验接收到的检验和(如接收到检验

和不为0)。但是,许多系统没有遵守这一点,只是在出口检验和选项被打开时才验证

接收到的检验和。

 

解决:

1.      先不要计算ipCRC校验和,而是先借用ip header 的几个字段,把它填入伪udpheader

2.      计算udpcrc校验和,起始地址从ip开始计算,长度=ip header长度+udp header 长度+udp数据长度。

3.      清空刚刚借用的ip header 字段。

4.      计算ip 的校验和。

 

代码实现:

 

#ifndef IPHL
#define IPHL sizeof(struct ip)
#endif

#ifndef UDPHL
#define UDPHL sizeof(struct udphdr)
#endif
unsigned short my_cksum(addr,len)
unsigned short *addr;
int len;
{
  register int sum = 0;
  register u_short *w = addr;
  register int nleft = len;
  while (nleft > 1) {
    sum += *w++;
    nleft -= 2;
  }
  if (nleft == 1) {
    u_char a = 0;
    memcpy(&a,w,1);
    sum += a;
  }
  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  return ~sum;
}

void udpipgen(
    unsigned char *frame,
    unsigned long saddr,
    unsigned long daddr,
    unsigned short sport,
    unsigned short dport,
    unsigned short msglen
)
{
    struct ip *ip = (struct ip *)(frame);
    struct udphdr *udp = (struct udphdr *)(frame + IPHL);
    struct ipovly *io=(struct ipovly *)frame;/*Only for UDP crc*/
    if(ip == NULL || udp == NULL || io == NULL)
    {
        return;
    }
    io->ih_next = io->ih_prev = 0;
    io->ih_dst.s_addr = daddr;
    io->ih_src.s_addr = saddr;
    io->ih_x1 = 0;
    io->ih_pr = IPPROTO_UDP;
    io->ih_len = htons (UDPHL + msglen);

    udp->uh_sport = sport;
    udp->uh_dport = dport;
    udp->uh_ulen = io->ih_len;
    udp->uh_sum=my_cksum((unsigned short *)ip,UDPHL + IPHL + msglen);    
    memset(io,0x00,sizeof(struct ipovly));
    ip->ip_v = IPVERSION;
    ip->ip_hl = IPHL >> 2;
    ip->ip_tos = 0;
    ip->ip_len = UDPHL + IPHL + msglen;
    ip->ip_id = htons (0xF1C);
    ip->ip_off = 0;
    ip->ip_ttl = IPDEFTTL;
    ip->ip_p = IPPROTO_UDP;
    ip->ip_src.s_addr = saddr;
    ip->ip_dst.s_addr = daddr;
    ip->ip_sum = my_cksum ( (u_short *)ip, IPHL);
}


阅读(9775) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

happyyangxu2010-04-02 21:48:26

對啊,上面就是啊“ip->ip_sum = my_cksum ( (u_short *)ip, IPHL); ”

chinaunix网友2010-04-02 14:11:35

哥们,计算ip首部校验和的时候只用ip头就行了