跨平台底层网络库libdnet源码分析系列(九)

admin 2026-03-29 23:46:13 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入分析跨平台网络库libdnet的校验和计算实现,系统讲解IP、TCP、UDP、ICMP、SCTP等协议的校验和算法原理与源码实现。文章详细解析了Internet校验和与CRC32-C算法的设计思想,展示了零拷贝、自动协议识别等架构特点,提供了分片处理、伪头部构造等关键技术的代码示例与常见错误分析。适合网络安全开发者和协议实现人员参考。 综合评分: 90 文章分类: 代码审计,网络安全,应用安全,安全开发,技术标准


cover_image

跨平台底层网络库libdnet源码分析系列(九)

原创

haidragon haidragon

安全狗的自我修养

2026年3月26日 15:32 湖南

官网:http://securitytech.cc

#

源码分析mettle后门工具学习 所使用的依赖库

#

#

校验和计算实现深入分析

目录

  1. 校验和基础概念
  2. libdnet校验和架构设计
  3. IP头部校验和计算
  4. TCP校验和计算
  5. UDP校验和计算
  6. ICMP校验和计算
  7. ICMPv6校验和计算
  8. SCTP校验和计算
  9. IPv6校验和计算
  10. 校验和算法详解
  11. CRC32-C算法
  12. 跨平台实现差异
  13. 性能优化技巧
  14. 常见问题与调试
  15. 实际应用示例

1. 校验和基础概念

1.1 校验和的作用

校验和在网络协议中起着至关重要的作用:

  • 数据完整性验证:确保数据在传输过程中未被篡改
  • 传输错误检测:发现链路层的比特错误
  • 协议兼容性:符合RFC标准的网络协议实现

1.2 网络协议中的校验和类型

| 协议 | 校验和类型 | RFC标准 | 覆盖范围 | | — | — | — | — | | IPv4 | Internet Checksum | RFC 791 | IP头部 | | IPv6 | 无(由上层协议保证) | RFC 2460 | – | | TCP | Internet Checksum | RFC 793 | 伪头部+TCP头部+数据 | | UDP | Internet Checksum | RFC 768 | 伪头部+UDP头部+数据 | | ICMP | Internet Checksum | RFC 792 | ICMP头部+数据 | | ICMPv6 | Internet Checksum | RFC 4443 | 伪头部+ICMPv6头部+数据 | | SCTP | CRC32-C | RFC 4960 | SCTP头部+数据 |

1.3 Internet校验和算法原理

Internet校验和是一种16位反码和校验算法:

  1. 计算过程
  • 将数据视为16位字序列
  • 对所有16位字求和
  • 将进位加到低16位(折叠)
  • 取反码作为最终校验和
  1. 验证过程
  • 对接收的数据(包含校验和字段)执行相同计算
  • 如果结果为0xFFFF,则校验通过
  1. 特点
  • 简单高效
  • 字节序无关
  • 适合硬件实现
  • 对突发错误检测效果好

1.4 CRC32-C算法原理

CRC32-C(Castagnoli)是SCTP协议使用的校验和算法:

  1. 多项式:0x1EDC6F41
  2. 初始值:0xFFFFFFFF
  3. 最终异或:0xFFFFFFFF
  4. 输出转换:字节序翻转

特点:

  • 比Internet校验和更强的错误检测能力
  • 更适合数据完整性保护
  • 现代CPU通常有硬件加速支持(SSE4.2)

2. libdnet校验和架构设计

2.1 核心设计原则

libdnet的校验和计算遵循以下设计原则:

  1. 零拷贝设计:直接在数据包缓冲区上计算
  2. 自动协议识别:根据IP协议字段自动选择校验和算法
  3. 跨平台一致性:所有平台产生相同的校验和结果
  4. 高性能优化:使用循环展开和快速路径
  5. 完整性覆盖:支持所有需要校验和的协议

2.2 核心函数体系

1. include/dnet/ip.h
2. ├── ip_checksum()[452行]IPv4数据包综合校验和
3. ├── ip_cksum_add()[452行]16位字累加基础函数
4. └── ip_cksum_carry()[453行]进位折叠和取反码

6. include/dnet/tcp.h
7. └── tcp_checksum()[195行]  TCP伪头部校验和

9. include/dnet/icmp.h
10. └── icmp_checksum()[264行]  ICMP校验和

12. include/dnet/ip6.h
13. └── ip6_checksum()[202行]IPv6数据包综合校验和

15. src/ip-util.c
16. ├── ip_checksum()[132行]  IP综合校验和实现
17. ├── tcp_checksum()[102行]  TCP校验和实现
18. ├── udp_checksum()[112行]  UDP校验和实现
19. ├── icmp_checksum()[124行]  ICMP校验和实现
20. └── ip_cksum_add()[185行]核心累加函数

22. src/ip6.c
23. └── ip6_checksum()[18行]IPv6校验和实现

25. src/crc32ct.h
26. └── CRC32C查表和宏定义[1-82行]

2.3 函数调用关系

1. 用户代码
2. │
3. ├─→ ip_checksum()──→ ip_cksum_add()──→ ip_cksum_carry()
4. ││││
5. ││└─────────────────┘
6. ││
7. │├─→ tcp_checksum()──→ ip_cksum_add()──→ ip_cksum_carry()
8. ││
9. │├─→ udp_checksum()──→ ip_cksum_add()──→ ip_cksum_carry()
10. ││
11. │└─→ icmp_checksum()──→ ip_cksum_add()──→ ip_cksum_carry()
12. │
13. └─→ ip6_checksum()
14. │
15. ├─→ TCP: ip_cksum_add()──→ ip_cksum_carry()
16. ├─→ UDP: ip_cksum_add()──→ ip_cksum_carry()
17. └─→ICMPv6: ip_cksum_add()──→ ip_cksum_carry()

2.4 数据结构

1. /* src/ip-util.c:101-109 */
2. void tcp_checksum(struct ip_hdr *ip,struct tcp_hdr *tcp,size_t len)
3. {
4. int sum;
5. tcp->th_sum =0;
6. sum = ip_cksum_add(tcp, len,0)+ htonl(ip->ip_p + len);
7. sum = ip_cksum_add(&ip->ip_src,8, sum);
8. tcp->th_sum = ip_cksum_carry(sum);
9. }

2.5 宏定义

1. /* include/dnet/ip.h:453-454 */
2. #define ip_cksum_carry(x) \
3. (x =(x >>16)+(x &0xffff),(~(x +(x >>16))&0xffff))

这个宏实现了:

  1. 将进位折叠到低16位
  2. 再次折叠可能的进位
  3. 取反码得到最终校验和

3. IP头部校验和计算

3.1 IP头部校验和规范

根据RFC 791,IPv4头部校验和的特点:

  • 计算范围:仅计算IP头部(包括选项)
  • 校验和字段:计算时先置0
  • 分片处理:只有第一个分片计算校验和
  • 重新计算:每次TTL减少都需要重新计算

3.2 源码分析

3.2.1 ip_checksum()函数

文件位置: src/ip-util.c:132-182

1. void ip_checksum(void*buf,size_t len,int flags)
2. {
3. struct ip_hdr *ip;
4. int hl, off, sum;

6. if(len&nbsp;<&nbsp;IP_HDR_LEN)
7. return;

9. ip&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)buf;
10. hl&nbsp;=&nbsp;ip->ip_hl&nbsp;<<2;/* 头部长度(字节) */
11. ip->ip_sum&nbsp;=0;/* 清零校验和字段 */
12. sum&nbsp;=&nbsp;ip_cksum_add(ip,&nbsp;hl,0);/* 计算头部校验和 */
13. ip->ip_sum&nbsp;=&nbsp;ip_cksum_carry(sum);/* 存储结果 */

15. /* 如果设置了IP_CHECKSUM_FRAGMENT标志,只计算IP头部 */
16. if(flags&nbsp;&&nbsp;IP_CHECKSUM_FRAGMENT)
17. return;

19. off&nbsp;=&nbsp;htons(ip->ip_off);

21. /* 如果是分片(除了最后一个分片),不计算上层协议校验和 */
22. if((off&nbsp;&&nbsp;IP_OFFMASK)!=0||(off&nbsp;&&nbsp;IP_MF)!=0)
23. return;

25. len&nbsp;-=&nbsp;hl;/* 剩余数据长度 */

27. /* 根据IP协议类型计算上层协议校验和 */
28. if(ip->ip_p&nbsp;==&nbsp;IP_PROTO_TCP){
29. struct&nbsp;tcp_hdr&nbsp;*tcp&nbsp;=(struct&nbsp;tcp_hdr&nbsp;*)((u_char&nbsp;*)ip&nbsp;+&nbsp;hl);
30. if(len&nbsp;>=&nbsp;TCP_HDR_LEN){
31. tcp_checksum(ip,&nbsp;tcp,&nbsp;len);
32. }
33. }elseif(ip->ip_p&nbsp;==&nbsp;IP_PROTO_UDP){
34. struct&nbsp;udp_hdr&nbsp;*udp&nbsp;=(struct&nbsp;udp_hdr&nbsp;*)((u_char&nbsp;*)ip&nbsp;+&nbsp;hl);
35. if(len&nbsp;>=&nbsp;UDP_HDR_LEN){
36. udp_checksum(ip,&nbsp;udp,&nbsp;len);
37. }
38. }elseif(ip->ip_p&nbsp;==&nbsp;IP_PROTO_ICMP&nbsp;||&nbsp;ip->ip_p&nbsp;==&nbsp;IP_PROTO_IGMP){
39. struct&nbsp;icmp_hdr&nbsp;*icmp&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)((u_char&nbsp;*)ip&nbsp;+&nbsp;hl);
40. if(len&nbsp;>=&nbsp;ICMP_HDR_LEN){
41. icmp_checksum(icmp,&nbsp;len);
42. }
43. }elseif(ip->ip_p&nbsp;==&nbsp;IP_PROTO_SCTP){
44. struct&nbsp;sctp_hdr&nbsp;*sctp&nbsp;=(struct&nbsp;sctp_hdr&nbsp;*)((u_char&nbsp;*)ip&nbsp;+&nbsp;hl);
45. if(len&nbsp;>=&nbsp;SCTP_HDR_LEN){
46. sctp->sh_sum&nbsp;=0;
47. sctp->sh_sum&nbsp;=&nbsp;htonl(_crc32c((u_char&nbsp;*)sctp,&nbsp;len));
48. }
49. }
50. }

