Skip to content

Commit

Permalink
✨ 154 IP 校验和
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenBaby committed Jul 21, 2023
1 parent 0210774 commit 0409c4f
Showing 1 changed file with 106 additions and 0 deletions.
106 changes: 106 additions & 0 deletions docs/14 网络协议/154 IP 校验和.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# IP 校验和

IP 校验和定义在 RFC1071 [^rfc1071]

设需要计算一列字节为 A, B, C, D, ..., Y, Z 的校验和,使用 [a, b] 标识十六位整型 a * 256 + b 或者说 a << 8 + b,十六位反码和可以表示成如下两种形式:

- [A,B] +' [C,D] +' ... +' [Y,Z] [1]

- [A,B] +' [C,D] +' ... +' [Z,0] [2]

其中 +' 表示反码加法,两式分别表示字节数量为偶数和奇数的情况,奇数需要在最后补零,**反码求和** 的结果 **再按位取反** 就是校验和。

## 反码 (1's complement) 加法

补码 (2's complement)

首先需要解释一下定义中的反码加法,反码的表示方式为:

正数就是自身,负数是其正数按位取反,下面是一些例子(以四位为例):

| 正数 | 正二进制 | 负数 | 负二进制 |
| ---- | -------- | ---- | -------- |
| 0 | 0000 | -0 | 1111 |
| 1 | 0001 | -1 | 1110 |
| 2 | 0010 | -2 | 1101 |
| 3 | 0011 | -3 | 1100 |
| 4 | 0100 | -4 | 1011 |
| 5 | 0101 | -5 | 1010 |
| 6 | 0110 | -6 | 1001 |
| 7 | 0111 | -7 | 1000 |

反码的加法需要 **循环进位(End Around Carry)**,下面是一个例子,没有进位就结束了,有进位需要将进位加到最低有效位(Least Significant Bit):

```
7 + (-3) = 4
0111 -> 7
1100 -> -3
-----
10011 -> carry
0011
1
-----
0100 -> 4
```

对于反码机器来说这个进位的过程是自动的,但一般 Intel 的 CPU 是小端补码加法,这个进位的过程需要手动计算;

于是对于补码机器来说 [A,B] +' [C,D] 的运算过程为:

[A,B] +' [C,D] = [A,B] + [C,D] + carry

其中 + 为十六位补码加法,carry 为进位;

## 交换律和结合律

由于程序使用加法,所以很显然具有交换律和结合律,但是,RFC1071 给出了一个拆分的例子,[1] 式可以表示成:

([A,B] +' [C,D] +' ... +' [J,0] ) +' ([0,K] +' ... +' [Y,Z]) [3]

## 字节序无关

校验和的计算与字节序无关,所以一下方式计算结果与 [1] 式相同:

[B,A] +' [D,C] +' ... +' [Z,Y] [4]

对于奇数个字节的情况,需要单独处理最后一个字节,交换过程后就变成了:

[B,A] +' [D,C] +' ... +' [0,Z] [5]

所以 RFC1071 后面的 C 代码 **只适用于小端字节序**

## 并行求和

当机器字长大于 16 bit 是,可以并行计算校验和,这个比较好理解;比如 32 位机器上一次可以计算两个 16bit 的和,然后再拆分成 16bit,需要注意的是进位操作可能需要操作状态字;

## 延迟进位

对于补码机器,每次计算都可能产生进位需要 **循环进位**,实际上可以在溢出之前再做进位,这样可以减少每次进位的判断,或者少做很多次进位加法;

## 增量更新

增量更新在 RFC1141 [^rfc1141],RFC1624 [^rfc1624] 中有更详细的说明。

有时可以避免在更新一个报头字段时,重新计算整个校验和。最著名的例子是网关更改 IP 报头中的 TTL 字段,但还有其他例子(例如,更新源路由时)。在这些情况下,可以在不扫描消息或数据报的情况下更新校验和。

要更新校验和,只需将已更改的 16 位整数的差相加即可。要了解其工作原理,请观察每个 16 位整数都有一个可加的逆,并且加法具有结合律和交换律。由此可以得出,给定原值 m,新值 m' 和旧校验和 C,新校验和 C'为:

C' = C + (-m) + m' = C + (m' - m)

## 循环展开

为了减少循环开销,可以展开内部求和循环,在一次循环中执行多次加法,通常可以提供性能,但是会使程序逻辑变得复杂。

## 校验和结合数据获取

将数据从一个内存位置复制到另一个内存位置涉及到每字节的开销。在这两种情况下,瓶颈本质上是内存总线,也就是说,获取数据的速度有多快。

在某些机器上(特别是相对缓慢和简单的微型计算机),通过将内存到内存复制和校验和结合起来,两者只获取一次数据,可以显著减少开销。

## 参考

[^rfc1071]: <https://datatracker.ietf.org/doc/html/rfc1071>
[^rfc1141]: <https://datatracker.ietf.org/doc/html/rfc1141>
[^rfc1624]: <https://datatracker.ietf.org/doc/html/rfc1624>

0 comments on commit 0409c4f

Please sign in to comment.