对UDP校验和的理解_udp 数据包 校验和 checksum=0-程序员宅基地

技术标签:   

很多文章对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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weiweiliulu/article/details/17923043

智能推荐

阿里面试:MySQL索引凭什么能让查询效率提高这么多?_什么公司要求mysql效率-程序员宅基地

文章浏览阅读1.7w次,点赞185次,收藏501次。面试官一定要让我从计算机底层开始说...._什么公司要求mysql效率

模拟实现memmove-程序员宅基地

文章浏览阅读268次,点赞12次,收藏3次。memmove函数的作用与memcpy的作用相似都是将内存复制到另一个区域但是他们的区别在于memmove函数可以进行同内存复制,但是memcpy函数不可以(在vs环境下也可以)

java web 题_javaweb试题带答案-程序员宅基地

文章浏览阅读1.4k次。《javaweb试题带答案》由会员分享,可在线阅读,更多相关《javaweb试题带答案(8页珍藏版)》请在金锄头文库上搜索。1、Javaweb试题一、选择题1二、问答题5一、 选择题没有注明则为单项。1.下面哪个不是Form的元素? ( D )A. Input B: textarea C: select D: table2. HTML页面中,下面哪个表示空格( B )A.& B. C. D..._javaweb程序阅读题

模块““react-router-dom“”没有导出的成员“useHistory”_模块“"react-router-dom"”没有导出的成员“usehistory”-程序员宅基地

文章浏览阅读1.9k次。useNavigate 是一个 React Router v6 中的新 Hook,用于在 React 组件中进行路由的导航。这个函数可以接受一个字符串参数,表示要导航到的路径,还可以接受一个可选的对象参数,表示导航的其他选项,例如替换当前路由、在历史记录中前进或后退等。注:useNavigate 只能在 React Router v6 中使用,如果你的项目中使用的是 React Router v5 或更早的版本,你需要继续使用 useHistory。_模块“"react-router-dom"”没有导出的成员“usehistory”

酒店宾馆在线订房小程序源码系统+商城点餐+会员卡等功能 附带完整的搭建教程_酒店订房源码-程序员宅基地

文章浏览阅读422次,点赞11次,收藏6次。无论是出差还是旅游,通过在线预订平台,用户可以方便快捷地预订到满意的酒店。因此,开发一款集合酒店预订、商城点餐和会员卡功能于一体的在线订房小程序源码系统,可以更好地服务用户,提高用户体验。在线订房小程序还集成了商城点餐功能,用户可以在小程序中直接点餐。同时,会员卡还可以根据用户的消费习惯和偏好推荐相应的产品和服务,提高用户的满意度和忠诚度。通过在线订房小程序,用户可以方便快捷地搜索到心仪的酒店,并在线预订房间。同时,用户还可以根据价格、地理位置、评分等多维度筛选酒店,提高预订效率。1.酒店宾馆在线订房。_酒店订房源码

java 修改源码_再谈给应用程序diy启动画面和java源代码补丁修改-程序员宅基地

文章浏览阅读96次。再谈给应用程序diy启动画面和java源代码补丁修改2006-8-21 16:186365再谈给应用程序diy启动画面和java源代码补丁修改2006-8-21 16:186365搞diy的朋友经常把自己设计的logo或者是打点广告窗口加进别人的应用程序作为启动画面.偶也搞了小例子给大家,并且编译了一个小java补丁小工具一下就搞定(只限本程序,程序异常概无负责任)现在偶把看雪论坛的一个logo加..._java如何更改补丁内容

随便推点

el-table-column循环生成时,在里面使用v-if问题_el-table-column v-if-程序员宅基地

文章浏览阅读2.2k次。el-table-column循环生成时,在里面使用v-if问题_el-table-column v-if

基于java的SCADA系统_基于普通PC的SCADA系统-程序员宅基地

文章浏览阅读1.1k次。1、普通pc运行scada系统和西门子plc通讯,对于wincc来说直接用内置的通讯驱动即可,对于第三方的scada软件(例如ifix)则可以通过西门子的simaticnet软件配置opcserver来实现通讯(当然ifix也可以走其它通讯驱动软件,例如s7A等等)2、博途的wincc和经典wincc目前是两种软件,功能方面有一定差异,但对于简易功能的系统,两者皆可。本回答有3位钻石用户推荐回答..._java scada

一次bash: docker: command not found bash: yum: command not found的解决_-bash: docker: command not found-程序员宅基地

文章浏览阅读9.2k次,点赞11次,收藏8次。解决bash: docker: command not found_-bash: docker: command not found

Duilib Label显示的文本实现自动换行_duilib富文本换行-程序员宅基地

文章浏览阅读4.2k次。Duilib-自动换行的处理Duilib中自动没有自动换行的属性,如果需要实现自动换行,就需要在代码中计算总字体的长度,根据需要添加‘\n’换行符进行换行。例子--Label显示的文本,自动换行的处理自定义个性化的弹窗提示框,提示文本在Label中显示。布局根据实际需要进行布局,这里说明一下计算文本长度,以及换行中需要注意的地方。具体实现计算文本长度,并换行显..._duilib富文本换行

python中 append() 和 extend() 的用法_python append用法-程序员宅基地

文章浏览阅读3w次,点赞45次,收藏132次。1. 概述python中内置了 append() 以及 extend() 方法,这两个方法功能类似,但是再处理多个利表示,完全不同;用法类似,均是list.appenf(obj) , list.extend(seq).list.appenf(obj)和list.extend(seq)的不同点:项list.appenf(obj)list.extend(seq)参数不相同..._python append用法

python如何下载库_python中如何下载库-程序员宅基地

文章浏览阅读9.2k次。python下载安装库的方法:1、在线安装在cmd窗口直接运行:pip install 包名,如 pip install requests注意:这种方式安装时会自动下载第三方库,安装完成后并不会删除,如需删除请到它的默认下载路径下手动删除;win7的默认路径://AppData可能是隐藏文件夹c:\用户\(你的用户名)\AppData\Local\pip\cache\linux的默认路径:~/.c..._python库下载