3.2.2 关键设计点

  1. 分片处理: cif((off&IP_OFFMASK)!=0||(off&IP_MF)!=0)return;
  • 只对完整的或最后一个分片计算上层校验和
  • 中间分片不计算(因为TCP/UDP/ICMP头部在第一个分片)
  1. 协议分发
  • TCP:计算伪头部校验和
  • UDP:计算伪头部校验和(特殊处理0值)
  • ICMP/IGMP:计算简单校验和
  • SCTP:使用CRC32-C算法
  1. 长度检查: cif(len>=TCP_HDR_LEN)/* 确保有足够的头部数据 */

3.3 计算流程图

1. 开始
2. │
3. ├─→检查长度>=&nbsp;IP_HDR_LEN
4. │
5. ├─→提取IP头部信息
6. │├─→头部长度&nbsp;hl&nbsp;=&nbsp;ip_hl&nbsp;<<2
7. │└─→标志位&nbsp;off&nbsp;=&nbsp;ip_off
8. │
9. ├─→清零&nbsp;ip_sum
10. │
11. ├─→计算IP头部校验和
12. │├─→&nbsp;ip_cksum_add(ip,&nbsp;hl,0)
13. │└─→&nbsp;ip_cksum_carry(sum)
14. │
15. ├─→检查IP_CHECKSUM_FRAGMENT标志
16. │└─→如果设置,直接返回
17. │
18. ├─→检查是否为分片
19. │└─→如果是中间分片,返回
20. │
21. ├─→根据协议类型分发
22. │├─→&nbsp;IP_PROTO_TCP&nbsp;→&nbsp;tcp_checksum()
23. │├─→&nbsp;IP_PROTO_UDP&nbsp;→&nbsp;udp_checksum()
24. │├─→&nbsp;IP_PROTO_ICMP/IGMP&nbsp;→&nbsp;icmp_checksum()
25. │└─→&nbsp;IP_PROTO_SCTP&nbsp;→&nbsp;_crc32c()
26. │
27. └─→结束

3.4 IPCHECKSUMFRAGMENT标志

1. /* include/dnet/ip.h:449 */
2. #define&nbsp;IP_CHECKSUM_FRAGMENT&nbsp;1

用途:

  • 手动分片时,对每个分片只计算IP头部校验和
  • 避免错误地尝试计算不完整的上层协议校验和

示例:

1. /* 发送第一个分片 */
2. ip_checksum(buf1,&nbsp;len1,0);/* 计算IP和上层协议校验和 */

4. /* 发送后续分片 */
5. ip_checksum(buf2,&nbsp;len2,&nbsp;IP_CHECKSUM_FRAGMENT);/* 只计算IP头部 */

4. TCP校验和计算

4.1 TCP伪头部

TCP校验和需要包含伪头部(Pseudo-header),以确保包含IP层信息:

1. +--------+--------+--------+--------+
2. |源IP地址(4字节)|
3. +--------+--------+--------+--------+
4. |目的IP地址(4字节)|
5. +--------+--------+--------+--------+
6. |保留|协议|&nbsp; &nbsp; &nbsp;TCP长度|
7. +--------+--------+--------+--------+
8. |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;TCP头部+数据|
9. +--------+--------+--------+--------+

4.2 源码分析

4.2.1 tcp_checksum()函数

文件位置: src/ip-util.c:102-109

1. void&nbsp;tcp_checksum(struct&nbsp;ip_hdr&nbsp;*ip,struct&nbsp;tcp_hdr&nbsp;*tcp,size_t&nbsp;len)
2. {
3. int&nbsp;sum;
4. tcp->th_sum&nbsp;=0;/* 清零校验和字段 */

6. /* 1. 计算TCP头部+数据的校验和 */
7. sum&nbsp;=&nbsp;ip_cksum_add(tcp,&nbsp;len,0);

9. /* 2. 加上伪头部信息(协议+长度) */
10. sum&nbsp;=&nbsp;sum&nbsp;+&nbsp;htonl(ip->ip_p&nbsp;+&nbsp;len);

12. /* 3. 加上源和目的IP地址(8字节) */
13. sum&nbsp;=&nbsp;ip_cksum_add(&ip->ip_src,8,&nbsp;sum);

15. /* 4. 进位折叠并取反码 */
16. tcp->th_sum&nbsp;=&nbsp;ip_cksum_carry(sum);
17. }

4.2.2 关键点解析

  1. 协议长度编码: c sum=sum+htonl(ip->ip_p+len);
  • ip->ip_p:协议号(TCP = 6)
  • len:TCP头部+数据长度
  • 使用 htonl()确保大端序
  1. IP地址处理: c sum=ip_cksum_add(&ip->ip_src,8,sum);
  • 直接累加8字节(源IP + 目的IP)
  • ip_cksum_add会自动处理字节序
  1. 验证接收时的校验和

  1. /* 验证TCP校验和 */
  2. tcp->th_sum&nbsp;=0;/* 先清零 */
  3. sum&nbsp;=&nbsp;ip_cksum_add(tcp,&nbsp;len,0);
  4. sum&nbsp;=&nbsp;ip_cksum_add(&ip->ip_src,8,&nbsp;sum)+&nbsp;htonl(ip->ip_p&nbsp;+&nbsp;len);
  5. cksum&nbsp;=&nbsp;ip_cksum_carry(sum);
  6. /* 如果cksum == 0xFFFF,校验通过 */
  7. if(cksum&nbsp;!=0xFFFF){
  8. /* 校验失败 */
  9. }

4.3 计算示例

假设发送一个TCP SYN包:

1. #include<dnet.h>
2. #include<string.h>

4. void&nbsp;send_tcp_syn(void){
5. u_char packet[128];
6. struct&nbsp;ip_hdr&nbsp;*ip&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
7. struct&nbsp;tcp_hdr&nbsp;*tcp&nbsp;=(struct&nbsp;tcp_hdr&nbsp;*)(packet&nbsp;+20);

9. /* 构造IP头部 */
10. memset(ip,0,sizeof(*ip));
11. ip->ip_v&nbsp;=4;
12. ip->ip_hl&nbsp;=5;
13. ip->ip_tos&nbsp;=0;
14. ip->ip_len&nbsp;=&nbsp;htons(40);/* IP头20 + TCP头20 */
15. ip->ip_id&nbsp;=&nbsp;rand()&0xffff;
16. ip->ip_off&nbsp;=0;
17. ip->ip_ttl&nbsp;=64;
18. ip->ip_p&nbsp;=&nbsp;IP_PROTO_TCP;
19. ip->ip_sum&nbsp;=0;
20. ip->ip_src&nbsp;=&nbsp;inet_addr("192.168.1.100");
21. ip->ip_dst&nbsp;=&nbsp;inet_addr("192.168.1.200");

23. /* 构造TCP头部 */
24. memset(tcp,0,sizeof(*tcp));
25. tcp->th_sport&nbsp;=&nbsp;htons(12345);
26. tcp->th_dport&nbsp;=&nbsp;htons(80);
27. tcp->th_seq&nbsp;=&nbsp;htonl(1000);
28. tcp->th_ack&nbsp;=0;
29. tcp->th_off&nbsp;=5;
30. tcp->th_flags&nbsp;=&nbsp;TH_SYN;
31. tcp->th_win&nbsp;=&nbsp;htons(65535);
32. tcp->th_urp&nbsp;=0;
33. tcp->th_sum&nbsp;=0;

35. /* 计算校验和 */
36. ip_checksum(packet,40,0);

38. /* 发送数据包... */
39. }

4.4 常见错误

  1. 忘记清零校验和字段

  1. /* 错误 */

  2. sum&nbsp;=&nbsp;ip_cksum_add(tcp,&nbsp;len,0);/* tcp->th_sum未清零 */

  3. /* 正确 */

  4. tcp->th_sum&nbsp;=0;

  5. sum&nbsp;=&nbsp;ip_cksum_add(tcp,&nbsp;len,0);

  6. 长度错误


  1. /* 错误:只传TCP头部 */

  2. tcp_checksum(ip,&nbsp;tcp,&nbsp;TCP_HDR_LEN);

  3. /* 正确:包括数据 */

  4. tcp_checksum(ip,&nbsp;tcp,&nbsp;TCP_HDR_LEN&nbsp;+&nbsp;data_len);

  5. 字节序错误


  1. /* 错误:未转换字节序 */
  2. sum&nbsp;=&nbsp;sum&nbsp;+&nbsp;ip->ip_p&nbsp;+&nbsp;len;
  3. /* 正确:使用htonl */
  4. sum&nbsp;=&nbsp;sum&nbsp;+&nbsp;htonl(ip->ip_p&nbsp;+&nbsp;len);

5. UDP校验和计算

5.1 UDP校验和特点

UDP校验和与TCP类似,但有一个重要特性:

  • 可选性:UDP校验和是可选的(RFC 768)
  • 零值特殊处理:如果校验和为0,表示发送方禁用校验和
  • 零值替换:计算结果为0时,必须替换为0xFFFF

5.2 源码分析

5.2.1 udp_checksum()函数

