乐趣区

翻译惹的祸-IPICMPTCP和UDP包的校验和的那些事

一、网络协议校验和定义

RFC 1071 中说到校验和的定义:

(1) Adjacent octets to be checksummed are paired to form 16-bit integers, and the 1’s complement sum of these 16-bit integers is formed.
(2) To generate a checksum, the checksum field itself is cleared, the 16-bit 1’s complement sum is computed over the octets concerned, and the 1’s complement of this sum is placed in the checksum field.
(3) To check a checksum, the 1’s complement sum is computed over the same set of octets, including the checksum field. If the result is all 1 bits (-0 in 1’s complement arithmetic), the check succeeds.

IP 校验和是报头中所有 16 位字的补码之和的 16 个位的补码。
许多人可能会问的一个问题是“1 的补码和是多少?”。这是因为所有计算机都使用‘2 的补码’表示,而不使用‘1 的补码’。以下简要介绍。

那么什么是 1 的补码(ones’s complement sum)、什么是 2 的补码(two’s complement sum):

1.1 什么是 1 的补码(反码、ones’s complement sum、1’s complement sum)

1 的补码(ones’s complement sum): 也称为 反码。之所以称为“反码”,是因为反码表示的负数,在形式上可以看做对其所对应正数(其相反数)的“按位求反”(将所有 0 换为 1,1 换为 0),例如,十进制数 6,用 8 位二进制数表示为 00000110,则 - 6 表示为 11111001。

这一名称十分直观地体现了反码的外貌,通俗易懂。但是“1 的补码”这个名字就有点让人摸不着头脑了,完全不明白是什么意思。其实,这是翻译的锅。反码的英文叫做 Ones’ complement(注意不是 One’s complement),意即“(复数个)一的补”。由于汉语中名词不能通过词形变化来体现单复数,我们无法通过译名来体会这个名字的含义。仍取上面的例子,我们将反码的 + 6 与 - 6 按二进制加法相加,发现得到了“一串一”。

仔细想想可以发现,任意一对相反数的反码的“和”都是一串一,所以我们可以这样定义负数 N 的 n 位二进制反码:

这便是反码叫做“(复数个)一的补”的原因。

1.2 什么是 2 的补码(反码、two’s complement sum、2’s complement sum)

然后你是不是以为“二的补”就是这样:

并不!二进制数中不会出现“2”。事实上,“2 的补码”,英文是 Two’s complement(而不是 Twos’ complement),所以它是“(一个)2 的补码”,与“1 的补码”其实不是同一个意思。负数的补码的定义如下:

这里的“two”就是指上式中的“2”。

总结:

“1 的补码”是指“以一串一为模求补”“2 的补码”是指“以二的 n 次幂为模求补”

1.3 举个栗子

2 的补码定点整数(8-bit)

Binary Decimal Hex
00000000 0 00
00000001 1 01
00000010 2 02
00000011 3 03
11111111 -1 FF
11111110 -2 FE
11111101 -3 FD

两个整数相加:
-3 + 5 = 2
FD + 05 = 01 02
丢弃进位(01)会得到正确的结果。

1 的补码定点整数(8-bit)

Binary Decimal Hex
00000000 0 00
00000001 1 01
00000010 2 02
00000011 3 03
11111111 -0 FF
11111110 -1 FE
11111101 -2 FD
11111100 -3 FC

同样的两个数相加:
-3 + 5 = 2
FC + 05 = 01 01
将进位(01)相加到低位(01)会得到正确的结果:
01 + 01 = 02

因此,1 的补码和是通过对数字求和并在结果中加上一个或多个进位来完成的。

一个简单的例子

假设我们有一个使用 2 的补码的 8 位机器并且发送一个数据包:
FE 05 00
00 是校验和字段。

让我们计算和校验网络校验和,这个普通的相加得到的结果:
FE + 05  =  01 03

1 的补码和要求将进位 (01) 加到结果里:
03 + 01 = 04 

所以 FE + 05 的 1 的补码和是 04。

1 的补码的 1 的补码和就是:
~04  = FB

数据包将会是:
FE 05 FB 

现在,在接收端我们将收到的字节相加,包括校验和:
FE + 05 + FB  = 01 FE 

这 1 的补码和是:
FE + 01 = FF = -0 

校验和为 - 0 说明是 ok 的。

另一个复杂一点的例子(32 位机器):

伪数据包:
01 00 F2 03 F4 F5 F6 F7 00 00
(00 00 是校验和字段)

按 16 位分组:
0100 F203 F4F5 F6F7

计算求和:
0100 + F203 + F4F5 + F6F7 = 0002 DEEF (值存在 32 位内存)

将进位(0002)加到结果里,得到 1 的补码和:
DEEF + 002 = DEF1

计算 1 的补码和的 1 的补码(Calculate 1’s complement of the 1’s complement sum):
~DEF1 = 210E

包含校验和(21 0E)的数据包:
01 00 F2 03 F4 F5 F6 F7 21 0E

在接收端:
0100 + F203 + F4F5 + F6F7 + 210E = 0002 FFFD
FFFD + 0002 = FFFF

校验结果为 FFFF,说明校验通过。

1.4 总结

在 2 的补码机上使用 1 的补码加法可能看起来很笼统。但是,这种方法有其自身的优点。
可能最重要的是它是字节序独立的。小端字节序计算机将 LSB(Least Significant Bit– 最低有效位)放在最后(例如 Intel 处理器)。大端字节序计算机将 LSB 放在首位(例如 IBM 大型机)。当将进位加到 LSB 上以形成 1 的补码和时(请参见示例),我们加 03 + 01 还是 01 + 03 都没关系。结果是相同的。

1、LSB(Least Significant Bit)– 最低有效位
LSB 代表二进制中最小的单位,可以用来指示数字很小的变化。也就是说,LSB 是一个二进制数字中的第 0 位(即最低位),具有权值为 2^0,可以用来检测数的奇偶性。
2、MSB(Most Significant Bit)– 最高有效位
MSB 代表一个 n 位二进制数字中的 n - 1 位,具有最高的权值 2^(n-1). 对于有符号的二进制数,负数采用反码或补码形式,此时 MSB 用来表示符号,msb 为 1 表示负数,0 表示正数。

其他好处包括易于检查传输和校验和计算,以及通过仅更新已更改的 IP 协议的字段来加快计算速度的多种方法。

二、ICMP 协议与校验和

2.1 ICMP 协议

wiki 对 ICMP 协议的介绍

三、ICMP 实现[rust 版本]

未完待续

参考:
1、《Short description of the Internet checksum》
2、《反码,补码为什么又叫“一的补”,“二的补”?》
3、RFC 1071
4、

退出移动版