对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

智能推荐

idea maven部分依赖无法导入_idea 引入项目,部分maven依赖引入不进来-程序员宅基地

文章浏览阅读103次。在网络和maven配置都没有问题的时候,仍然无法导入部分依赖的原因:大概率是所需要的依赖版本与idea版本或者是jdk有冲突,换一个idea版本能适应的版本就可以了。_idea 引入项目,部分maven依赖引入不进来

matlab如何使输出结果更美观(symdisp函数——pretty函数升级版)_matlab pretty-程序员宅基地

文章浏览阅读1.7w次,点赞44次,收藏86次。matlab中有些计算结果比较长,直接查看有些困难,下面介绍pretty和symdisp函数优化输出结果,使结果更为直观。演示示例1有一个计算结果如下:>> f1 f1 = y^5 + (- w - y0)*y^4 + 1800*y^3 + (1498200*w - 1800*y0)*y^2 + (3600*w*y0 + 810000)*y - 1350810000*w - 810000*y0 1. 使用pretty函数美化输出>> pretty(f1) 5_matlab pretty

学习Java必须避开的十大致命雷区,千万不要踩!_学习java的坏处-程序员宅基地

文章浏览阅读305次。Tiobe发布了最新一期(3月)编程语言欢迎度榜单,其榜单根据互联网上开发人员、课程和第三方厂商的数量,并根据使用搜索引擎(如Google、Bing、Yahoo!)以及Wikipedia、Amazon、YouTube统计出排名数据。毫无疑问,老大哥Java 稳居第一。同样都是编程语言,为何java那么优秀?创一个小群,供大家学习交流聊天如果有对学java方面有什么疑惑问题的,或者有什么想说的想..._学习java的坏处

python实现日历功能_Python如何绘制日历图和热力图-程序员宅基地

文章浏览阅读206次。本文以2019年全国各城市的空气质量观测数据为例,利用matplotlib、calmap、pyecharts绘制日历图和热力图。在绘图之前先利用pandas对空气质量数据进行处理。数据处理从网站下载的数据为逐小时数据,每天一个文件。如果要绘制全年的日历图或者热图,首先要将所有的数据进行合并处理。下载好数据之后,将数据解压到当前目录的2019文件夹内,然后处理数据:import globfrom d..._python绘制日历图4-6月份

Git 项目管理快速入门_git项目管理-程序员宅基地

文章浏览阅读451次。Git 项目管理快速入门,当安装完git之后应该做的第一件事就是设置用户名称与邮件地址。因为每一个git的提交都会使用这些信息,并且它会写入到你的每一次提交中,不可更改。TIP- 版本库又名为仓库,英文名repositiry- 可以简单理解为一个目录,这个目录里面的所有文件都可以被git管理起来,每个文件的修改、删除,git都能跟踪,以便任何时刻都可以追踪历史,或者再将某个时刻可以“还原”。_git项目管理

memmove函数详解 看这一篇就够了-C语言(函数讲解、函数实现、使用用法举例、作用、自己实现函数 )-程序员宅基地

文章浏览阅读1.7w次,点赞20次,收藏65次。memmove函数详解 看这一篇就够了-C语言(函数讲解、函数实现、使用用法举例、作用、自己实现函数 )_memmove

随便推点

Python numpy.column_stack函数方法的使用_numpy column_stack-程序员宅基地

文章浏览阅读2.7k次。NumPy(Numerical Python的缩写)是一个开源的Python科学计算库。使用NumPy,就可以很自然地使用数组和矩阵。NumPy包含很多实用的数学函数,涵盖线性代数运算、傅里叶变换和随机数生成等功能。本文主要介绍一下NumPy中column_stack方法的使用。原文地址:Python numpy.column_stack函数方法的使用..._numpy column_stack

mac 免费的方法读写NTFS_mac ntfs读写工具免费-程序员宅基地

文章浏览阅读1.5k次。等到电脑完全关机后,按着开机(指纹)按钮十秒钟以上,等到进入系统的恢复模式,选择你开机启动的硬盘 - 启动安全性实用工具 - 降低安全性 - 选择“允许用户管理来自被认可的开发者的内核拓展”,点击“好”,等待loading完,点击顶栏菜单,找到关机,关闭电脑,然后正常开机。MacBook Pro M1想连个移动硬盘拷贝一点内容,结果发现只能读取,无法写入,上网找了几个免费的NTFS读写软件,例如OMI NTFS的,结果一顿安装后还提示我的硬盘有问题需要修复,点了修复也无济于事……_mac ntfs读写工具免费

react onclick 跳转路由-程序员宅基地

文章浏览阅读566次。/ export时要使用withRouter。react onclick 跳转路由。_react onclick 跳转

应对NBA 2K13启动时提示缺失zlib1.dll文件的全面解决方案-程序员宅基地

文章浏览阅读1k次,点赞21次,收藏20次。在享受NBA 2K13这款经典篮球模拟游戏的过程中,玩家有时会遇到启动时提示“缺少zlib1.dll”文件的困扰。这一错误信息意味着游戏在启动时无法找到或加载名为“zlib1.dll”的关键系统组件,进而导致游戏无法启动或运行异常。为帮助玩家有效地解决这一问题,本文将深入探讨zlib1.dll文件的功能、缺失原因,并提供一套详尽的解决步骤。

Kubernetes入门——Longhorn简介-程序员宅基地

文章浏览阅读3.9k次。Longhorn是由Rancher创建的一款云原生的、轻量级、可靠且易用的开源分布式块存储系统。部署到K8s集群上之后,Longhorn会自动将集群中所有节点上可用的本地存储聚集为存储集群,然后利用这些存储管理分布式、带有复制功能的块存储,支持快照和数据备份。_longhorn

abcde依次进入一个队列_程序员考试复习题-程序员宅基地

文章浏览阅读1.4k次。程序员习题1)经过以下栈运算后,x的值是_____________。InitStack(s); Push(s,a); Push(s,b); Pop(s,x);GetTop(s,x);A. aB. bC. 1D. 02) 经过以下栈运算后,StackEmpty(s)的值是___________。InitStack(s); Push(s,a); Push(s,b); Pop(s,x); Pop(s,y..._经过以下队列运算后,队头的元素是( )。