文件位置: src/ip-util.c:112-121

1. void&nbsp;udp_checksum(struct&nbsp;ip_hdr&nbsp;*ip,struct&nbsp;udp_hdr&nbsp;*udp,size_t&nbsp;len)
2. {
3. int&nbsp;sum;
4. udp->uh_sum&nbsp;=0;/* 清零校验和字段 */

6. /* 1. 计算UDP头部+数据的校验和 */
7. sum&nbsp;=&nbsp;ip_cksum_add(udp,&nbsp;len,0);

9. /* 2. 加上伪头部信息(协议+长度) */
10. sum&nbsp;=&nbsp;sum&nbsp;+&nbsp;htonl(IP_PROTO_UDP&nbsp;+&nbsp;len);

12. /* 3. 加上源和目的IP地址(8字节) */
13. sum&nbsp;=&nbsp;ip_cksum_add(&ip->ip_src,8,&nbsp;sum);

15. /* 4. 进位折叠并取反码 */
16. udp->uh_sum&nbsp;=&nbsp;ip_cksum_carry(sum);

18. /* 5. 特殊处理:如果结果为0,替换为0xFFFF */
19. if(!udp->uh_sum)
20. udp->uh_sum&nbsp;=0xffff;/* RFC 768 */
21. }

5.2.2 与TCP的区别

| 特性 | TCP | UDP | | — | — | — | | 伪头部 | 相同 | 相同 | | 必填性 | 必须计算 | 可选(为0表示禁用) | | 零值处理 | 允许 | 替换为0xFFFF | | 协议号 | IPPROTOTCP (6) | IPPROTOUDP (17) |

5.3 禁用UDP校验和

有些应用为了性能会禁用UDP校验和:

1. /* 方法1:直接设置为0 */
2. udp->uh_sum&nbsp;=0;/* 接收方不验证 */

4. /* 方法2:不调用udp_checksum() */
5. /* 注意:某些网络设备可能会在传输时添加校验和 */

5.4 验证UDP校验和

1. /* 接收端验证 */
2. int&nbsp;verify_udp_checksum(struct&nbsp;ip_hdr&nbsp;*ip,struct&nbsp;udp_hdr&nbsp;*udp,size_t&nbsp;len){
3. uint16_t&nbsp;received_sum&nbsp;=&nbsp;udp->uh_sum;

5. /* 如果校验和为0,表示发送方禁用了校验和 */
6. if(received_sum&nbsp;==0){
7. return1;/* 跳过验证 */
8. }

10. /* 重新计算校验和 */
11. udp->uh_sum&nbsp;=0;
12. int&nbsp;sum&nbsp;=&nbsp;ip_cksum_add(udp,&nbsp;len,0);
13. sum&nbsp;=&nbsp;sum&nbsp;+&nbsp;htonl(IP_PROTO_UDP&nbsp;+&nbsp;len);
14. sum&nbsp;=&nbsp;ip_cksum_add(&ip->ip_src,8,&nbsp;sum);
15. uint16_t&nbsp;calc_sum&nbsp;=&nbsp;ip_cksum_carry(sum);

17. /* 恢复原始值 */
18. udp->uh_sum&nbsp;=&nbsp;received_sum;

20. /* 检查是否匹配 */
21. return&nbsp;calc_sum&nbsp;==0xffff;
22. }

6. ICMP校验和计算

6.1 ICMP校验和特点

ICMP校验和的特点:

  • 简单校验:只覆盖ICMP头部+数据
  • 无伪头部:不需要IP地址信息
  • 全包覆盖:包括所有ICMP类型和代码

6.2 源码分析

6.2.1 icmp_checksum()函数

文件位置: src/ip-util.c:124-129

1. void&nbsp;icmp_checksum(struct&nbsp;icmp_hdr&nbsp;*icmp,size_t&nbsp;len)
2. {
3. int&nbsp;sum;

5. /* 计算ICMP头部+数据的校验和 */
6. sum&nbsp;=&nbsp;ip_cksum_add(icmp,&nbsp;len,0);

8. /* 进位折叠并取反码 */
9. icmp->icmp_cksum&nbsp;=&nbsp;ip_cksum_carry(sum);
10. }

6.2.2 ICMP Echo示例

1. #include<dnet.h>

3. void&nbsp;send_icmp_echo(void){
4. u_char packet[128];
5. struct&nbsp;ip_hdr&nbsp;*ip&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
6. struct&nbsp;icmp_hdr&nbsp;*icmp&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)(packet&nbsp;+20);
7. struct&nbsp;icmp_msg_echo&nbsp;*echo&nbsp;=(struct&nbsp;icmp_msg_echo&nbsp;*)(packet&nbsp;+24);

9. /* 构造IP头部 */
10. memset(ip,0,sizeof(*ip));
11. ip->ip_v&nbsp;=4;
12. ip->ip_hl&nbsp;=5;
13. ip->ip_len&nbsp;=&nbsp;htons(28);/* IP头20 + ICMP头4 + 数据8 */
14. ip->ip_id&nbsp;=&nbsp;rand()&0xffff;
15. ip->ip_ttl&nbsp;=64;
16. ip->ip_p&nbsp;=&nbsp;IP_PROTO_ICMP;
17. ip->ip_src&nbsp;=&nbsp;inet_addr("192.168.1.100");
18. ip->ip_dst&nbsp;=&nbsp;inet_addr("8.8.8.8");

20. /* 构造ICMP Echo请求 */
21. icmp->icmp_type&nbsp;=&nbsp;ICMP_ECHO;
22. icmp->icmp_code&nbsp;=0;
23. icmp->icmp_cksum&nbsp;=0;

25. /* Echo数据 */
26. echo->icmp_id&nbsp;=&nbsp;htons(12345);
27. echo->icmp_seq&nbsp;=&nbsp;htons(1);
28. memcpy(echo->icmp_data,"Hello",5);

30. /* 计算校验和 */
31. icmp_checksum(icmp,12);/* ICMP头4 + 数据8 */
32. ip_checksum(packet,28,0);

34. /* 发送数据包... */
35. }

6.3 ICMP错误消息校验和

ICMP错误消息需要包含原始IP包的前8字节:

1. /* 构造ICMP目的不可达消息 */
2. void&nbsp;send_icmp_unreachable(void){
3. u_char packet[128];
4. struct&nbsp;ip_hdr&nbsp;*ip&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
5. struct&nbsp;icmp_hdr&nbsp;*icmp&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)(packet&nbsp;+20);
6. struct&nbsp;icmp_msg_quote&nbsp;*quote&nbsp;=(struct&nbsp;icmp_msg_quote&nbsp;*)(packet&nbsp;+24);
7. u_char&nbsp;*orig_ip&nbsp;=&nbsp;packet&nbsp;+28;

9. /* 构造ICMP目的不可达 */
10. icmp->icmp_type&nbsp;=&nbsp;ICMP_UNREACH;
11. icmp->icmp_code&nbsp;=&nbsp;ICMP_UNREACH_PORT;
12. icmp->icmp_cksum&nbsp;=0;

14. /* 引用原始IP包(假设在buf中) */
15. memcpy(orig_ip,&nbsp;original_packet,8);

17. /* 计算校验和:4字节头 + 4字节保留 + 8字节原始IP */
18. icmp_checksum(icmp,16);
19. ip_checksum(packet,36,0);
20. }

7. ICMPv6校验和计算

7.1 ICMPv6伪头部

ICMPv6需要IPv6伪头部:

1. +--------+--------+--------+--------+
2. |源IPv6地址(16字节)|
3. +--------+--------+--------+--------+
4. |目的IPv6地址(16字节)|
5. +--------+--------+--------+--------+
6. |上层包长度(4字节)|
7. +--------+--------+--------+--------+
8. |保留(3字节)|
9. +--------+--------+--------+
10. |下一个头(1字节)|
11. +--------+--------+--------+--------+
12. |ICMPv6头部+数据|
13. +--------+--------+--------+--------+

7.2 源码分析

7.2.1 ip6_checksum()函数

文件位置: src/ip6.c:18-72

