一文读懂网络报问中的检验和(checksum)—— 原理+举例+代码

Posted by 刘知安 on 2019-03-13
文章目录
  1. 如何求解网络报文中的checksum?
    1. 什么是检验和?
      1. 算法归纳
        1. example
    2. IP报文各字段
      1. IP首部检验和示例
  • 发送端
  • 接收端
    1. Python实现
  • Reference
  • 如何求解网络报文中的checksum?

    最近在学习计算机网络,在运输层和网络层部分存在各种协议TCP、UDP、IP、ICMP等等,而在这些报文中都存在一个公共的字段——检验和(checksum)。接下来,我将从什么是检验和、检验和怎么算、检验和计算示例、python代码计算检验和这几个部分详细介绍。

    什么是检验和?

    检验和是存在于各种报文中的一个字段,它存在的目的是验证报文在网络传输过程中的完整性(有的数据可能在链路传输时发生0-1数据翻转,从而导致报文出错)。因此,在报文的发送端,会根据报文中的首部或数据来计算一个检验和(IP报文的检验和只对首部进行计算,ICMP报文对报文首部和数据都进行计算),然后一旦接收端接受到相应报文,接收端也会对报文的首部或数据进行一次检验和计算,如果接收端算出来的检验和和发送端发送的不一样,那么对不起,接收端认为报文在传输过程中出了错,于是就丢掉该报文。

    算法归纳

    待检验部分从头开始,每16比特进行一次加法计算(如果最后有8位剩余,最后加上这8位),这样最终计算出来的和进行一次反码运算,就是检验和。
    注意:如果求和过程中遇到了任何溢出,都进行回卷(即加回到最低位)

    example

    在这里插入图片描述

    IP报文各字段

    看完上面还迷迷糊糊的?没关系,在这里,就以IP报文为例,介绍更多的细节。首先,还是先把IP报文的各个字段信息回顾一下,如下图。如果你看到这个,有些字段忘记了它的意思,就百度回忆一下吧,这里我就不多说了。
    在这里插入图片描述

    IP首部检验和示例

    现在假设有一个IP报文的首部如下所示(都是16进制数):
    4500 003c 1c46 4000 4006 b1e6 ac10 0a63 ac10 0a0c

    ok,我们将这个报文的各个字段一一来匹配一下

    • 45 —— 对应IP首部前8位,4是version字段,表示IPV4,5是首部长度字段,但注意,首部长度是以每4个字节为1个单位的,所以这里就是5*4=20个字节 (这也说明IP报文首部的option字段和padding字段没用上)
    • 00 —— 对应服务类型(TOS,type of service)字段,00表明是正常操作
    • 003C —— 对应total length字段,说明改IP报文首部加数据段一共是60字节(也就是说,数据段部分占了40字节)
    • 1C46 —— 对应报文标识符字段
    • 4000 —— 对应flags和fragment offset字段,其中flag字段占3位,分片偏移占13位
    • 40 —— 对应TTL字段(Time to live),表明该报文可以经过40跳(hops)
    • 06 —— 对应IP报文封装的上层协议代码,这里是6,表明是TCP报文
    • b1e6 —— 这就是发送端即将求出来的检验和
    • ac10 0a63 —— 对应IP报文的源IP地址
    • ac10 0a0c —— 对应IP报文的目的IP地址

    发送端

    Alright ! 终于可以开始计算了。 : )

    说明:在计算检验和字段之前,我们先把检验和的16位全部置0。

    我们现在把16进制数全部转为二进制,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    4500 -> 0100010100000000
    003c -> 0000000000111100
    1c46 -> 0001110001000110
    4000 -> 0100000000000000
    4006 -> 0100000000000110
    0000 -> 0000000000000000 // 先全部置零,最后再把算出来的结果附加上
    ac10 -> 1010110000010000
    0a63 -> 0000101001100011
    ac10 -> 1010110000010000
    0a0c -> 0000101000001100

    然后我们每16位进行一次加法运算:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    4500 -> 0100010100000000
    003c -> 0000000000111100
    453C -> 0100010100111100 // 第一次计算结果

    453C -> 0100010100111100 // 第一次计算结果 加 后16位
    1c46 -> 0001110001000110
    6182 -> 0110000110000010 // 第二次计算结果

    6182 -> 0110000110000010 // 第二次计算结果 加 后16位
    4000 -> 0100000000000000
    A182 -> 1010000110000010 // 第三次计算结果

    A182 -> 1010000110000010 // 第三次计算结果 加 后16位
    4006 -> 0100000000000110
    E188 -> 1110000110001000

    E188 -> 1110000110001000
    AC10 -> 1010110000010000
    18D98 -> 11000110110011000 // 这里产生了一次溢出,根据回卷规则,把溢出位加到最后

    18D98 -> 11000110110011000
    8D99 -> 1000110110011001 // 进行类似16位加法。。。就不再重述了

    8D99 -> 1000110110011001
    0A63 -> 0000101001100011
    97FC -> 1001011111111100

    97FC -> 1001011111111100
    AC10 -> 1010110000010000
    1440C -> 10100010000001100 // 由产生了进位,继续回卷

    1440C -> 10100010000001100
    440D -> 0100010000001101

    440D -> 0100010000001101
    0A0C -> 0000101000001100
    4E19 -> 0100111000011001

    Well,终于加完了,别急还没完!!!还要求一次反码,别忘了哈~~

    1
    2
    4E19 -> 0100111000011001
    B1E6 ->1011000111100110 // 检验和

    好了,这样我们再把b1e6这个检验和放进到IP报文的checksum字段,检验的工作也就此完成。

    接收端

    接收端就比较简单了,把所有的二进制位每16位进行一次加法,最后求一次补码(同样也要溢出回卷),如果结果全部是1,那就稳了,没出错;只要有一位是0,那就说明出现了错误。

    Python实现

    看完以上,你肯定懂了是怎么算的,但是代码实现可能还有点困难,这里特别感谢JamesF. Kurose ——《计算机网络:自顶向下方法》的作者在书中附带的资源和代码,以下代码来自书中编程实验的ICMPping程序。

    具体的代码解析我注释好了,大家可以看看。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    def checksum(str):
    csum = 0 # 校验和 (一个32位十进制数,因为每16位相加时可能会产生进位(即溢出),这些溢出将会被回卷)
    # 奇偶控制,如果总长的字节数为奇数时,肯定最后一个字节要单独相加(求校验和时是每16位一加)
    countTo = (len(str) // 2) * 2
    count = 0
    while count < countTo:
    # ord()函数返回一个字符的ASCII码
    # 取两个字节,第二个字节放在16位的高位,第一个字节放在16位的地位
    thisVal = (str[count + 1] << 8) + str[count]
    csum = csum + thisVal
    # 这里和0xffffffff进行and运算主要是为了保留每次运算过程中可能出现的16位溢出,
    # 这样一来,就可以将溢出位(也就是进位)保存到sum的高16位
    csum = csum & 0xffffffff
    count = count + 2 # 后移两个字节,也就是准备求和下一个16位

    # 如果真的有一个字节剩余
    if countTo < len(str):
    csum = csum + str[len(str) - 1].decode()
    csum = csum & 0xffffffff

    # 把csum的高16位溢出回卷,加到低16位上
    csum = (csum >> 16) + (csum & 0xffff)
    # 如果还产生了溢出,再操作一次
    csum = csum + (csum >> 16)
    # 求反码
    answer = ~csum
    answer = answer & 0xffff
    # 这里进行字节序大小端转换,因为网络字节序是大端模式
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer

    Reference