ip、tcp、udp 首部校验和算法的 Python 实现
ip 首部校验和、tcp/udp 首部校验和能够判断网络传输中是否出现丢包、错包等。下面分别介绍它们的计算方法和 python 实现。
ip 首部校验和
ip 首部校验和是针对 ip 数据包的包头20字节部分进行校验。假设我们有如下20字节的 ip 包头数据(16进制格式):
1 | 45 00 00 30 80 4c 40 00 80 06 b5 2e d3 43 11 7b cb 51 15 3d |
- 45:4表示 ip 版本号,这里是 ipv4,5表示首部数据长度,为5个32比特,即20字节;
- 00 30: 表示 ip 包总长度为 0x0030=48字节,减去头部20字节,数据部分为28字节;
- 80 4c:表示标识符;
- 40 00:3比特标志和13比特片偏移量;
- 80 06:80表示ttl值0x80=128,06表示 tcp 协议(0x11=17表示udp协议);
- b5 2e:表示 ip 首部校验和;
- d3 43 11 7b:表示源地址0xd3, 0x43, 0x11, 0x7b = (211, 67, 17, 123);
- cb 51 15 3d:表示目的地址0xcb, 0x51, 0x15, 0x3d = (203, 81, 21, 61);
ip 首部校验和算法
计算 ip 首部校验和,
将 ip 包头20字节的 ip 首部校验和2字节替换为 00 00,替换后如下所示:
yaml1
45 00 00 30 80 4c 40 00 80 06 00 00 d3 43 11 7b cb 51 15 3d
对20字节进行两字节以大端方式累加求和:
python1
0x4500 + 0x0030 + 0x804c + 0x4000 + 0x8006 + 0x0000 + 0xd343 + 0x117b + 0xcb51 + 0x153d = 0x34ace
如果和大于2字节时,则和值中高2字节加上低2字节的值作为最终的和值。注意,如果仍然大于2字节,那么重复该步骤:
python1
0x0003 + 0x4ace = 0x4ad1
对最终的和值求反,即关于2字节的补:
python1
0xffff - 0x4ad1 = 0xb52e
此时,得到的值 0xb52e 就是上面的 ip 首部校验和 b5 2e
ip 首部校验和的 python 实现
1 | from numba import jit |
tcp/udp 首部校验和
tcp 和 udp 的首部校验和算法是一致的,只是数据中的协议标识不同,tcp为0x06,udp为0x11。同时,除了需要对数据添加伪首部和校验和位置不同外,它们的计算算法和 ip 首部校验和是一致的。下面以 udp 为例。
数据格式如下:
1 | udp 伪首部 + udp 首部 + 载荷数据 |
其中:
- udp 伪首部(12字节):4字节源地址 + 4字节目的地址 + 0x00 + 0x11(udp 协议。 tcp 协议取值0x06)+ 2字节 udp 长度;
- udp 首部(8字节):2字节源端口 + 2字节目的端口 + 2字节 udp 长度 + 2字节校验和;
注意:
- udp 数据报的总长度包括 udp 首部 + 载荷数据;
- udp 伪首部加入校验的目的是为了再次核对数据报是否到达正确的目的地,并防止 ip 欺骗攻击 (spoofing);
数据例子:
1 | 45 00 00 30 80 4c 40 00 80 06 00 00 d3 43 11 7b cb 51 15 3d f8 3d 30 39 00 12 23 19 55 aa 68 65 6c 6c 6f 20 55 50 |
- 前面20字节为 ip 包头数据;
- d3 43 11 7b:为 ip 源地址;
- cb 51 15 3d:为 ip 目的地址;
- f8 3d:为源端口;
- 30 30: 为目的端口;
- 00 12:为 udp 包长度,0x0012 = 18,表示 udp 首部 + 载荷数据,从源端口到数据包末尾;
- b1 2d:为 udp 校验和;
- 55 aa 开始到末尾都是载荷数据。
udp 首部校验和算法
udp 首部校验和的算法步骤如下(步骤3、4、5>同ip 首部校验和算法):
根据 ip 数据包构造 udp 伪首部:
yaml1
d3 43 11 7b cb 51 15 3d 00 11 00 12
通过 “udp 伪首部 + udp 首部 + 载荷数据” 构造新数据:
yaml1
d3 43 11 7b cb 51 15 3d 00 11 00 12 f8 3d 30 39 00 12 23 19 55 aa 68 65 6c 6c 6f 20 55 50
把 udp 校验和置为 00 00:
yaml1
d3 43 11 7b cb 51 15 3d 00 11 00 12 f8 3d 30 39 00 12 00 00 55 aa 68 65 6c 6c 6f 20 55 50
把 udp 校验和置为 00 00:
yaml1
0xd343 + 0x117b + 0xcb51 + 0x153d + 0x0011 + 0x0012 + 0xf83d + 0x3039 + 0x0012 + 0x0000 + 0x55aa + 0x6865 + 0x6c6c + 0x6f20 + 0x5550 = 0x4dce2
进行两字节以大端方式累加求和,如果和大于2字节时,则和值中高2字节加上低2字节的值作为最终的和值。注意,如果仍然大于2字节,那么重复该步骤:
python1
0x0004 + 0xdce2 = 0xdce6
对最终的和值求反,即关于2字节的补:
python1
0xffff - 0xdce6 = 0x2319
udp 首部校验和 python 实现
1 | from numba import jit |
简便方案
其实在上面计算 ip 首部校验和时,首先将 ip 首部校验和置位 00 00 的那一步可以省略,只需要在最终计算的结果进行低2、1字节(sum & 0xFFFF)和低4、3字节(sum >> 16)组成的整数相加即可(注意,这里是循环,直到校验和不超过2字节为止),如果值为 0xFFFF,则说明 ip 首部校验和正确。
对于 udp/tcp 校验一样,因为算法与 ip 首部校验相同。正常构造伪 udp,省去首部校验和置为 00 00 的一步,最后计算低2、1字节(sum & 0xFFFF)和低4、3字节(sum >> 16)组成的整数相加即可(注意,这里是循环,直到校验和不超过2字节为止),如果值为 0xFFFF,则说明 udp/tcp 首部校验和正确。