1. void&nbsp;ip6_checksum(void*buf,size_t&nbsp;len)
2. {
3. struct&nbsp;ip6_hdr&nbsp;*ip6&nbsp;=(struct&nbsp;ip6_hdr&nbsp;*)buf;
4. struct&nbsp;ip6_ext_hdr&nbsp;*ext;
5. u_char&nbsp;*p,&nbsp;nxt;
6. int&nbsp;i,&nbsp;sum;

8. nxt&nbsp;=&nbsp;ip6->ip6_nxt;/* 获取下一个头部类型 */

10. /* 跳过扩展头部,找到上层协议头部 */
11. for(i&nbsp;=&nbsp;IP6_HDR_LEN;&nbsp;IP6_IS_EXT(nxt);&nbsp;i&nbsp;+=(ext->ext_len&nbsp;+1)<<3){
12. if(i&nbsp;>=(int)len)return;
13. ext&nbsp;=(struct&nbsp;ip6_ext_hdr&nbsp;*)((u_char&nbsp;*)buf&nbsp;+&nbsp;i);
14. nxt&nbsp;=&nbsp;ext->ext_nxt;
15. }

17. p&nbsp;=(u_char&nbsp;*)buf&nbsp;+&nbsp;i;/* 上层协议头部位置 */
18. len&nbsp;-=&nbsp;i;/* 上层协议数据长度 */

20. if(nxt&nbsp;==&nbsp;IP_PROTO_TCP){
21. struct&nbsp;tcp_hdr&nbsp;*tcp&nbsp;=(struct&nbsp;tcp_hdr&nbsp;*)p;

23. if(len&nbsp;>=&nbsp;TCP_HDR_LEN){
24. tcp->th_sum&nbsp;=0;
25. /* 计算TCP数据 + 协议号(8) + 长度 */
26. sum&nbsp;=&nbsp;ip_cksum_add(tcp,&nbsp;len,0)+&nbsp;htons(nxt&nbsp;+(u_short)len);
27. /* 加上IPv6伪头部(32字节) */
28. sum&nbsp;=&nbsp;ip_cksum_add(&ip6->ip6_src,32,&nbsp;sum);
29. tcp->th_sum&nbsp;=&nbsp;ip_cksum_carry(sum);
30. }
31. }elseif(nxt&nbsp;==&nbsp;IP_PROTO_UDP){
32. struct&nbsp;udp_hdr&nbsp;*udp&nbsp;=(struct&nbsp;udp_hdr&nbsp;*)p;

34. if(len&nbsp;>=&nbsp;UDP_HDR_LEN){
35. udp->uh_sum&nbsp;=0;
36. sum&nbsp;=&nbsp;ip_cksum_add(udp,&nbsp;len,0)+&nbsp;htons(nxt&nbsp;+(u_short)len);
37. sum&nbsp;=&nbsp;ip_cksum_add(&ip6->ip6_src,32,&nbsp;sum);
38. /* UDP特殊处理:0替换为0xFFFF */
39. if((udp->uh_sum&nbsp;=&nbsp;ip_cksum_carry(sum))==0)
40. udp->uh_sum&nbsp;=0xffff;
41. }
42. }elseif(nxt&nbsp;==&nbsp;IP_PROTO_ICMPV6){
43. struct&nbsp;icmp_hdr&nbsp;*icmp&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)p;

45. if(len&nbsp;>=&nbsp;ICMP_HDR_LEN){
46. icmp->icmp_cksum&nbsp;=0;
47. sum&nbsp;=&nbsp;ip_cksum_add(icmp,&nbsp;len,0)+&nbsp;htons(nxt&nbsp;+(u_short)len);
48. sum&nbsp;=&nbsp;ip_cksum_add(&ip6->ip6_src,32,&nbsp;sum);
49. icmp->icmp_cksum&nbsp;=&nbsp;ip_cksum_carry(sum);
50. }
51. }elseif(nxt&nbsp;==&nbsp;IP_PROTO_ICMP&nbsp;||&nbsp;nxt&nbsp;==&nbsp;IP_PROTO_IGMP){
52. /* 注意:这里处理的是IPv4封装的ICMP */
53. struct&nbsp;icmp_hdr&nbsp;*icmp&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)p;

55. if(len&nbsp;>=&nbsp;ICMP_HDR_LEN){
56. icmp->icmp_cksum&nbsp;=0;
57. /* 没有伪头部 */
58. sum&nbsp;=&nbsp;ip_cksum_add(icmp,&nbsp;len,0);
59. icmp->icmp_cksum&nbsp;=&nbsp;ip_cksum_carry(sum);
60. }
61. }
62. }

7.2.2 关键点

  1. 扩展头部处理: c#defineIP6_IS_EXT(n)\((n)==IP_PROTO_HOPOPTS||(n)==IP_PROTO_DSTOPTS||\(n)==IP_PROTO_ROUTING||(n)==IP_PROTO_FRAGMENT)
  • 跳过所有扩展头部
  • 找到真正的上层协议头部
  1. 伪头部差异
  • IPv4:8字节(源IP + 目的IP)
  • IPv6:32字节(源IPv6 + 目的IPv6 + 保留 + 下一个头)
  1. 协议号编码: c sum=sum+htons(nxt+(u_short)len);
  • 使用 htons而非 htonl(因为协议号是1字节)

7.3 ICMPv6示例

1. #include<dnet.h>

3. void&nbsp;send_icmpv6_echo(void){
4. u_char packet[128];
5. struct&nbsp;ip6_hdr&nbsp;*ip6&nbsp;=(struct&nbsp;ip6_hdr&nbsp;*)packet;
6. struct&nbsp;icmp_hdr&nbsp;*icmp&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)(packet&nbsp;+40);
7. struct&nbsp;icmp_msg_echo&nbsp;*echo&nbsp;=(struct&nbsp;icmp_msg_echo&nbsp;*)(packet&nbsp;+44);

9. /* 构造IPv6头部 */
10. memset(ip6,0,sizeof(*ip6));
11. ip6->ip6_vfc&nbsp;=0x60;/* IPv6 */
12. ip6->ip6_plen&nbsp;=&nbsp;htons(12);/* ICMP头4 + 数据8 */
13. ip6->ip6_nxt&nbsp;=&nbsp;IP_PROTO_ICMPV6;
14. ip6->ip6_hlim&nbsp;=64;
15. inet_pton(AF_INET6,"2001:db8::1",&ip6->ip6_src);
16. inet_pton(AF_INET6,"2001:4860:4860::8888",&ip6->ip6_dst);

18. /* 构造ICMPv6 Echo请求 */
19. icmp->icmp_type&nbsp;=&nbsp;ICMP_ECHO;/* ICMPv6和ICMPv4使用相同的类型 */
20. icmp->icmp_code&nbsp;=0;
21. icmp->icmp_cksum&nbsp;=0;

23. echo->icmp_id&nbsp;=&nbsp;htons(12345);
24. echo->icmp_seq&nbsp;=&nbsp;htons(1);

26. /* 计算校验和 */
27. ip6_checksum(packet,52);/* IPv6头40 + ICMP头4 + 数据8 */

29. /* 发送数据包... */
30. }

8. SCTP校验和计算

8.1 SCTP校验和特点

SCTP使用CRC32-C(Castagnoli)校验和:

  • 更强的错误检测:比Internet校验和更好的错误检测能力
  • RFC标准:RFC 4960
  • 多项式:0x1EDC6F41
  • 硬件加速:现代CPU的SSE4.2指令集支持

8.2 CRC32-C算法

8.2.1 查表实现

文件位置: src/crc32ct.h:1-82

1. /* crc32ct.h */
2. #define&nbsp;CRC32C_POLY&nbsp;0x1EDC6F41
3. #define&nbsp;CRC32C(c,d)(c=(c>>8)^crc_c[(c^(d))&0xFF])

5. staticunsignedlong&nbsp;crc_c[256]={
6. 0x00000000L,0xF26B8303L,0xE13B70F7L,0x1350F3F4L,
7. /* ... 256个预计算值 ... */
8. 0xBE2DA0A5L,0x4C4623A6L,0x5F16D052L,0xAD7D5351L,
9. };

8.2.2 _crc32c()函数

文件位置: src/ip-util.c:18-38

1. staticunsignedlong&nbsp;_crc32c(unsignedchar*buf,int&nbsp;len)
2. {
3. int&nbsp;i;
4. unsignedlong&nbsp;crc32&nbsp;=~0L;/* 初始值:全1 */
5. unsignedlong&nbsp;result;
6. unsignedchar&nbsp;byte0,&nbsp;byte1,&nbsp;byte2,&nbsp;byte3;

8. /* 对每个字节应用CRC32C */
9. for(i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;len;&nbsp;i++){
10. CRC32C(crc32,&nbsp;buf[i]);
11. }

13. result&nbsp;=~crc32;/* 最终异或 */

15. /* 字节序翻转(输出转换) */
16. byte0&nbsp;=&nbsp; result &nbsp; &nbsp; &nbsp; &nbsp;&0xff;
17. byte1&nbsp;=(result&nbsp;>>8)&0xff;
18. byte2&nbsp;=(result&nbsp;>>16)&0xff;
19. byte3&nbsp;=(result&nbsp;>>24)&0xff;
20. crc32&nbsp;=((byte0&nbsp;<<24)|(byte1&nbsp;<<16)|(byte2&nbsp;<<8)|&nbsp;byte3);

22. return&nbsp;crc32;
23. }

8.2.3 在ip_checksum()中的调用

文件位置: src/ip-util.c:176-181

1. }elseif(ip->ip_p&nbsp;==&nbsp;IP_PROTO_SCTP){
2. struct&nbsp;sctp_hdr&nbsp;*sctp&nbsp;=(struct&nbsp;sctp_hdr&nbsp;*)((u_char&nbsp;*)ip&nbsp;+&nbsp;hl);
3. if(len&nbsp;>=&nbsp;SCTP_HDR_LEN){
4. sctp->sh_sum&nbsp;=0;
5. sctp->sh_sum&nbsp;=&nbsp;htonl(_crc32c((u_char&nbsp;*)sctp,&nbsp;len));
6. }
7. }

8.3 CRC32-C计算过程

1. 1.初始化:crc32&nbsp;=0xFFFFFFFF

3. 2.对每个字节:
4. crc32&nbsp;=(crc32&nbsp;>>8)^&nbsp;table[(crc32&nbsp;^byte)&0xFF]

6. 3.最终异或:crc32&nbsp;=&nbsp;crc32&nbsp;^0xFFFFFFFF

8. 4.字节序翻转(大端序输出)

8.4 SCTP示例

1. #include<dnet.h>

