如何求解网络报文中的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 | 4500 -> 0100010100000000 |
然后我们每16位进行一次加法运算:
1 | 4500 -> 0100010100000000 |
Well,终于加完了,别急还没完!!!还要求一次反码,别忘了哈~~
1 | 4E19 -> 0100111000011001 |
好了,这样我们再把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
30def 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
- RFC 1071
- Computer Network A Top-Down Approach:JamesF. Kurose
- How to Calculate IP Header Checksum (With an Example)