对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

智能推荐

蓝桥杯Python组国二经验+备考建议-程序员宅基地

文章浏览阅读1.9w次,点赞141次,收藏834次。楼主参加了2021年的蓝桥杯算法程序竞赛Python组。经过长达半年的训练+比赛时候的一点点运气,最终获得了蓝桥杯Python组省一国二的好成绩。只要好好准备,Python组的省一并没有那么难,我今年10道题中做了7道就拿到了省一等奖。本篇经验适合:1.希望参加Python组的同学2.想参加Java、C++ B组的同学(难度与Python组类似)3.想系统提升算法能力的同学4.ACM大佬可以直接退出了、点击就送总结:1.要总结并背诵基本的算法模板2.需要一定的训练量,但更追求的是写题质量和_蓝桥杯python组

PHP 浮点数计算精度问题_php float 精度-程序员宅基地

文章浏览阅读1.2k次。​ 在计算机中,只有二进制的数据才能被识别和处理。所以无论是哪种编程语言,在什么编译环境下工作,都要先把源程序(编译)转换成二进制的机器码后才能被计算机识别。二进制的方式可以准确表示一个整数,但不能准确表示一个浮点数。和十进制无法精确表示分数的1/3同样,二进制也无法精确表示十进制的小数。我们可以看下面的例子:// 但是对于浮点数来说,二进制并不能完整地表示一个浮点数。// 例如,我们将浮点数 2.4 表示为二进制,此时不能使用 decbin(), bindec()等类似的php系统函数。这里我是用在线_php float 精度

常见的十大运算符及其优先级_运算符优先级-程序员宅基地

文章浏览阅读1.1w次,点赞18次,收藏99次。常见运算符及其优先级_运算符优先级

最长公共子串的问题(正常方法和矩阵法,动态规划)-程序员宅基地

文章浏览阅读1.1k次。这个题我本人看着在网上没有详细的解释,其实你要搞懂一个问题,整体是让你求最长公共子串的长度比较简单,一直双重遍历,比较 最长子串的长度,但是如果最后要你那个最长公共子串难度会有一个提升,首先下面第一种方法我用双重遍历去找一下,找到最长公共子串,找到最长公共子串的关键是用map去储存字符串,这样以len为键一下就找到了最长公共子串。是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。,返回这两个字符串的最长。矩阵法:简单的动态规划。

Android图片压缩框架Tiny学习记录_android tiny-程序员宅基地

文章浏览阅读2.9k次。Android Tiny Compress_android tiny

黑马程序员——Java基础---I/O流(上[异常])_gephi显示java调用目标异常-程序员宅基地

文章浏览阅读512次。——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——- 引言 讲解IO流之前为什么先讲解异常和File类呢? 因为File表示的是IO流将来要操作的文件,所以我们需要学习File类。 而操作文件无非就是上传文件和下载文件,在这个操作的过程中可能出现问题, 出现问题后,我们需要对对应的代码进行处理。所以我们需要学习异常异常。 I/O流操作之上传下载 异_gephi显示java调用目标异常

随便推点

AppsFlyer Unity V6_edm4u-程序员宅基地

文章浏览阅读1.8k次。AppsFlyer Unity Plugin V6踩坑记录Unity plugin V6 插件链接首先粗略讲一下EDM4U(Unity外部依赖管理器)。EDM4U类似于一个插件管理器,通过Android Resolver 和 iOS Resolver来进行库文件的下载、更新、去重等,目前新的facebook,google,appsflyer等插件都自带这两个玩意儿了。iOS开发需要注意一点,没有安装CocoaPods,则需要使用/Assets/External Dependency Manage_edm4u

【JAVA秒会技术之玩转SQL】MySQL优化技术(一)_java mysql语句中,表关联,sql会如何优化-程序员宅基地

文章浏览阅读2.3k次。MySQL优化技术(一) 开发的路上,总会碰到一些老系统,越用越慢。“慢”的原因也许有很多,但是,博主个人觉得,数据库的设计和sql语句写的好坏,对系统效率的影响是最直接,最显而易见的!所以,学习一下MySQL的优化,还是很有必要的。当然,博主能力有限,没那么多经验,更多的是“道听途说”和“纸上谈兵”。如有不正之处,望大神开后给予指正,不胜感激!(一)MySQL优化技术概述_java mysql语句中,表关联,sql会如何优化

ros的l2tp服务端创建与pptp多出口配置-程序员宅基地

文章浏览阅读5.4k次。小二最近为了搭建自已的动态IP池,同时为了让苹果终端也能正常使用,初次引入了ros系统,纯属分享,如果不对之处请大神们多指点。Ros基础网络配置不再描述,前提保证ros本身能正常上网。l2tp服务端的配置l2tp的pool配置点击IP -> Pool,点击“+”,添加VPN ip地址池。Name:比如l2tp-poolAddress:比如10.1.1.1-10.1.1.253 ...

【MySQL进阶之路丨第十篇】一文带你精通MySQL排序、分组、连接_mysql 分组排序-程序员宅基地

文章浏览阅读1w次,点赞55次,收藏46次。MySQL中可以使用ORDER BY语句对查询结果进行排序。ORDER BY语句按照指定的列或表达式对结果进行排序,可以按升序(默认)或降序排列。模板如下:SELECT column1, column2, ...FROM tableORDER BY {{column}} {{order}};将需要排序的列名替换为{{column}},并将排序顺序(ASC或DESC)替换为{{order}}MySQL中可以使用ORDER BY语句对查询结果进行排序。ORDER BY语句按照指定的列或表达式_mysql 分组排序

解决java.net.ConnectException: Connection refused: connect报错_apache. der by.client .am.disconnectexception: jav-程序员宅基地

文章浏览阅读312次。在application.yml配置文件中加入:eureka: client: register-with-eureka: false fetch-registry: false_apache. der by.client .am.disconnectexception: java.net.connectexcetionerror

Golang.org/x库初探2——text库_golang.org/x/text-程序员宅基地

文章浏览阅读1.6k次。golang/x 库下text库详解,提供国际化、编码转换等丰富功能_golang.org/x/text