3. void&nbsp;send_sctp_init(void){
4. u_char packet[128];
5. struct&nbsp;ip_hdr&nbsp;*ip&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
6. struct&nbsp;sctp_hdr&nbsp;*sctp&nbsp;=(struct&nbsp;sctp_hdr&nbsp;*)(packet&nbsp;+20);
7. struct&nbsp;sctp_chunkhdr_init&nbsp;*init&nbsp;=
8. (struct&nbsp;sctp_chunkhdr_init&nbsp;*)(packet&nbsp;+32);

10. /* 构造IP头部 */
11. memset(ip,0,sizeof(*ip));
12. ip->ip_v&nbsp;=4;
13. ip->ip_hl&nbsp;=5;
14. ip->ip_len&nbsp;=&nbsp;htons(68);/* IP头20 + SCTP头12 + INIT块36 */
15. ip->ip_id&nbsp;=&nbsp;rand()&0xffff;
16. ip->ip_ttl&nbsp;=64;
17. ip->ip_p&nbsp;=&nbsp;IP_PROTO_SCTP;
18. ip->ip_src&nbsp;=&nbsp;inet_addr("192.168.1.100");
19. ip->ip_dst&nbsp;=&nbsp;inet_addr("192.168.1.200");

21. /* 构造SCTP头部 */
22. sctp->sh_sport&nbsp;=&nbsp;htons(12345);
23. sctp->sh_dport&nbsp;=&nbsp;htons(80);
24. sctp->sh_vtag&nbsp;=0;/* INIT时为0 */
25. sctp->sh_sum&nbsp;=0;

27. /* 构造INIT块 */
28. init->chunkhdr.sch_type&nbsp;=&nbsp;SCTP_INIT;
29. init->chunkhdr.sch_flags&nbsp;=0;
30. init->chunkhdr.sch_length&nbsp;=&nbsp;htons(36);
31. init->schi_itag&nbsp;=&nbsp;htonl(0x12345678);
32. init->schi_arwnd&nbsp;=&nbsp;htonl(32768);
33. init->schi_nos&nbsp;=&nbsp;htons(1);
34. init->schi_nis&nbsp;=&nbsp;htons(1);
35. init->schi_itsn&nbsp;=&nbsp;htonl(1000);

37. /* 计算校验和(CRC32-C) */
38. ip_checksum(packet,68,0);

40. /* 发送数据包... */
41. }

8.5 CRC32-C vs Internet校验和

| 特性 | Internet校验和 | CRC32-C | | — | — | — | | 多项式 | 无 | 0x1EDC6F41 | | 输出长度 | 16位 | 32位 | | 错误检测能力 | 一般 | 强 | | 硬件支持 | 无 | SSE4.2 | | 适用协议 | TCP/UDP/ICMP | SCTP | | 性能 | 快 | 较慢(无硬件加速时) |


9. IPv6校验和计算

9.1 IPv6校验和特点

IPv6本身没有校验和(RFC 2460),所有上层协议必须自己计算:

  • 无IP头部校验和:IPv6简化了设计
  • 强制伪头部:所有上层协议必须包含伪头部
  • 扩展头部:需要跳过扩展头部找到上层协议

9.2 源码分析

9.2.1 扩展头部处理

文件位置: src/ip6.c:27-31

1. /* 跳过扩展头部,找到上层协议头部 */
2. for(i&nbsp;=&nbsp;IP6_HDR_LEN;&nbsp;IP6_IS_EXT(nxt);&nbsp;i&nbsp;+=(ext->ext_len&nbsp;+1)<<3){
3. if(i&nbsp;>=(int)len)return;
4. ext&nbsp;=(struct&nbsp;ip6_ext_hdr&nbsp;*)((u_char&nbsp;*)buf&nbsp;+&nbsp;i);
5. nxt&nbsp;=&nbsp;ext->ext_nxt;
6. }

扩展头部类型:

  • IP_PROTO_HOPOPTS:逐跳选项(0)
  • IP_PROTO_DSTOPTS:目的选项(60)
  • IP_PROTO_ROUTING:路由头(43)
  • IP_PROTO_FRAGMENT:分片头(44)

9.2.2 伪头部构造

IPv6伪头部包含:

  • 源IPv6地址(16字节)
  • 目的IPv6地址(16字节)
  • 上层包长度(4字节)
  • 保留(3字节)
  • 下一个头(1字节)

总长度:40字节

9.3 IPv6 vs IPv4伪头部对比

| 字段 | IPv4伪头部 | IPv6伪头部 | | — | — | — | | 源地址 | 4字节(IPv4) | 16字节(IPv6) | | 目的地址 | 4字节(IPv4) | 16字节(IPv6) | | 协议/下一个头 | 1字节 | 1字节 | | 长度 | 2字节(TCP/UDP长度) | 4字节(上层包长度) | | 保留 | 1字节 | 3字节 | | 总长度 | 12字节 | 40字节 |

9.4 IPv6完整示例

1. #include<dnet.h>

3. void&nbsp;send_ipv6_tcp(void){
4. u_char packet[128];
5. struct&nbsp;ip6_hdr&nbsp;*ip6&nbsp;=(struct&nbsp;ip6_hdr&nbsp;*)packet;
6. struct&nbsp;tcp_hdr&nbsp;*tcp&nbsp;=(struct&nbsp;tcp_hdr&nbsp;*)(packet&nbsp;+40);

8. /* 构造IPv6头部 */
9. memset(ip6,0,sizeof(*ip6));
10. ip6->ip6_vfc&nbsp;=0x60;
11. ip6->ip6_plen&nbsp;=&nbsp;htons(20);/* TCP头20 */
12. ip6->ip6_nxt&nbsp;=&nbsp;IP_PROTO_TCP;
13. ip6->ip6_hlim&nbsp;=64;
14. inet_pton(AF_INET6,"2001:db8::1",&ip6->ip6_src);
15. inet_pton(AF_INET6,"2001:db8::2",&ip6->ip6_dst);

17. /* 构造TCP头部 */
18. memset(tcp,0,sizeof(*tcp));
19. tcp->th_sport&nbsp;=&nbsp;htons(12345);
20. tcp->th_dport&nbsp;=&nbsp;htons(80);
21. tcp->th_seq&nbsp;=&nbsp;htonl(1000);
22. tcp->th_ack&nbsp;=0;
23. tcp->th_off&nbsp;=5;
24. tcp->th_flags&nbsp;=&nbsp;TH_SYN;
25. tcp->th_win&nbsp;=&nbsp;htons(65535);
26. tcp->th_sum&nbsp;=0;

28. /* 计算校验和 */
29. ip6_checksum(packet,60);

31. /* 发送数据包... */
32. }

10. 校验和算法详解

10.1 ipcksumadd()函数

文件位置: src/ip-util.c:185-238

1. int&nbsp;ip_cksum_add(constvoid*buf,size_t&nbsp;len,int&nbsp;cksum)
2. {
3. uint16_t*sp&nbsp;=(uint16_t*)buf;
4. int&nbsp;n,&nbsp;sn;

6. /* 处理奇数长度的特殊情况 */
7. if(len&nbsp;==1){
8. constuint8_t*b&nbsp;=&nbsp;buf;
9. return&nbsp;htons(((uint16_t)*b)<<8);
10. }

12. sn&nbsp;=&nbsp;len&nbsp;/2;/* 16位字的数量 */
13. n&nbsp;=(sn&nbsp;+15)/16;/* 循环次数(每16个字一次) */

15. /* 使用Duff's Device展开循环 */
16. switch(sn&nbsp;%16){
17. case0:do{
18. cksum&nbsp;+=*sp++;
19. case15:
20. cksum&nbsp;+=*sp++;
21. case14:
22. cksum&nbsp;+=*sp++;
23. case13:
24. cksum&nbsp;+=*sp++;
25. case12:
26. cksum&nbsp;+=*sp++;
27. case11:
28. cksum&nbsp;+=*sp++;
29. case10:
30. cksum&nbsp;+=*sp++;
31. case9:
32. cksum&nbsp;+=*sp++;
33. case8:
34. cksum&nbsp;+=*sp++;
35. case7:
36. cksum&nbsp;+=*sp++;
37. case6:
38. cksum&nbsp;+=*sp++;
39. case5:
40. cksum&nbsp;+=*sp++;
41. case4:
42. cksum&nbsp;+=*sp++;
43. case3:
44. cksum&nbsp;+=*sp++;
45. case2:
46. cksum&nbsp;+=*sp++;
47. case1:
48. cksum&nbsp;+=*sp++;
49. }while(--n&nbsp;>0);
50. }

52. /* 处理奇数长度 */
53. if(len&nbsp;&1)
54. cksum&nbsp;+=&nbsp;htons(*(u_char&nbsp;*)sp&nbsp;<<8);

56. return(cksum);
57. }

10.2 Duff’s Device优化

Duff’s Device是一种循环展开技术:

1. switch(sn&nbsp;%16){
2. case0:do{
3. cksum&nbsp;+=*sp++;
4. case15:&nbsp;cksum&nbsp;+=*sp++;
5. case14:&nbsp;cksum&nbsp;+=*sp++;
6. /* ... */
7. case1:&nbsp; cksum&nbsp;+=*sp++;
8. }while(--n&nbsp;>0);
9. }

优点:

  • 减少循环开销
  • 适合处理16的倍数的数据
  • 兼容所有C编译器

10.3 ipcksumcarry()宏

文件位置: include/dnet/ip.h:453-454

1. #define&nbsp;ip_cksum_carry(x)&nbsp;\
2. (x&nbsp;=(x&nbsp;>>16)+(x&nbsp;&0xffff),(~(x&nbsp;+(x&nbsp;>>16))&0xffff))

展开后:

1. x&nbsp;=(x&nbsp;>>16)+(x&nbsp;&0xffff);/* 第一次折叠:将高16位加到低16位 */
2. x&nbsp;=&nbsp;x&nbsp;+(x&nbsp;>>16);/* 第二次折叠:处理可能的新进位 */
3. x&nbsp;=~x&nbsp;&0xffff;/* 取反码 */
4. return&nbsp;x;

10.4 完整计算流程

1. 输入:数据缓冲区&nbsp;buf,长度&nbsp;len

3. 1.初始化:
4. sum&nbsp;=0

6. 2.累加16位字:
7. for(i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;len/2;&nbsp;i++){
8. sum&nbsp;+=*(uint16_t*)buf++;
9. }

11. 3.处理奇数字节:
12. if(len&nbsp;%2==1){
13. sum&nbsp;+=(uint16_t)(*(uint8_t*)buf)<<8;
14. }

