对UDP校验和的理解

关于udp传输的不可靠性,用过这个的人都知道会丢包。具体细节可能就不清楚了,经过我的理解和总结,有以下两点:

1)udp包的大小可以达到64k,但实际上mtu大小只有1k多,如果直接发一个超过mtu大小的包,就会在协议层被分片,这样的问题是,如果只要有一个分片在传输中出错了即校验不正确(这是较容易发生的),整个传输的udp包就被丢弃。注意是整个而不是单个分片。这就是为什么发送udp包通常也是1k多大小的原因,rtp是在udp之上的协议,也考虑了这个问题。

2)实际上收到的数据都是经过校验的,不存在传错的问题,即在应用层调用udp传输时,不会出现发送了"ABCD",收到的却是"ABED"的情况,只有丢包或乱序的问题。而udp接收缓冲区过小也是造成丢包的原因,适当增大udp缓冲区能够降低丢包率。

在实际应用中,比如流媒体传输中,就要对接收的乱序的包进行重排(重排时间的长短又是一个关键),这时候发现某个包丢了(前提是要加一个序号,这个rtp包头里面有),还需要做重传的工作,然而udp协议就是为了传输的实时性而生的,所以这里面就有一个权衡的问题,既不能全部都重传,也不能不重传。如果全部都重传,那就和TCP没有区别了;如果不重传,丢包就会导致流媒体的不完整性,需要做的处理就是错误修复或隐藏,对于音频和视频都有一些算法,由于信息丢失,这些算法也只能做一些弥补,但对于无法进行重传的情况,比如播放基于udp的ts节目流,这些纠错算法的好坏就是关键。

以下是转载,原文地址不详。原文如下:

很多文章对ip首部检验和的计算介绍得很简略,在理解上常常会比较困难。这篇文章是我自己的一些理解。或许也有不正确的地方,希望大家指正。

这个问题一直困绕了我很长时间,今天终于理解了。 

我们可以通过spynet sniffer抓包软件,抓取一个ip数据包进行分析研究。 
下面我以本机抓到的一个完整的ip首部为例(红色字体表示): 

0000: 00 e0 0f 7d 1e ba 00 13 8f 54 3b 70 08 00 45 00 
0010: 00 2e be 55 00 00 7a 11 51 ac de b7 7e e3 c0 a8 
0020: 12 7a 

45 00 00 2e----4表示ip版本号为ip第4版;5表示首部长度为5个32 bit字长,即为20字节;00 2e表示ip总长度为46字节,其中ip数据部分为 
26字节。 
be 55 00 00----be 55表示标识符;00 00表示3 bit标志及13 bit片偏移量; 
7a 11 51 ac----7a表示ttl值为122;11表示协议号为17的udp协议;51 ac表示16 bit首部检验和值; 
de b7 7e e3----表示32 bit 源ip地址为222.183.126.227 
c0 a8 12 7a----表示32 bit 目的ip地址为192.168.18.122

检验和计算: 
首先,把检验和字段置为0。 
45 00 00 2e 
be 55 00 00 
7a 11 00 00<----检验和置为0 
de b7 7e e3 
c0 a8 12 7a 
其次,对整个首部中的每个16 bit进行二进制反码求和,求和值为3ae50,然后3+ae50=ae53(这是根据源代码中算法 cksum = (cksum 
>> 16) + (cksum & 0xffff) 进行的 ) 

最后,ae53+51ac=ffff。因此判断ip首部在传输过程中没有发生任何差错。

"二进制反码求和" 等价于 "二进制求和再取反"
从源代码看,很关键的一点是二进制求出的和如果大于16位时所做的操作,用和值中高16位加上低16位的值作为最终的和值,然后再做取反运算.

 

The IP Header Checksum is computed on the header fields only. 
Before starting the calculation, the checksum fields (octets 11 and 12) 
are made equal to zero.

In the example code, 
u16 buff[] is an array containing all octets in the header with octets 11 and 12

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.
***************************************************************************
*/
typedef unsigned short u16;
typedef unsigned long u32;

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){
        word16 =((buff[i]<<8)&0xFF00)+(buff[i+1]&0xFF);
        sum = sum + (u32) word16;
 }


 // 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);
}

又一种写法

//计算校验和(直接相加,校验和需取反)

ip_buf->i.checksum=0;

ip_buf->i.checksum=~csum((WORD *)&ip_buf->i,sizeof(ipheader));

//ipheader是字节为单位的
WORD csum(void *dp, WORD count)
{
          register DWORD total=0;
          register WORD n, *p, carries;

          n = count / 2;
          p = (WORD *)dp;
          while (n--)
              total += *p++; //先加total = *p +total ;再P++;
          if (count & 1) //如果为单数,就是上面所count/2除不尽,
 {
 n=*(BYTE *)p;
              total +=n<<8;//n变成16位,在后面补0,再相加
 }
          while ((carries=(WORD)(total>>16))!=0)
              total = (total & 0xffff) + carries;
          return((WORD)total);
}

