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 首部校验和,

  1. 将 ip 包头20字节的 ip 首部校验和2字节替换为 00 00,替换后如下所示:

    1
    45 00 00 30 80 4c 40 00 80 06 00 00 d3 43 11 7b cb 51 15 3d
  2. 对20字节进行两字节以大端方式累加求和:

    1
    0x4500 + 0x0030 + 0x804c + 0x4000 + 0x8006 + 0x0000 + 0xd343 + 0x117b + 0xcb51 + 0x153d = 0x34ace
  3. 如果和大于2字节时,则和值中高2字节加上低2字节的值作为最终的和值。注意,如果仍然大于2字节,那么重复该步骤:

    1
    0x0003 + 0x4ace = 0x4ad1
  4. 对最终的和值求反,即关于2字节的补:

    1
    0xffff - 0x4ad1 = 0xb52e

此时,得到的值 0xb52e 就是上面的 ip 首部校验和 b5 2e

ip 首部校验和的 python 实现

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
from numba import jit


@jit(nopython=True)
def ip_check_sum_int(block):
"""
对应算法里的步骤2、3
return: 整数
"""
check_sum = 0
for i in range(0, len(block), 2):
check_sum += (block[i] << 8) + block[i + 1]
# 注意,这里是循环,直到校验和不超过2字节为止
while (check_sum >> 16) != 0:
check_sum = (check_sum >> 16) + (check_sum & 0xFFFF)
return check_sum


def ip_check_sum(block):
"""
对应算法里的步骤1、4
return: 2字节二进制整数,即 ip 首部校验和
"""
if len(block) != 20:
return "ip 首部必须为20字节"
block = block[:10] + bytearray([0x00, 0x00]) + block[12:]
check_sum = 0xFFFF - ip_check_sum_int(block)
return bytearray([check_sum >> 8, check_sum & 0xFF])

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 首部校验和算法):

  1. 根据 ip 数据包构造 udp 伪首部:

    1
    d3 43 11 7b cb 51 15 3d 00 11 00 12
  2. 通过 “udp 伪首部 + udp 首部 + 载荷数据” 构造新数据:

    1
    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
  3. 把 udp 校验和置为 00 00:

    1
    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
  4. 把 udp 校验和置为 00 00:

    1
    0xd343 + 0x117b + 0xcb51 + 0x153d + 0x0011 + 0x0012 + 0xf83d + 0x3039 + 0x0012 + 0x0000 + 0x55aa + 0x6865 + 0x6c6c + 0x6f20 + 0x5550 = 0x4dce2
  5. 进行两字节以大端方式累加求和,如果和大于2字节时,则和值中高2字节加上低2字节的值作为最终的和值。注意,如果仍然大于2字节,那么重复该步骤:

    1
    0x0004 + 0xdce2 = 0xdce6
  6. 对最终的和值求反,即关于2字节的补:

    1
    0xffff - 0xdce6 = 0x2319

udp 首部校验和 python 实现

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
from numba import jit


@jit(nopython=True)
def ip_check_sum_int(block):
"""
对应算法里的步骤5
return: 整数
"""
check_sum = 0
for i in range(0, len(block), 2):
check_sum += (block[i] << 8) + block[i + 1]
# 注意,这里是循环,直到校验和不超过2字节为止
while (check_sum >> 16) != 0:
check_sum = (check_sum >> 16) + (check_sum & 0xFFFF)
return check_sum


def udp_check_sum(block, ptcl = 0x11):
"""
对应算法里的步骤1、2、3、6
ptcl: tcp 时 ptcl = 0x06;ucp 时 ptcl = 0x11
return: 2字节二进制整数,即 udp/tcp 首部校验和
"""

fake_udp_header = block[12:20] + bytearray([0x00, ptcl]) + block[24:26]
new_block = fake_udp_header + block[20:26] + bytearray([0x00, 0x00]) + block[28:]
check_sum = 0xFFFF - ip_check_sum_int(new_block)
return bytearray([check_sum >> 8, check_sum & 0xFF])

简便方案

其实在上面计算 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 首部校验和正确。

参考链接

  1. 理解UDP协议的首部校验和校验和
  2. IP数据报首部checksum的计算