16. 4.折叠进位:
17. while(sum&nbsp;>>16){
18. sum&nbsp;=(sum&nbsp;&0xFFFF)+(sum&nbsp;>>16);
19. }

21. 5.取反码:
22. sum&nbsp;=~sum;

24. 6.返回:
25. return&nbsp;sum;

10.5 手动计算示例

假设计算数据: 0x000x010xF20xF3

  1. 按16位字分组:

  1. 字1:0x0001

  2. 字2:0xF2F3

  3. 求和: “` 0x0001

  • 0xF2F3

0xF2F4 “`

  1. 折叠进位(如果需要):

(无需折叠,因为高16位为0)

  1. 0xF2F4

  2. 取反码:


  1. ~0xF2F4=0x0D0B

  2. 最终校验和: 0x0D0B


11. CRC32-C算法

11.1 CRC基础概念

CRC(Cyclic Redundancy Check,循环冗余校验)是一种基于多项式除法的错误检测算法:

  • 多项式:表示除数
  • 消息:被除数
  • 余数:校验值

11.2 CRC32-C多项式

1. CRC32-C多项式:x^32+&nbsp;x^28+&nbsp;x^27+&nbsp;x^26+&nbsp;x^25+&nbsp;x^23+&nbsp;x^22+
2. x^20+&nbsp;x^19+&nbsp;x^18+&nbsp;x^14+&nbsp;x^13+&nbsp;x^11+&nbsp;x^10+
3. x^9+&nbsp;x^8+&nbsp;x^6+1

5. 十六进制表示:0x1EDC6F41

11.3 查表法实现

11.3.1 预计算表

文件位置: src/crc32ct.h:15-80

1. staticunsignedlong&nbsp;crc_c[256]={
2. 0x00000000L,0xF26B8303L,0xE13B70F7L,0x1350F3F4L,
3. /* ... 256个值 ... */
4. };

每个值对应: crc32(0x00000000,byte)

11.3.2 CRC32C宏

1. #define&nbsp;CRC32C(c,d)(c=(c>>8)^crc_c[(c^(d))&0xFF])

参数:

  • c:当前CRC值
  • d:输入字节

操作:

  1. 将当前CRC右移8位
  2. 与查表值异或
  3. 查表索引: (c^d)&0xFF

11.4 完整计算过程

1. unsignedlong&nbsp;calculate_crc32c(constunsignedchar*data,int&nbsp;len){
2. unsignedlong&nbsp;crc&nbsp;=0xFFFFFFFF;

4. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;len;&nbsp;i++){
5. crc&nbsp;=(crc&nbsp;>>8)^&nbsp;crc_c[(crc&nbsp;^&nbsp;data[i])&0xFF];
6. }

8. crc&nbsp;=~crc;

10. /* 字节序翻转 */
11. return((crc&nbsp;&0xFF)<<24)|
12. ((crc&nbsp;>>8&0xFF)<<16)|
13. ((crc&nbsp;>>16&0xFF)<<8)|
14. (crc&nbsp;>>24&0xFF);
15. }

11.5 为什么需要字节序翻转?

CRC32-C标准规定输出为大端序,但:

  • Intel x86是小端序
  • 直接计算得到的是小端序
  • 需要翻转字节序以符合标准

11.6 硬件加速

现代CPU支持CRC32指令(SSE4.2):

1. #ifdef&nbsp;__SSE4_2__
2. #include<smmintrin.h>

4. uint32_t&nbsp;crc32c_hardware(constuint8_t*data,size_t&nbsp;len){
5. uint32_t&nbsp;crc&nbsp;=0xFFFFFFFF;

7. /* 使用硬件指令 */
8. for(size_t&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;len;&nbsp;i++){
9. crc&nbsp;=&nbsp;_mm_crc32_u8(crc,&nbsp;data[i]);
10. }

12. return~crc;
13. }
14. #endif

性能对比:

  • 查表法:~2 cycles/byte
  • 硬件加速:~1 cycle/byte

12. 跨平台实现差异

12.1 字节序处理

12.1.1 大端序(Big-Endian)

网络协议使用大端序(网络字节序):

1. 内存布局(大端序):
2. 地址0:0x12(高字节)
3. 地址1:0x34
4. 地址2:0x56
5. 地址3:0x78(低字节)

7. 表示值:0x12345678

12.1.2 小端序(Little-Endian)

x86/x64使用小端序:

1. 内存布局(小端序):
2. 地址0:0x78(低字节)
3. 地址1:0x56
4. 地址2:0x34
5. 地址3:0x12(高字节)

7. 表示值:0x12345678

12.1.3 转换函数

1. /* 主机字节序 → 网络字节序 */
2. uint16_t&nbsp;htons(uint16_t&nbsp;hostshort);
3. uint32_t&nbsp;htonl(uint32_t&nbsp;hostlong);

5. /* 网络字节序 → 主机字节序 */
6. uint16_t&nbsp;ntohs(uint16_t&nbsp;netshort);
7. uint32_t&nbsp;ntohl(uint32_t&nbsp;netlong);

12.2 平台差异

| 平台 | 字节序 | 影响 | 处理方式 | | — | — | — | — | | x86/x64 | 小端序 | 需要转换 | htons/htonl | | ARM | 可配置 | 可能需要转换 | 运行时检测 | | PowerPC | 大端序 | 可能无需转换 | 条件编译 | | MIPS | 可配置 | 需要检测 | 运行时检测 |

12.3 字节序检测

1. /* 检测字节序 */
2. int&nbsp;is_little_endian(void){
3. uint16_t&nbsp;x&nbsp;=0x0001;
4. return*(uint8_t*)&x&nbsp;==1;
5. }

7. /* 条件编译 */
8. #if&nbsp;DNET_BYTESEX == DNET_BIG_ENDIAN
9. /* 大端序代码 */
10. #elif&nbsp;DNET_BYTESEX&nbsp;==&nbsp;DNET_LIL_ENDIAN
11. /* 小端序代码 */
12. #else
13. #error"Unknown byte order"
14. #endif

12.4 对齐问题

某些平台对内存对齐有严格要求:

1. /* 不安全:可能导致总线错误 */
2. uint16_t*p&nbsp;=(uint16_t*)buffer;
3. uint16_t&nbsp;value&nbsp;=*p;/* 如果buffer未对齐 */

5. /* 安全:逐字节读取 */
6. uint16_t&nbsp;read_u16(constuint8_t*p){
7. return(p[0]<<8)|&nbsp;p[1];
8. }

libdnet的处理:

1. /* src/ip-util.c:185 */
2. uint16_t*sp&nbsp;=(uint16_t*)buf;

假设:

  • buf是对齐的缓冲区
  • 或者平台支持未对齐访问

12.5 不同平台的校验和结果

理论上,所有平台应该产生相同的校验和:

1. /* 测试代码 */
2. void&nbsp;test_checksum_cross_platform(void){
3. uint8_t&nbsp;data[]={0x45,0x00,0x00,0x3c,0x12,0x34,0x00,0x00,
4. 0x40,0x06,0x00,0x00,0xc0,0xa8,0x01,0x64,
5. 0xc0,0xa8,0x01,0x01};

7. int&nbsp;sum&nbsp;=&nbsp;ip_cksum_add(data,20,0);
8. uint16_t&nbsp;cksum&nbsp;=&nbsp;ip_cksum_carry(sum);

10. /* 所有平台应该得到相同结果:0x1234 */
11. assert(cksum&nbsp;==0x1234);
12. }

13. 性能优化技巧

13.1 避免重复计算

1. /* 错误:每次都重新计算 */
2. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i++){
3. ip_checksum(buf[i],&nbsp;len,0);
4. /* 修改数据 */
5. ip_checksum(buf[i],&nbsp;len,0);/* 重复计算!*/
6. }

8. /* 正确:只在修改后计算 */
9. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i++){
10. ip_checksum(buf[i],&nbsp;len,0);
11. }

13.2 批量计算

1. /* 批量计算校验和 */
2. void&nbsp;batch_calculate_checksums(void**bufs,size_t*lens,int&nbsp;count){
3. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;count;&nbsp;i++){
4. ip_checksum(bufs[i],&nbsp;lens[i],0);
5. }
6. }

13.3 缓存友好访问

1. /* 顺序访问,缓存友好 */
2. int&nbsp;sum_sequential(constuint16_t*data,int&nbsp;n){
3. int&nbsp;sum&nbsp;=0;
4. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i++){
5. sum&nbsp;+=&nbsp;data[i];
6. }
7. return&nbsp;sum;
8. }

10. /* 跳跃访问,缓存不友好 */
11. int&nbsp;sum_strided(constuint16_t*data,int&nbsp;n,int&nbsp;stride){
12. int&nbsp;sum&nbsp;=0;
13. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i&nbsp;+=&nbsp;stride){
14. sum&nbsp;+=&nbsp;data[i];
15. }
16. return&nbsp;sum;
17. }

13.4 使用SIMD指令

1. #ifdef&nbsp;__AVX2__
2. #include<immintrin.h>

4. int&nbsp;ip_cksum_add_avx2(constvoid*buf,size_t&nbsp;len,int&nbsp;cksum){
5. constuint16_t*p&nbsp;=(constuint16_t*)buf;
6. __m256i sum_vec&nbsp;=&nbsp;_mm256_setzero_si256();

8. /* 每16个uint16_t(32字节)处理一次 */
9. for(size_t&nbsp;i&nbsp;=0;&nbsp;i&nbsp;+16<=&nbsp;len/2;&nbsp;i&nbsp;+=16){
10. __m256i data&nbsp;=&nbsp;_mm256_loadu_si256((__m256i&nbsp;*)&p[i]);
11. sum_vec&nbsp;=&nbsp;_mm256_add_epi16(sum_vec,&nbsp;data);
12. }

14. /* 折叠256位到16位 */
15. /* ... */

17. return&nbsp;cksum;
18. }
19. #endif