可以这样子理解:1110101,反码 0001010,两个相加的话,为1111111.

//////////////////////////////////////

关于IP分组头的校验和(checksum)算法,简单的说就是16位累加的反码运算,但具体是如何实现的,许多资料不得其详。TCP和UDP数据报头也使用相同的校验算法,但参与运算的数据与IP分组头不一样。此外,IPv6对校验和的运算与IPv4又有些许不同。因此有必要对IP分组的校验和算法作全面的解析。

 

IPv4分组头的结构如下所示:

      0                      1                      2                      3       
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1  
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |Version|  IHL  |Type of Service|             Total Length            |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |            Identification           |Flags|         Fragment Offset       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |  Time to Live |       Protocol      |           Header Checksum           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          Source Address                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                       Destination Address                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                       Options                       |       Padding       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

其中的"Header Checksum"域即为头校验和部分。当要计算IPv4分组头校验和时,发送方先将其置为全0,然后按16位逐一累加至IPv4分组头结束,累加和保存于一个32位的数值中。如果总的字节数为奇数,则最后一个字节单独相加。累加完毕将结果中高16位再加到低16位上,重复这一过程直到高16位为全0。下面用实际截获的IPv4分组来演示整个计算过程:

 

0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00 
0x0010: 00 1c 74 68 00 00 80 11 59 8f c0 a8 64 01 ab 46 
0x0020: 9c e9 0f 3a 04 05 00 08 7f c5 00 00 00 00 00 00 
0x0030: 00 00 00 00 00 00 00 00 00 00 00 00

 

在上面的16进制采样中,起始为Ethernet帧的开头。IPv4分组头从地址偏移量0x000e开始,第一个字节为0x45,最后一个字节为0xe9。根据以上的算法描述,我们可以作如下计算:

 

(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 +
       0x0000 + 0xc0a8 + 0x6401 + 0xab46 + 0x9ce9 = 0x3a66d
(2) 0xa66d + 0x3 = 0xa670
(3) 0xffff - 0xa670 = 0x598f

 

注意在第一步我们用0x0000设置头校验和部分。可以看出这一分组头的校验和与收到的值完全一致。以上的过程仅用于发送方计算初始的校验和,实际中对于中间转发的路由器和最终接收方,可将收到的IPv4分组头校验和部分直接按同样算法相加,如果结果为0xffff,则校验正确。

 

如何编写产生IPv4头校验和的C程序?RFC1071(Computing the Internet Checksum)给出了一个参考实现 :

 

{

           /* Compute Internet Checksum for "count" bytes

            *         beginning at location "addr".

            */

       register long sum = 0;

 

        while( count > 1 )  {

           /*  This is the inner loop */

               sum += * (unsigned short) addr++;

               count -= 2;

       }

 

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

       if( count > 0 )

               sum += * (unsigned char *) addr;

 

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

       while (sum>>16)

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

 

       checksum = ~sum;

   }

 

对于TCP和UDP的数据报,其头部也包含16位的校验和,校验算法与IPv4分组头完全一致,但参与校验的数据不同。这时校验和不仅包含整个TCP/UDP数据报,还覆盖了一个虚头部。虚头部的定义如下:

 

                     0         7 8        15 16       23 24       31 
                    +--------+--------+--------+--------+
                    |             source address              |
                       +--------+--------+--------+--------+
                    |           destination address           |
                    +--------+--------+--------+--------+
                    |  zero  |protocol| TCP/UDP length  |
                    +--------+--------+--------+--------+

 

其中有IP源地址,IP目的地址,协议号(TCP:6/UDP:17)及TCP或UDP数据报的总长度(头部+数据)。将虚头部加入校验的目的,是为了再次核对数据报是否到达正确的目的地,并防止IP欺骗攻击(spoofing)。

 

///////////////////////////

UDP校验方法:
UDP的CHECKSUM算法与[wiki]IP[/wiki]包的HEADER CHECKSUM的计算方法基本一样,只是取样数据不同。
UDP中,参与计算CHEKCSUM的数据包括三部分: 亚头部+UDP头部+数据部分
亚头部包括:2byte源IP地址+2byte目的IP地址+0x00+1byte[wiki]协议[/wiki]+2byte的UDP长度
UDP包头:2byte源端口+2byte目的端口+2byteUDP包长+0x0000(checksum)
数据部分

计算方法,以2字节为一个单位,将其顺序相加,就会产生2个字节的SUM,如果超过2字节,则将高位的值再加回低位,然后取补,得到的就是UDP的checksum


阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页