网络校验和增量计算详解
适用于 NAT、隧道加密、FPGA 网络加速等场景
1. 背景
在网络设备(如 NAT 网关、VPN 隧道、FPGA 加速卡)中,经常需要修改 IP 地址。修改后,相关的校验和必须更新:
| 校验和类型 | 覆盖范围 | 改 IP 后需要更新吗 |
|---|
| IP Header Checksum | 仅 IP 头(20 字节) | ✅ 必须 |
| UDP Checksum | 伪头部 + UDP 头 + 载荷 | ✅ 必须 |
| TCP Checksum | 伪头部 + TCP 头 + 载荷 | ✅ 必须 |
问题:如果重新遍历整个包来计算校验和,对于大包分片场景效率很低。
解决方案:增量计算——只根据改变的字段更新校验和,无需遍历全包。
2. 反码加法原理
IP/TCP/UDP 校验和使用 16-bit 反码加法(One’s Complement Arithmetic):
2.1 计算规则
1
2
3
4
| 1. 把数据分成 16-bit 块
2. 逐块相加(32-bit 累加)
3. 进位回卷:高 16-bit 加回低 16-bit
4. 最后取反(~)得到校验和
|
2.2 计算示例
1
2
3
4
5
6
7
8
9
10
11
12
| 计算 0x1234 + 0xFEDC 的反码和:
0x1234
+ 0xFEDC
─────────
0x11110 ← 产生进位(超过 16-bit)
进位回卷:
0x1110 + 0x0001 = 0x1111
取反得校验和:
~0x1111 = 0xEEEE
|
2.3 关键性质
反码加法有一个重要性质:可逆且结合
1
2
3
4
5
6
7
8
9
| 如果:Checksum = ~(A + B + C + D)
那么:~Checksum = A + B + C + D(中间和)
改变 A → A' 后:
新的中间和 = A' + B + C + D
= (A + B + C + D) - A + A'
= ~Checksum - A + A'
= ~Checksum + ~A + A' (反码中 -X = ~X + 进位调整)
|
3. 增量计算公式
3.1 正向增量(加密端 / NAT 出向)
场景:将 IP 地址从 OLD_IP 改为 NEW_IP
1
2
3
4
5
6
| 公式:
S = ~C_old // Step 1: 取反旧校验和
S = S + ~OLD_IP[31:16] + ~OLD_IP[15:0] // Step 2: 减去旧值
S = S + NEW_IP[31:16] + NEW_IP[15:0] // Step 3: 加上新值
S = fold(S) // Step 4: 进位回卷
C_new = ~S // Step 5: 取反得新校验和
|
进位回卷函数:
1
| fold(S) = (S[15:0] + S[31:16]),重复直到 S ≤ 0xFFFF
|
3.2 反向增量(解密端 / NAT 入向)
场景:将 IP 地址从 NEW_IP 改回 OLD_IP(与正向完全对称)
1
2
3
4
5
6
| 公式:
S = ~C_encrypted // Step 1: 取反加密端的校验和
S = S + ~NEW_IP[31:16] + ~NEW_IP[15:0] // Step 2: 减去新值
S = S + OLD_IP[31:16] + OLD_IP[15:0] // Step 3: 加上旧值
S = fold(S) // Step 4: 进位回卷
C_original = ~S // Step 5: 取反得原始校验和
|
注意:反向增量和正向增量是完全对称的操作,执行后可恢复原始校验和。
4. 完整计算示例
4.1 正向增量示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| 原始数据:
Src IP = 192.168.1.100 = 0xC0A8_0164
UDP Checksum = 0x1234
修改后:
Src IP = 172.16.0.50 = 0xAC10_0032
计算过程:
Step 1: S = ~0x1234 = 0xEDCB
Step 2: 减去旧 IP(加反码)
S = 0xEDCB + ~0xC0A8 + ~0x0164
= 0xEDCB + 0x3F57 + 0xFE9B
= 0x2CBBD
回卷: 0xCBBD + 0x0002 = 0xCBBF
Step 3: 加上新 IP
S = 0xCBBF + 0xAC10 + 0x0032
= 0x17801
回卷: 0x7801 + 0x0001 = 0x7802
Step 4: 新校验和
C_new = ~0x7802 = 0x87FD
|
4.2 反向增量示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| 加密端数据:
Src IP = 172.16.0.50 = 0xAC10_0032
UDP Checksum = 0x87FD(加密端计算的结果)
还原后:
Src IP = 192.168.1.100 = 0xC0A8_0164
计算过程:
Step 1: S = ~0x87FD = 0x7802
Step 2: 减去新 IP
S = 0x7802 + ~0xAC10 + ~0x0032
= 0x7802 + 0x53EF + 0xFFCD
= 0x1C9BE
回卷: 0xC9BE + 0x0001 = 0xC9BF
Step 3: 加上旧 IP
S = 0xC9BF + 0xC0A8 + 0x0164
= 0x18BCB
回卷: 0x8BCB + 0x0001 = 0x8BCC
(继续检查,不需要再回卷)
Step 4: 原始校验和
C_original = ~0xEDCB = 0x1234 ✓
|
结果与原始校验和一致!
5. FPGA Verilog 实现
5.1 增量更新模块
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
| module checksum_incremental_update (
input wire [15 :0] i_old_checksum ,
input wire [31 :0] i_old_ip ,
input wire [31 :0] i_new_ip ,
output wire [15 :0] o_new_checksum
);
// 中间累加和
wire [31 :0] w_sum ;
// 进位折叠
wire [16 :0] w_fold_1 ;
wire [16 :0] w_fold_2 ;
// Step 1-3: 取反 + 减旧 + 加新
assign w_sum = {16'h0, ~i_old_checksum}
+ {16'h0, ~i_old_ip[31:16]}
+ {16'h0, ~i_old_ip[15:0]}
+ {16'h0, i_new_ip[31:16]}
+ {16'h0, i_new_ip[15:0]} ;
// Step 4: 进位回卷(两次确保完全折叠)
assign w_fold_1 = {1'b0, w_sum[15:0]} + {1'b0, w_sum[31:16]} ;
assign w_fold_2 = {1'b0, w_fold_1[15:0]} + {15'h0, w_fold_1[16]} ;
// Step 5: 取反得校验和
assign o_new_checksum = ~w_fold_2[15:0] ;
endmodule
|
5.2 同时更新 Src IP 和 Dst IP
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
30
31
32
33
34
35
36
| module checksum_update_dual_ip (
input wire [15 :0] i_old_checksum ,
input wire [31 :0] i_old_src_ip ,
input wire [31 :0] i_old_dst_ip ,
input wire [31 :0] i_new_src_ip ,
input wire [31 :0] i_new_dst_ip ,
output wire [15 :0] o_new_checksum
);
wire [31 :0] w_sum ;
wire [16 :0] w_fold_1 ;
wire [16 :0] w_fold_2 ;
wire [16 :0] w_fold_3 ;
assign w_sum = {16'h0, ~i_old_checksum}
// 减旧 Src IP
+ {16'h0, ~i_old_src_ip[31:16]}
+ {16'h0, ~i_old_src_ip[15:0]}
// 减旧 Dst IP
+ {16'h0, ~i_old_dst_ip[31:16]}
+ {16'h0, ~i_old_dst_ip[15:0]}
// 加新 Src IP
+ {16'h0, i_new_src_ip[31:16]}
+ {16'h0, i_new_src_ip[15:0]}
// 加新 Dst IP
+ {16'h0, i_new_dst_ip[31:16]}
+ {16'h0, i_new_dst_ip[15:0]} ;
// 多次折叠确保溢出处理
assign w_fold_1 = {1'b0, w_sum[15:0]} + {1'b0, w_sum[31:16]} ;
assign w_fold_2 = {1'b0, w_fold_1[15:0]} + {15'h0, w_fold_1[16]} ;
assign w_fold_3 = {1'b0, w_fold_2[15:0]} + {15'h0, w_fold_2[16]} ;
assign o_new_checksum = ~w_fold_3[15:0] ;
endmodule
|
6. 应用场景
6.1 NAT 网关
1
2
3
4
5
6
7
8
9
| 出向(LAN → WAN):
改 Src IP: 192.168.1.x → 公网 IP
改 Src Port(可选)
正向增量更新 UDP/TCP Checksum
入向(WAN → LAN):
改 Dst IP: 公网 IP → 192.168.1.x
改 Dst Port(可选)
正向增量更新(或理解为"反向还原")
|
6.2 VPN 隧道加密设备
1
2
3
4
5
6
7
8
9
| 加密端:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 收到原始包 │ ──► │ 改 IP 地址 │ ──► │ 更新校验和 │ ──► 加密
└─────────────┘ └─────────────┘ └─────────────┘
解密端:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 解密密文 │ ──► │ 还原 IP 地址│ ──► │ 反向更新校验│ ──► 原始包
└─────────────┘ └─────────────┘ └─────────────┘
|
6.3 处理顺序
加密端:
- 修改 IP 地址
- 增量更新 IP Header Checksum(仅覆盖 IP 头)
- 增量更新 UDP/TCP Checksum(伪头部含 IP)
- 加密 IP 载荷
解密端:
- 解密 IP 载荷
- 还原 IP 地址
- 反向更新 IP Header Checksum
- 反向更新 UDP/TCP Checksum
7. 特殊情况处理
7.1 UDP 校验和为 0
在 IPv4 中,UDP Checksum = 0 表示"不校验"。但通过计算可能得到 0xFFFF(取反后为 0x0000),需要特殊处理:
1
2
| // 计算结果为 0 时,用 0xFFFF 表示
assign o_udp_checksum = (w_result == 16'h0000) ? 16'hFFFF : w_result ;
|
7.2 IP 分片
- 第一个分片:有 UDP/TCP 头,需要更新对应校验和
- 后续分片:无 UDP/TCP 头,只更新 IP Header Checksum
1
2
3
| 分片 1: [IP Hdr][UDP Hdr][Payload...] ← 更新 IP Cksum + UDP Cksum
分片 2: [IP Hdr][Payload...] ← 只更新 IP Cksum
分片 3: [IP Hdr][Payload...] ← 只更新 IP Cksum
|
8. 性能对比
| 方法 | 时间复杂度 | FPGA 资源 | 延迟 |
|---|
| 完整计算 | O(N),N=包长 | 需缓存全包 | 高 |
| 增量计算 | O(1) | 几个加法器 | 极低(1-2 拍) |
结论:增量计算是 FPGA 网络处理的标准做法,性能远优于完整计算。
9. 总结
| 项目 | 正向增量 | 反向增量 |
|---|
| 场景 | 修改字段后更新校验和 | 还原字段后恢复校验和 |
| 公式 | ~(~C + ~OLD + NEW) | ~(~C + ~NEW + OLD) |
| 对称性 | 正向和反向完全对称 | 执行后恢复原值 |
| 实现 | 纯组合逻辑 | 纯组合逻辑 |
掌握增量计算,是 FPGA 网络处理的基本功!