13.5 预分配缓冲区

1. /* 错误:每次都malloc */
2. void&nbsp;send_packets(int&nbsp;n){
3. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i++){
4. uint8_t*buf&nbsp;=&nbsp;malloc(1500);
5. /* 构造数据包 */
6. ip_checksum(buf,1500,0);
7. send(buf);
8. free(buf);
9. }
10. }

12. /* 正确:重用缓冲区 */
13. void&nbsp;send_packets_optimized(int&nbsp;n){
14. uint8_t&nbsp;buf[1500];
15. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i++){
16. /* 构造数据包 */
17. ip_checksum(buf,1500,0);
18. send(buf);
19. }
20. }

13.6 性能对比

| 优化方法 | 性能提升 | 实现难度 | | — | — | — | | 循环展开(Duff’s Device) | 20-30% | 低 | | SIMD(AVX2) | 3-5倍 | 中 | | 硬件CRC32 | 2-3倍 | 低 | | 缓存优化 | 10-20% | 中 | | 批量处理 | 10-15% | 低 |


14. 常见问题与调试

14.1 校验和不匹配

症状:接收方拒绝数据包

原因

  1. 字节序错误
  2. 长度错误
  3. 未清零校验和字段
  4. 内存对齐问题

调试方法

1. void&nbsp;debug_checksum(void*buf,size_t&nbsp;len){
2. struct&nbsp;ip_hdr&nbsp;*ip&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)buf;

4. /* 打印IP头部 */
5. printf("IP Header:\n");
6. printf(" &nbsp;src: %s\n",&nbsp;ip_ntoa(&ip->ip_src));
7. printf(" &nbsp;dst: %s\n",&nbsp;ip_ntoa(&ip->ip_dst));
8. printf(" &nbsp;proto: %d\n",&nbsp;ip->ip_p);
9. printf(" &nbsp;sum: 0x%04x\n",&nbsp;ntohs(ip->ip_sum));

11. /* 重新计算并比较 */
12. uint16_t&nbsp;original_sum&nbsp;=&nbsp;ip->ip_sum;
13. ip->ip_sum&nbsp;=0;
14. ip_checksum(buf,&nbsp;len,0);

16. printf(" &nbsp;calc sum: 0x%04x\n",&nbsp;ntohs(ip->ip_sum));

18. if(original_sum&nbsp;!=&nbsp;ip->ip_sum){
19. printf(" &nbsp;ERROR: Checksum mismatch!\n");
20. }

22. ip->ip_sum&nbsp;=&nbsp;original_sum;/* 恢复 */
23. }

14.2 UDP校验和为0

症状:UDP数据包被丢弃

原因:UDP校验和为0表示禁用,但接收方强制验证

解决

1. /* 确保UDP校验和不为0 */
2. if(udp->uh_sum&nbsp;==0){
3. udp->uh_sum&nbsp;=0xffff;
4. }

14.3 奇数长度处理

问题:奇数长度数据可能导致错误

解决:libdnet已自动处理

1. /* src/ip-util.c:234-235 */
2. if(len&nbsp;&1)
3. cksum&nbsp;+=&nbsp;htons(*(u_char&nbsp;*)sp&nbsp;<<8);

14.4 分片处理

问题:分片数据包校验和错误

原因:对所有分片计算上层协议校验和

解决

1. /* 使用IP_CHECKSUM_FRAGMENT标志 */
2. ip_checksum(frag_buf,&nbsp;frag_len,&nbsp;IP_CHECKSUM_FRAGMENT);

14.5 IPv6扩展头部

问题:IPv6校验和不正确

原因:未跳过扩展头部

解决:libdnet的 ip6_checksum()已自动处理

14.6 性能问题

症状:校验和计算太慢

优化

  1. 使用硬件CRC32(SCTP)
  2. 启用SIMD优化
  3. 批量计算
  4. 缓存友好的数据布局

14.7 调试工具

1. /* 打印校验和计算过程 */
2. void&nbsp;trace_checksum(constvoid*buf,size_t&nbsp;len){
3. constuint16_t*p&nbsp;=(constuint16_t*)buf;
4. int&nbsp;sum&nbsp;=0;

6. printf("Checksum calculation:\n");
7. for(size_t&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;len/2;&nbsp;i++){
8. printf(" &nbsp;[%2zu] 0x%04x",&nbsp;i,&nbsp;ntohs(p[i]));
9. int&nbsp;old_sum&nbsp;=&nbsp;sum;
10. sum&nbsp;+=&nbsp;ntohs(p[i]);
11. printf(" -> sum: 0x%08x\n",&nbsp;sum);

13. if(sum&nbsp;<&nbsp;old_sum){
14. printf(" &nbsp;(carry added)\n");
15. sum&nbsp;+=1;
16. }
17. }

19. if(len&nbsp;%2==1){
20. uint8_t&nbsp;last&nbsp;=((constuint8_t*)buf)[len-1];
21. printf(" &nbsp;[last] 0x%02x00\n",&nbsp;last);
22. sum&nbsp;+=(last&nbsp;<<8);
23. }

25. printf("Final sum: 0x%08x\n",&nbsp;sum);
26. sum&nbsp;=(sum&nbsp;>>16)+(sum&nbsp;&0xFFFF);
27. sum&nbsp;=~(sum&nbsp;+(sum&nbsp;>>16))&0xFFFF;
28. printf("Checksum: 0x%04x\n",&nbsp;sum);
29. }

15. 实际应用示例

15.1 发送自定义TCP SYN包

1. #include<dnet.h>
2. #include<stdio.h>
3. #include<stdlib.h>
4. #include<string.h>

6. int&nbsp;send_tcp_syn(constchar*src_ip,constchar*dst_ip,
7. uint16_t&nbsp;src_port,uint16_t&nbsp;dst_port){
8. ip_t*ip;
9. uint8_t&nbsp;packet[64];
10. struct&nbsp;ip_hdr&nbsp;*ip_hdr&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
11. struct&nbsp;tcp_hdr&nbsp;*tcp_hdr&nbsp;=(struct&nbsp;tcp_hdr&nbsp;*)(packet&nbsp;+20);
12. struct&nbsp;addr src,&nbsp;dst;

14. /* 打开IP接口 */
15. if((ip&nbsp;=&nbsp;ip_open())==&nbsp;NULL){
16. perror("ip_open");
17. return-1;
18. }

20. /* 构造IP头部 */
21. memset(ip_hdr,0,sizeof(*ip_hdr));
22. ip_hdr->ip_v&nbsp;=4;
23. ip_hdr->ip_hl&nbsp;=5;
24. ip_hdr->ip_tos&nbsp;=0;
25. ip_hdr->ip_len&nbsp;=&nbsp;htons(40);/* IP头20 + TCP头20 */
26. ip_hdr->ip_id&nbsp;=&nbsp;rand()&0xffff;
27. ip_hdr->ip_off&nbsp;=0;
28. ip_hdr->ip_ttl&nbsp;=64;
29. ip_hdr->ip_p&nbsp;=&nbsp;IP_PROTO_TCP;
30. ip_hdr->ip_sum&nbsp;=0;

32. /* 转换IP地址 */
33. addr_aton(src_ip,&src);
34. addr_aton(dst_ip,&dst);
35. ip_hdr->ip_src&nbsp;=&nbsp;src.addr_ip;
36. ip_hdr->ip_dst&nbsp;=&nbsp;dst.addr_ip;

38. /* 构造TCP头部 */
39. memset(tcp_hdr,0,sizeof(*tcp_hdr));
40. tcp_hdr->th_sport&nbsp;=&nbsp;htons(src_port);
41. tcp_hdr->th_dport&nbsp;=&nbsp;htons(dst_port);
42. tcp_hdr->th_seq&nbsp;=&nbsp;htonl(rand());
43. tcp_hdr->th_ack&nbsp;=0;
44. tcp_hdr->th_off&nbsp;=5;
45. tcp_hdr->th_flags&nbsp;=&nbsp;TH_SYN;
46. tcp_hdr->th_win&nbsp;=&nbsp;htons(65535);
47. tcp_hdr->th_urp&nbsp;=0;
48. tcp_hdr->th_sum&nbsp;=0;

50. /* 计算校验和 */
51. ip_checksum(packet,40,0);

53. /* 发送数据包 */
54. if(ip_send(ip,&nbsp;packet,40)<0){
55. perror("ip_send");
56. ip_close(ip);
57. return-1;
58. }

60. printf("Sent TCP SYN: %s:%d -> %s:%d\n",
61. src_ip,&nbsp;src_port,&nbsp;dst_ip,&nbsp;dst_port);
62. printf(" &nbsp;IP checksum: 0x%04x\n",&nbsp;ntohs(ip_hdr->ip_sum));
63. printf(" &nbsp;TCP checksum: 0x%04x\n",&nbsp;ntohs(tcp_hdr->th_sum));

65. ip_close(ip);
66. return0;
67. }

69. int&nbsp;main(int&nbsp;argc,char*argv[]){
70. if(argc&nbsp;!=5){
71. fprintf(stderr,"Usage: %s <src_ip> <dst_ip> <src_port> <dst_port>\n",
72. argv[0]);
73. return1;
74. }

76. srand(time(NULL));
77. return&nbsp;send_tcp_syn(argv[1],&nbsp;argv[2],&nbsp;atoi(argv[3]),&nbsp;atoi(argv[4]));
78. }

15.2 发送ICMP Echo请求(Ping)

1. #include<dnet.h>
2. #include<stdio.h>
3. #include<stdlib.h>
4. #include<string.h>
5. #include<time.h>

7. int&nbsp;send_ping(constchar*dst_ip){
8. ip_t*ip;
9. uint8_t&nbsp;packet[128];
10. struct&nbsp;ip_hdr&nbsp;*ip_hdr&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
11. struct&nbsp;icmp_hdr&nbsp;*icmp_hdr&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)(packet&nbsp;+20);
12. struct&nbsp;icmp_msg_echo&nbsp;*echo&nbsp;=(struct&nbsp;icmp_msg_echo&nbsp;*)(packet&nbsp;+24);
13. struct&nbsp;addr dst;

15. if((ip&nbsp;=&nbsp;ip_open())==&nbsp;NULL){
16. perror("ip_open");
17. return-1;
18. }

20. /* 构造IP头部 */
21. memset(ip_hdr,0,sizeof(*ip_hdr));
22. ip_hdr->ip_v&nbsp;=4;
23. ip_hdr->ip_hl&nbsp;=5;
24. ip_hdr->ip_len&nbsp;=&nbsp;htons(28);/* IP头20 + ICMP头4 + Echo数据8 */
25. ip_hdr->ip_id&nbsp;=&nbsp;rand()&0xffff;
26. ip_hdr->ip_ttl&nbsp;=64;
27. ip_hdr->ip_p&nbsp;=&nbsp;IP_PROTO_ICMP;
28. ip_hdr->ip_src&nbsp;=&nbsp;inet_addr("0.0.0.0");/* 由系统填充 */

30. addr_aton(dst_ip,&dst);
31. ip_hdr->ip_dst&nbsp;=&nbsp;dst.addr_ip;

33. /* 构造ICMP Echo请求 */
34. icmp_hdr->icmp_type&nbsp;=&nbsp;ICMP_ECHO;
35. icmp_hdr->icmp_code&nbsp;=0;
36. icmp_hdr->icmp_cksum&nbsp;=0;

38. echo->icmp_id&nbsp;=&nbsp;htons(getpid());
39. echo->icmp_seq&nbsp;=&nbsp;htons(1);
40. memcpy(echo->icmp_data,"Hello",5);

42. /* 计算校验和 */
43. icmp_checksum(icmp_hdr,12);/* ICMP头4 + 数据8 */
44. ip_checksum(packet,28,0);

46. /* 发送数据包 */
47. if(ip_send(ip,&nbsp;packet,28)<0){
48. perror("ip_send");
49. ip_close(ip);
50. return-1;
51. }

53. printf("Sent ICMP Echo to %s\n",&nbsp;dst_ip);
54. printf(" &nbsp;IP checksum: 0x%04x\n",&nbsp;ntohs(ip_hdr->ip_sum));
55. printf(" &nbsp;ICMP checksum: 0x%04x\n",&nbsp;ntohs(icmp_hdr->icmp_cksum));

57. ip_close(ip);
58. return0;
59. }

61. int&nbsp;main(int&nbsp;argc,char*argv[]){
62. if(argc&nbsp;!=2){
63. fprintf(stderr,"Usage: %s <dst_ip>\n",&nbsp;argv[0]);
64. return1;
65. }

67. srand(time(NULL));
68. return&nbsp;send_ping(argv[1]);
69. }

15.3 验证接收到的数据包

1. #include<dnet.h>
2. #include<stdio.h>
3. #include<string.h>

5. int&nbsp;verify_ip_packet(constuint8_t*packet,size_t&nbsp;len){
6. struct&nbsp;ip_hdr&nbsp;*ip_hdr&nbsp;=(struct&nbsp;ip_hdr&nbsp;*)packet;
7. uint16_t&nbsp;original_sum;
8. int&nbsp;valid&nbsp;=1;

10. /* 检查最小长度 */
11. if(len&nbsp;<&nbsp;IP_HDR_LEN){
12. printf("Packet too short: %zu bytes\n",&nbsp;len);
13. return0;
14. }

16. /* 验证IP头部校验和 */
17. original_sum&nbsp;=&nbsp;ip_hdr->ip_sum;
18. ip_hdr->ip_sum&nbsp;=0;
19. ip_checksum((void*)packet,&nbsp;len,0);

21. if(original_sum&nbsp;!=&nbsp;ip_hdr->ip_sum){
22. printf("IP checksum mismatch:\n");
23. printf(" &nbsp;Expected: 0x%04x\n",&nbsp;ntohs(original_sum));
24. printf(" &nbsp;Calculated: 0x%04x\n",&nbsp;ntohs(ip_hdr->ip_sum));
25. valid&nbsp;=0;
26. }

28. /* 恢复原始值 */
29. ip_hdr->ip_sum&nbsp;=&nbsp;original_sum;

31. /* 根据协议验证上层校验和 */
32. int&nbsp;ip_hl&nbsp;=&nbsp;ip_hdr->ip_hl&nbsp;<<2;
33. int&nbsp;payload_len&nbsp;=&nbsp;ntohs(ip_hdr->ip_len)-&nbsp;ip_hl;
34. uint8_t*payload&nbsp;=(uint8_t*)packet&nbsp;+&nbsp;ip_hl;

36. switch(ip_hdr->ip_p){
37. case&nbsp;IP_PROTO_TCP:{
38. struct&nbsp;tcp_hdr&nbsp;*tcp_hdr&nbsp;=(struct&nbsp;tcp_hdr&nbsp;*)payload;
39. uint16_t&nbsp;tcp_sum&nbsp;=&nbsp;tcp_hdr->th_sum;

41. tcp_hdr->th_sum&nbsp;=0;
42. tcp_checksum(ip_hdr,&nbsp;tcp_hdr,&nbsp;payload_len);

44. if(tcp_sum&nbsp;!=&nbsp;tcp_hdr->th_sum){
45. printf("TCP checksum mismatch\n");
46. valid&nbsp;=0;
47. }
48. tcp_hdr->th_sum&nbsp;=&nbsp;tcp_sum;
49. break;
50. }
51. case&nbsp;IP_PROTO_UDP:{
52. struct&nbsp;udp_hdr&nbsp;*udp_hdr&nbsp;=(struct&nbsp;udp_hdr&nbsp;*)payload;

54. if(udp_hdr->uh_sum&nbsp;!=0){
55. uint16_t&nbsp;udp_sum&nbsp;=&nbsp;udp_hdr->uh_sum;

57. udp_hdr->uh_sum&nbsp;=0;
58. udp_checksum(ip_hdr,&nbsp;udp_hdr,&nbsp;payload_len);

60. if(udp_sum&nbsp;!=&nbsp;udp_hdr->uh_sum){
61. printf("UDP checksum mismatch\n");
62. valid&nbsp;=0;
63. }
64. udp_hdr->uh_sum&nbsp;=&nbsp;udp_sum;
65. }
66. break;
67. }
68. case&nbsp;IP_PROTO_ICMP:{
69. struct&nbsp;icmp_hdr&nbsp;*icmp_hdr&nbsp;=(struct&nbsp;icmp_hdr&nbsp;*)payload;
70. uint16_t&nbsp;icmp_sum&nbsp;=&nbsp;icmp_hdr->icmp_cksum;

72. icmp_hdr->icmp_cksum&nbsp;=0;
73. icmp_checksum(icmp_hdr,&nbsp;payload_len);

75. if(icmp_sum&nbsp;!=&nbsp;icmp_hdr->icmp_cksum){
76. printf("ICMP checksum mismatch\n");
77. valid&nbsp;=0;
78. }
79. icmp_hdr->icmp_cksum&nbsp;=&nbsp;icmp_sum;
80. break;
81. }
82. }

84. return&nbsp;valid;
85. }

15.4 使用dnet命令行工具

1. # 构造并计算IP数据包校验和
2. echo&nbsp;"Hello, World!"|&nbsp;dnet ip src&nbsp;192.168.1.100&nbsp;dst&nbsp;192.168.1.200&nbsp;\
3. proto&nbsp;6&nbsp;ttl&nbsp;64|&nbsp;xxd

5. # 构造TCP段
6. echo&nbsp;"GET / HTTP/1.1\r\n"|&nbsp;dnet tcp sport&nbsp;12345&nbsp;dport&nbsp;80&nbsp;\
7. flags SYN win&nbsp;65535|&nbsp;xxd

9. # 组合使用(管道)
10. cat&nbsp;<<EOF&nbsp;|&nbsp;dnet ip src&nbsp;192.168.1.100&nbsp;dst&nbsp;192.168.1.200&nbsp;proto&nbsp;6&nbsp;\
11. |&nbsp;dnet tcp sport&nbsp;12345&nbsp;dport&nbsp;80&nbsp;flags SYN win&nbsp;65535&nbsp;\
12. |&nbsp;xxd
13. EOF

总结

本文档深入分析了libdnet中校验和计算的实现,涵盖了:

  1. 九种协议的校验和:IPv4、TCP、UDP、ICMP、ICMPv6、IPv6、SCTP
  2. 两种算法:Internet Checksum和CRC32-C
  3. 核心函数: ip_checksum()、 tcp_checksum()、 udp_checksum()、 icmp_checksum()、 ip6_checksum()
  4. 底层实现: ip_cksum_add()、 ip_cksum_carry()、 _crc32c()
  5. 性能优化:Duff’s Device、SIMD、硬件加速
  6. 跨平台:字节序处理、对齐问题
  7. 实际应用:完整的示例代码

通过本文档,读者可以深入理解libdnet的校验和计算机制,并正确应用于网络编程中。

  • 公众号:安全狗的自我修养
  • vx:2207344074
  • http://gitee.com/haidragon
  • http://github.com/haidragon
  • bilibili:haidragonx

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全狗的自我修养 haidragon haidragon《跨平台底层网络库libdnet源码分析系列(九)》

评论:0   参与:  0