文章总结: 本文深入分析跨平台网络库libdnet的校验和计算实现,系统讲解IP、TCP、UDP、ICMP、SCTP等协议的校验和算法原理与源码实现。文章详细解析了Internet校验和与CRC32-C算法的设计思想,展示了零拷贝、自动协议识别等架构特点,提供了分片处理、伪头部构造等关键技术的代码示例与常见错误分析。适合网络安全开发者和协议实现人员参考。 综合评分: 90 文章分类: 代码审计,网络安全,应用安全,安全开发,技术标准
跨平台底层网络库libdnet源码分析系列(九)
原创
haidragon haidragon
安全狗的自我修养
2026年3月26日 15:32 湖南
官网:http://securitytech.cc
#
源码分析mettle后门工具学习 所使用的依赖库
#
#
校验和计算实现深入分析
目录
- 校验和基础概念
- libdnet校验和架构设计
- IP头部校验和计算
- TCP校验和计算
- UDP校验和计算
- ICMP校验和计算
- ICMPv6校验和计算
- SCTP校验和计算
- IPv6校验和计算
- 校验和算法详解
- CRC32-C算法
- 跨平台实现差异
- 性能优化技巧
- 常见问题与调试
- 实际应用示例
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位反码和校验算法:
- 计算过程:
- 将数据视为16位字序列
- 对所有16位字求和
- 将进位加到低16位(折叠)
- 取反码作为最终校验和
- 验证过程:
- 对接收的数据(包含校验和字段)执行相同计算
- 如果结果为0xFFFF,则校验通过
- 特点:
- 简单高效
- 字节序无关
- 适合硬件实现
- 对突发错误检测效果好
1.4 CRC32-C算法原理
CRC32-C(Castagnoli)是SCTP协议使用的校验和算法:
- 多项式:0x1EDC6F41
- 初始值:0xFFFFFFFF
- 最终异或:0xFFFFFFFF
- 输出转换:字节序翻转
特点:
- 比Internet校验和更强的错误检测能力
- 更适合数据完整性保护
- 现代CPU通常有硬件加速支持(SSE4.2)
2. libdnet校验和架构设计
2.1 核心设计原则
libdnet的校验和计算遵循以下设计原则:
- 零拷贝设计:直接在数据包缓冲区上计算
- 自动协议识别:根据IP协议字段自动选择校验和算法
- 跨平台一致性:所有平台产生相同的校验和结果
- 高性能优化:使用循环展开和快速路径
- 完整性覆盖:支持所有需要校验和的协议
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))
这个宏实现了:
- 将进位折叠到低16位
- 再次折叠可能的进位
- 取反码得到最终校验和
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 < IP_HDR_LEN)
7. return;
9. ip =(struct ip_hdr *)buf;
10. hl = ip->ip_hl <<2;/* 头部长度(字节) */
11. ip->ip_sum =0;/* 清零校验和字段 */
12. sum = ip_cksum_add(ip, hl,0);/* 计算头部校验和 */
13. ip->ip_sum = ip_cksum_carry(sum);/* 存储结果 */
15. /* 如果设置了IP_CHECKSUM_FRAGMENT标志,只计算IP头部 */
16. if(flags & IP_CHECKSUM_FRAGMENT)
17. return;
19. off = htons(ip->ip_off);
21. /* 如果是分片(除了最后一个分片),不计算上层协议校验和 */
22. if((off & IP_OFFMASK)!=0||(off & IP_MF)!=0)
23. return;
25. len -= hl;/* 剩余数据长度 */
27. /* 根据IP协议类型计算上层协议校验和 */
28. if(ip->ip_p == IP_PROTO_TCP){
29. struct tcp_hdr *tcp =(struct tcp_hdr *)((u_char *)ip + hl);
30. if(len >= TCP_HDR_LEN){
31. tcp_checksum(ip, tcp, len);
32. }
33. }elseif(ip->ip_p == IP_PROTO_UDP){
34. struct udp_hdr *udp =(struct udp_hdr *)((u_char *)ip + hl);
35. if(len >= UDP_HDR_LEN){
36. udp_checksum(ip, udp, len);
37. }
38. }elseif(ip->ip_p == IP_PROTO_ICMP || ip->ip_p == IP_PROTO_IGMP){
39. struct icmp_hdr *icmp =(struct icmp_hdr *)((u_char *)ip + hl);
40. if(len >= ICMP_HDR_LEN){
41. icmp_checksum(icmp, len);
42. }
43. }elseif(ip->ip_p == IP_PROTO_SCTP){
44. struct sctp_hdr *sctp =(struct sctp_hdr *)((u_char *)ip + hl);
45. if(len >= SCTP_HDR_LEN){
46. sctp->sh_sum =0;
47. sctp->sh_sum = htonl(_crc32c((u_char *)sctp, len));
48. }
49. }
50. }
3.2.2 关键设计点
- 分片处理:
cif((off&IP_OFFMASK)!=0||(off&IP_MF)!=0)return;
- 只对完整的或最后一个分片计算上层校验和
- 中间分片不计算(因为TCP/UDP/ICMP头部在第一个分片)
- 协议分发:
- TCP:计算伪头部校验和
- UDP:计算伪头部校验和(特殊处理0值)
- ICMP/IGMP:计算简单校验和
- SCTP:使用CRC32-C算法
- 长度检查:
cif(len>=TCP_HDR_LEN)/* 确保有足够的头部数据 */
3.3 计算流程图
1. 开始
2. │
3. ├─→检查长度>= IP_HDR_LEN
4. │
5. ├─→提取IP头部信息
6. │├─→头部长度 hl = ip_hl <<2
7. │└─→标志位 off = ip_off
8. │
9. ├─→清零 ip_sum
10. │
11. ├─→计算IP头部校验和
12. │├─→ ip_cksum_add(ip, hl,0)
13. │└─→ ip_cksum_carry(sum)
14. │
15. ├─→检查IP_CHECKSUM_FRAGMENT标志
16. │└─→如果设置,直接返回
17. │
18. ├─→检查是否为分片
19. │└─→如果是中间分片,返回
20. │
21. ├─→根据协议类型分发
22. │├─→ IP_PROTO_TCP → tcp_checksum()
23. │├─→ IP_PROTO_UDP → udp_checksum()
24. │├─→ IP_PROTO_ICMP/IGMP → icmp_checksum()
25. │└─→ IP_PROTO_SCTP → _crc32c()
26. │
27. └─→结束
3.4 IPCHECKSUMFRAGMENT标志
1. /* include/dnet/ip.h:449 */
2. #define IP_CHECKSUM_FRAGMENT 1
用途:
- 手动分片时,对每个分片只计算IP头部校验和
- 避免错误地尝试计算不完整的上层协议校验和
示例:
1. /* 发送第一个分片 */
2. ip_checksum(buf1, len1,0);/* 计算IP和上层协议校验和 */
4. /* 发送后续分片 */
5. ip_checksum(buf2, len2, IP_CHECKSUM_FRAGMENT);/* 只计算IP头部 */
4. TCP校验和计算
4.1 TCP伪头部
TCP校验和需要包含伪头部(Pseudo-header),以确保包含IP层信息:
1. +--------+--------+--------+--------+
2. |源IP地址(4字节)|
3. +--------+--------+--------+--------+
4. |目的IP地址(4字节)|
5. +--------+--------+--------+--------+
6. |保留|协议| TCP长度|
7. +--------+--------+--------+--------+
8. | TCP头部+数据|
9. +--------+--------+--------+--------+
4.2 源码分析
4.2.1 tcp_checksum()函数
文件位置: src/ip-util.c:102-109
1. void tcp_checksum(struct ip_hdr *ip,struct tcp_hdr *tcp,size_t len)
2. {
3. int sum;
4. tcp->th_sum =0;/* 清零校验和字段 */
6. /* 1. 计算TCP头部+数据的校验和 */
7. sum = ip_cksum_add(tcp, len,0);
9. /* 2. 加上伪头部信息(协议+长度) */
10. sum = sum + htonl(ip->ip_p + len);
12. /* 3. 加上源和目的IP地址(8字节) */
13. sum = ip_cksum_add(&ip->ip_src,8, sum);
15. /* 4. 进位折叠并取反码 */
16. tcp->th_sum = ip_cksum_carry(sum);
17. }
4.2.2 关键点解析
- 协议长度编码:
c sum=sum+htonl(ip->ip_p+len);
ip->ip_p:协议号(TCP = 6)len:TCP头部+数据长度- 使用
htonl()确保大端序
- IP地址处理:
c sum=ip_cksum_add(&ip->ip_src,8,sum);
- 直接累加8字节(源IP + 目的IP)
ip_cksum_add会自动处理字节序
- 验证接收时的校验和:
/* 验证TCP校验和 */tcp->th_sum =0;/* 先清零 */sum = ip_cksum_add(tcp, len,0);sum = ip_cksum_add(&ip->ip_src,8, sum)+ htonl(ip->ip_p + len);cksum = ip_cksum_carry(sum);/* 如果cksum == 0xFFFF,校验通过 */if(cksum !=0xFFFF){/* 校验失败 */}
4.3 计算示例
假设发送一个TCP SYN包:
1. #include<dnet.h>
2. #include<string.h>
4. void send_tcp_syn(void){
5. u_char packet[128];
6. struct ip_hdr *ip =(struct ip_hdr *)packet;
7. struct tcp_hdr *tcp =(struct tcp_hdr *)(packet +20);
9. /* 构造IP头部 */
10. memset(ip,0,sizeof(*ip));
11. ip->ip_v =4;
12. ip->ip_hl =5;
13. ip->ip_tos =0;
14. ip->ip_len = htons(40);/* IP头20 + TCP头20 */
15. ip->ip_id = rand()&0xffff;
16. ip->ip_off =0;
17. ip->ip_ttl =64;
18. ip->ip_p = IP_PROTO_TCP;
19. ip->ip_sum =0;
20. ip->ip_src = inet_addr("192.168.1.100");
21. ip->ip_dst = inet_addr("192.168.1.200");
23. /* 构造TCP头部 */
24. memset(tcp,0,sizeof(*tcp));
25. tcp->th_sport = htons(12345);
26. tcp->th_dport = htons(80);
27. tcp->th_seq = htonl(1000);
28. tcp->th_ack =0;
29. tcp->th_off =5;
30. tcp->th_flags = TH_SYN;
31. tcp->th_win = htons(65535);
32. tcp->th_urp =0;
33. tcp->th_sum =0;
35. /* 计算校验和 */
36. ip_checksum(packet,40,0);
38. /* 发送数据包... */
39. }
4.4 常见错误
- 忘记清零校验和字段:
-
/* 错误 */ -
sum = ip_cksum_add(tcp, len,0);/* tcp->th_sum未清零 */ -
/* 正确 */ -
tcp->th_sum =0; -
sum = ip_cksum_add(tcp, len,0); -
长度错误:
-
/* 错误:只传TCP头部 */ -
tcp_checksum(ip, tcp, TCP_HDR_LEN); -
/* 正确:包括数据 */ -
tcp_checksum(ip, tcp, TCP_HDR_LEN + data_len); -
字节序错误:
/* 错误:未转换字节序 */sum = sum + ip->ip_p + len;/* 正确:使用htonl */sum = sum + htonl(ip->ip_p + 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 udp_checksum(struct ip_hdr *ip,struct udp_hdr *udp,size_t len)
2. {
3. int sum;
4. udp->uh_sum =0;/* 清零校验和字段 */
6. /* 1. 计算UDP头部+数据的校验和 */
7. sum = ip_cksum_add(udp, len,0);
9. /* 2. 加上伪头部信息(协议+长度) */
10. sum = sum + htonl(IP_PROTO_UDP + len);
12. /* 3. 加上源和目的IP地址(8字节) */
13. sum = ip_cksum_add(&ip->ip_src,8, sum);
15. /* 4. 进位折叠并取反码 */
16. udp->uh_sum = ip_cksum_carry(sum);
18. /* 5. 特殊处理:如果结果为0,替换为0xFFFF */
19. if(!udp->uh_sum)
20. udp->uh_sum =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 =0;/* 接收方不验证 */
4. /* 方法2:不调用udp_checksum() */
5. /* 注意:某些网络设备可能会在传输时添加校验和 */
5.4 验证UDP校验和
1. /* 接收端验证 */
2. int verify_udp_checksum(struct ip_hdr *ip,struct udp_hdr *udp,size_t len){
3. uint16_t received_sum = udp->uh_sum;
5. /* 如果校验和为0,表示发送方禁用了校验和 */
6. if(received_sum ==0){
7. return1;/* 跳过验证 */
8. }
10. /* 重新计算校验和 */
11. udp->uh_sum =0;
12. int sum = ip_cksum_add(udp, len,0);
13. sum = sum + htonl(IP_PROTO_UDP + len);
14. sum = ip_cksum_add(&ip->ip_src,8, sum);
15. uint16_t calc_sum = ip_cksum_carry(sum);
17. /* 恢复原始值 */
18. udp->uh_sum = received_sum;
20. /* 检查是否匹配 */
21. return calc_sum ==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 icmp_checksum(struct icmp_hdr *icmp,size_t len)
2. {
3. int sum;
5. /* 计算ICMP头部+数据的校验和 */
6. sum = ip_cksum_add(icmp, len,0);
8. /* 进位折叠并取反码 */
9. icmp->icmp_cksum = ip_cksum_carry(sum);
10. }
6.2.2 ICMP Echo示例
1. #include<dnet.h>
3. void send_icmp_echo(void){
4. u_char packet[128];
5. struct ip_hdr *ip =(struct ip_hdr *)packet;
6. struct icmp_hdr *icmp =(struct icmp_hdr *)(packet +20);
7. struct icmp_msg_echo *echo =(struct icmp_msg_echo *)(packet +24);
9. /* 构造IP头部 */
10. memset(ip,0,sizeof(*ip));
11. ip->ip_v =4;
12. ip->ip_hl =5;
13. ip->ip_len = htons(28);/* IP头20 + ICMP头4 + 数据8 */
14. ip->ip_id = rand()&0xffff;
15. ip->ip_ttl =64;
16. ip->ip_p = IP_PROTO_ICMP;
17. ip->ip_src = inet_addr("192.168.1.100");
18. ip->ip_dst = inet_addr("8.8.8.8");
20. /* 构造ICMP Echo请求 */
21. icmp->icmp_type = ICMP_ECHO;
22. icmp->icmp_code =0;
23. icmp->icmp_cksum =0;
25. /* Echo数据 */
26. echo->icmp_id = htons(12345);
27. echo->icmp_seq = 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 send_icmp_unreachable(void){
3. u_char packet[128];
4. struct ip_hdr *ip =(struct ip_hdr *)packet;
5. struct icmp_hdr *icmp =(struct icmp_hdr *)(packet +20);
6. struct icmp_msg_quote *quote =(struct icmp_msg_quote *)(packet +24);
7. u_char *orig_ip = packet +28;
9. /* 构造ICMP目的不可达 */
10. icmp->icmp_type = ICMP_UNREACH;
11. icmp->icmp_code = ICMP_UNREACH_PORT;
12. icmp->icmp_cksum =0;
14. /* 引用原始IP包(假设在buf中) */
15. memcpy(orig_ip, 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 ip6_checksum(void*buf,size_t len)
2. {
3. struct ip6_hdr *ip6 =(struct ip6_hdr *)buf;
4. struct ip6_ext_hdr *ext;
5. u_char *p, nxt;
6. int i, sum;
8. nxt = ip6->ip6_nxt;/* 获取下一个头部类型 */
10. /* 跳过扩展头部,找到上层协议头部 */
11. for(i = IP6_HDR_LEN; IP6_IS_EXT(nxt); i +=(ext->ext_len +1)<<3){
12. if(i >=(int)len)return;
13. ext =(struct ip6_ext_hdr *)((u_char *)buf + i);
14. nxt = ext->ext_nxt;
15. }
17. p =(u_char *)buf + i;/* 上层协议头部位置 */
18. len -= i;/* 上层协议数据长度 */
20. if(nxt == IP_PROTO_TCP){
21. struct tcp_hdr *tcp =(struct tcp_hdr *)p;
23. if(len >= TCP_HDR_LEN){
24. tcp->th_sum =0;
25. /* 计算TCP数据 + 协议号(8) + 长度 */
26. sum = ip_cksum_add(tcp, len,0)+ htons(nxt +(u_short)len);
27. /* 加上IPv6伪头部(32字节) */
28. sum = ip_cksum_add(&ip6->ip6_src,32, sum);
29. tcp->th_sum = ip_cksum_carry(sum);
30. }
31. }elseif(nxt == IP_PROTO_UDP){
32. struct udp_hdr *udp =(struct udp_hdr *)p;
34. if(len >= UDP_HDR_LEN){
35. udp->uh_sum =0;
36. sum = ip_cksum_add(udp, len,0)+ htons(nxt +(u_short)len);
37. sum = ip_cksum_add(&ip6->ip6_src,32, sum);
38. /* UDP特殊处理:0替换为0xFFFF */
39. if((udp->uh_sum = ip_cksum_carry(sum))==0)
40. udp->uh_sum =0xffff;
41. }
42. }elseif(nxt == IP_PROTO_ICMPV6){
43. struct icmp_hdr *icmp =(struct icmp_hdr *)p;
45. if(len >= ICMP_HDR_LEN){
46. icmp->icmp_cksum =0;
47. sum = ip_cksum_add(icmp, len,0)+ htons(nxt +(u_short)len);
48. sum = ip_cksum_add(&ip6->ip6_src,32, sum);
49. icmp->icmp_cksum = ip_cksum_carry(sum);
50. }
51. }elseif(nxt == IP_PROTO_ICMP || nxt == IP_PROTO_IGMP){
52. /* 注意:这里处理的是IPv4封装的ICMP */
53. struct icmp_hdr *icmp =(struct icmp_hdr *)p;
55. if(len >= ICMP_HDR_LEN){
56. icmp->icmp_cksum =0;
57. /* 没有伪头部 */
58. sum = ip_cksum_add(icmp, len,0);
59. icmp->icmp_cksum = ip_cksum_carry(sum);
60. }
61. }
62. }
7.2.2 关键点
- 扩展头部处理:
c#defineIP6_IS_EXT(n)\((n)==IP_PROTO_HOPOPTS||(n)==IP_PROTO_DSTOPTS||\(n)==IP_PROTO_ROUTING||(n)==IP_PROTO_FRAGMENT)
- 跳过所有扩展头部
- 找到真正的上层协议头部
- 伪头部差异:
- IPv4:8字节(源IP + 目的IP)
- IPv6:32字节(源IPv6 + 目的IPv6 + 保留 + 下一个头)
- 协议号编码:
c sum=sum+htons(nxt+(u_short)len);
- 使用
htons而非htonl(因为协议号是1字节)
7.3 ICMPv6示例
1. #include<dnet.h>
3. void send_icmpv6_echo(void){
4. u_char packet[128];
5. struct ip6_hdr *ip6 =(struct ip6_hdr *)packet;
6. struct icmp_hdr *icmp =(struct icmp_hdr *)(packet +40);
7. struct icmp_msg_echo *echo =(struct icmp_msg_echo *)(packet +44);
9. /* 构造IPv6头部 */
10. memset(ip6,0,sizeof(*ip6));
11. ip6->ip6_vfc =0x60;/* IPv6 */
12. ip6->ip6_plen = htons(12);/* ICMP头4 + 数据8 */
13. ip6->ip6_nxt = IP_PROTO_ICMPV6;
14. ip6->ip6_hlim =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 = ICMP_ECHO;/* ICMPv6和ICMPv4使用相同的类型 */
20. icmp->icmp_code =0;
21. icmp->icmp_cksum =0;
23. echo->icmp_id = htons(12345);
24. echo->icmp_seq = 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 CRC32C_POLY 0x1EDC6F41
3. #define CRC32C(c,d)(c=(c>>8)^crc_c[(c^(d))&0xFF])
5. staticunsignedlong 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 _crc32c(unsignedchar*buf,int len)
2. {
3. int i;
4. unsignedlong crc32 =~0L;/* 初始值:全1 */
5. unsignedlong result;
6. unsignedchar byte0, byte1, byte2, byte3;
8. /* 对每个字节应用CRC32C */
9. for(i =0; i < len; i++){
10. CRC32C(crc32, buf[i]);
11. }
13. result =~crc32;/* 最终异或 */
15. /* 字节序翻转(输出转换) */
16. byte0 = result &0xff;
17. byte1 =(result >>8)&0xff;
18. byte2 =(result >>16)&0xff;
19. byte3 =(result >>24)&0xff;
20. crc32 =((byte0 <<24)|(byte1 <<16)|(byte2 <<8)| byte3);
22. return crc32;
23. }
8.2.3 在ip_checksum()中的调用
文件位置: src/ip-util.c:176-181
1. }elseif(ip->ip_p == IP_PROTO_SCTP){
2. struct sctp_hdr *sctp =(struct sctp_hdr *)((u_char *)ip + hl);
3. if(len >= SCTP_HDR_LEN){
4. sctp->sh_sum =0;
5. sctp->sh_sum = htonl(_crc32c((u_char *)sctp, len));
6. }
7. }
8.3 CRC32-C计算过程
1. 1.初始化:crc32 =0xFFFFFFFF
3. 2.对每个字节:
4. crc32 =(crc32 >>8)^ table[(crc32 ^byte)&0xFF]
6. 3.最终异或:crc32 = crc32 ^0xFFFFFFFF
8. 4.字节序翻转(大端序输出)
8.4 SCTP示例
1. #include<dnet.h>
3. void send_sctp_init(void){
4. u_char packet[128];
5. struct ip_hdr *ip =(struct ip_hdr *)packet;
6. struct sctp_hdr *sctp =(struct sctp_hdr *)(packet +20);
7. struct sctp_chunkhdr_init *init =
8. (struct sctp_chunkhdr_init *)(packet +32);
10. /* 构造IP头部 */
11. memset(ip,0,sizeof(*ip));
12. ip->ip_v =4;
13. ip->ip_hl =5;
14. ip->ip_len = htons(68);/* IP头20 + SCTP头12 + INIT块36 */
15. ip->ip_id = rand()&0xffff;
16. ip->ip_ttl =64;
17. ip->ip_p = IP_PROTO_SCTP;
18. ip->ip_src = inet_addr("192.168.1.100");
19. ip->ip_dst = inet_addr("192.168.1.200");
21. /* 构造SCTP头部 */
22. sctp->sh_sport = htons(12345);
23. sctp->sh_dport = htons(80);
24. sctp->sh_vtag =0;/* INIT时为0 */
25. sctp->sh_sum =0;
27. /* 构造INIT块 */
28. init->chunkhdr.sch_type = SCTP_INIT;
29. init->chunkhdr.sch_flags =0;
30. init->chunkhdr.sch_length = htons(36);
31. init->schi_itag = htonl(0x12345678);
32. init->schi_arwnd = htonl(32768);
33. init->schi_nos = htons(1);
34. init->schi_nis = htons(1);
35. init->schi_itsn = 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 = IP6_HDR_LEN; IP6_IS_EXT(nxt); i +=(ext->ext_len +1)<<3){
3. if(i >=(int)len)return;
4. ext =(struct ip6_ext_hdr *)((u_char *)buf + i);
5. nxt = 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 send_ipv6_tcp(void){
4. u_char packet[128];
5. struct ip6_hdr *ip6 =(struct ip6_hdr *)packet;
6. struct tcp_hdr *tcp =(struct tcp_hdr *)(packet +40);
8. /* 构造IPv6头部 */
9. memset(ip6,0,sizeof(*ip6));
10. ip6->ip6_vfc =0x60;
11. ip6->ip6_plen = htons(20);/* TCP头20 */
12. ip6->ip6_nxt = IP_PROTO_TCP;
13. ip6->ip6_hlim =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 = htons(12345);
20. tcp->th_dport = htons(80);
21. tcp->th_seq = htonl(1000);
22. tcp->th_ack =0;
23. tcp->th_off =5;
24. tcp->th_flags = TH_SYN;
25. tcp->th_win = htons(65535);
26. tcp->th_sum =0;
28. /* 计算校验和 */
29. ip6_checksum(packet,60);
31. /* 发送数据包... */
32. }
10. 校验和算法详解
10.1 ipcksumadd()函数
文件位置: src/ip-util.c:185-238
1. int ip_cksum_add(constvoid*buf,size_t len,int cksum)
2. {
3. uint16_t*sp =(uint16_t*)buf;
4. int n, sn;
6. /* 处理奇数长度的特殊情况 */
7. if(len ==1){
8. constuint8_t*b = buf;
9. return htons(((uint16_t)*b)<<8);
10. }
12. sn = len /2;/* 16位字的数量 */
13. n =(sn +15)/16;/* 循环次数(每16个字一次) */
15. /* 使用Duff's Device展开循环 */
16. switch(sn %16){
17. case0:do{
18. cksum +=*sp++;
19. case15:
20. cksum +=*sp++;
21. case14:
22. cksum +=*sp++;
23. case13:
24. cksum +=*sp++;
25. case12:
26. cksum +=*sp++;
27. case11:
28. cksum +=*sp++;
29. case10:
30. cksum +=*sp++;
31. case9:
32. cksum +=*sp++;
33. case8:
34. cksum +=*sp++;
35. case7:
36. cksum +=*sp++;
37. case6:
38. cksum +=*sp++;
39. case5:
40. cksum +=*sp++;
41. case4:
42. cksum +=*sp++;
43. case3:
44. cksum +=*sp++;
45. case2:
46. cksum +=*sp++;
47. case1:
48. cksum +=*sp++;
49. }while(--n >0);
50. }
52. /* 处理奇数长度 */
53. if(len &1)
54. cksum += htons(*(u_char *)sp <<8);
56. return(cksum);
57. }
10.2 Duff’s Device优化
Duff’s Device是一种循环展开技术:
1. switch(sn %16){
2. case0:do{
3. cksum +=*sp++;
4. case15: cksum +=*sp++;
5. case14: cksum +=*sp++;
6. /* ... */
7. case1: cksum +=*sp++;
8. }while(--n >0);
9. }
优点:
- 减少循环开销
- 适合处理16的倍数的数据
- 兼容所有C编译器
10.3 ipcksumcarry()宏
文件位置: include/dnet/ip.h:453-454
1. #define ip_cksum_carry(x) \
2. (x =(x >>16)+(x &0xffff),(~(x +(x >>16))&0xffff))
展开后:
1. x =(x >>16)+(x &0xffff);/* 第一次折叠:将高16位加到低16位 */
2. x = x +(x >>16);/* 第二次折叠:处理可能的新进位 */
3. x =~x &0xffff;/* 取反码 */
4. return x;
10.4 完整计算流程
1. 输入:数据缓冲区 buf,长度 len
3. 1.初始化:
4. sum =0
6. 2.累加16位字:
7. for(i =0; i < len/2; i++){
8. sum +=*(uint16_t*)buf++;
9. }
11. 3.处理奇数字节:
12. if(len %2==1){
13. sum +=(uint16_t)(*(uint8_t*)buf)<<8;
14. }
16. 4.折叠进位:
17. while(sum >>16){
18. sum =(sum &0xFFFF)+(sum >>16);
19. }
21. 5.取反码:
22. sum =~sum;
24. 6.返回:
25. return sum;
10.5 手动计算示例
假设计算数据: 0x000x010xF20xF3
- 按16位字分组:
-
字1:0x0001 -
字2:0xF2F3 -
求和: “` 0x0001
- 0xF2F3
0xF2F4 “`
- 折叠进位(如果需要):
(无需折叠,因为高16位为0)
-
0xF2F4 -
取反码:
-
~0xF2F4=0x0D0B -
最终校验和:
0x0D0B
11. CRC32-C算法
11.1 CRC基础概念
CRC(Cyclic Redundancy Check,循环冗余校验)是一种基于多项式除法的错误检测算法:
- 多项式:表示除数
- 消息:被除数
- 余数:校验值
11.2 CRC32-C多项式
1. CRC32-C多项式:x^32+ x^28+ x^27+ x^26+ x^25+ x^23+ x^22+
2. x^20+ x^19+ x^18+ x^14+ x^13+ x^11+ x^10+
3. x^9+ x^8+ x^6+1
5. 十六进制表示:0x1EDC6F41
11.3 查表法实现
11.3.1 预计算表
文件位置: src/crc32ct.h:15-80
1. staticunsignedlong crc_c[256]={
2. 0x00000000L,0xF26B8303L,0xE13B70F7L,0x1350F3F4L,
3. /* ... 256个值 ... */
4. };
每个值对应: crc32(0x00000000,byte)
11.3.2 CRC32C宏
1. #define CRC32C(c,d)(c=(c>>8)^crc_c[(c^(d))&0xFF])
参数:
c:当前CRC值d:输入字节
操作:
- 将当前CRC右移8位
- 与查表值异或
- 查表索引:
(c^d)&0xFF
11.4 完整计算过程
1. unsignedlong calculate_crc32c(constunsignedchar*data,int len){
2. unsignedlong crc =0xFFFFFFFF;
4. for(int i =0; i < len; i++){
5. crc =(crc >>8)^ crc_c[(crc ^ data[i])&0xFF];
6. }
8. crc =~crc;
10. /* 字节序翻转 */
11. return((crc &0xFF)<<24)|
12. ((crc >>8&0xFF)<<16)|
13. ((crc >>16&0xFF)<<8)|
14. (crc >>24&0xFF);
15. }
11.5 为什么需要字节序翻转?
CRC32-C标准规定输出为大端序,但:
- Intel x86是小端序
- 直接计算得到的是小端序
- 需要翻转字节序以符合标准
11.6 硬件加速
现代CPU支持CRC32指令(SSE4.2):
1. #ifdef __SSE4_2__
2. #include<smmintrin.h>
4. uint32_t crc32c_hardware(constuint8_t*data,size_t len){
5. uint32_t crc =0xFFFFFFFF;
7. /* 使用硬件指令 */
8. for(size_t i =0; i < len; i++){
9. crc = _mm_crc32_u8(crc, 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 htons(uint16_t hostshort);
3. uint32_t htonl(uint32_t hostlong);
5. /* 网络字节序 → 主机字节序 */
6. uint16_t ntohs(uint16_t netshort);
7. uint32_t ntohl(uint32_t netlong);
12.2 平台差异
| 平台 | 字节序 | 影响 | 处理方式 | | — | — | — | — | | x86/x64 | 小端序 | 需要转换 | htons/htonl | | ARM | 可配置 | 可能需要转换 | 运行时检测 | | PowerPC | 大端序 | 可能无需转换 | 条件编译 | | MIPS | 可配置 | 需要检测 | 运行时检测 |
12.3 字节序检测
1. /* 检测字节序 */
2. int is_little_endian(void){
3. uint16_t x =0x0001;
4. return*(uint8_t*)&x ==1;
5. }
7. /* 条件编译 */
8. #if DNET_BYTESEX == DNET_BIG_ENDIAN
9. /* 大端序代码 */
10. #elif DNET_BYTESEX == DNET_LIL_ENDIAN
11. /* 小端序代码 */
12. #else
13. #error"Unknown byte order"
14. #endif
12.4 对齐问题
某些平台对内存对齐有严格要求:
1. /* 不安全:可能导致总线错误 */
2. uint16_t*p =(uint16_t*)buffer;
3. uint16_t value =*p;/* 如果buffer未对齐 */
5. /* 安全:逐字节读取 */
6. uint16_t read_u16(constuint8_t*p){
7. return(p[0]<<8)| p[1];
8. }
libdnet的处理:
1. /* src/ip-util.c:185 */
2. uint16_t*sp =(uint16_t*)buf;
假设:
buf是对齐的缓冲区- 或者平台支持未对齐访问
12.5 不同平台的校验和结果
理论上,所有平台应该产生相同的校验和:
1. /* 测试代码 */
2. void test_checksum_cross_platform(void){
3. uint8_t 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 sum = ip_cksum_add(data,20,0);
8. uint16_t cksum = ip_cksum_carry(sum);
10. /* 所有平台应该得到相同结果:0x1234 */
11. assert(cksum ==0x1234);
12. }
13. 性能优化技巧
13.1 避免重复计算
1. /* 错误:每次都重新计算 */
2. for(int i =0; i < n; i++){
3. ip_checksum(buf[i], len,0);
4. /* 修改数据 */
5. ip_checksum(buf[i], len,0);/* 重复计算!*/
6. }
8. /* 正确:只在修改后计算 */
9. for(int i =0; i < n; i++){
10. ip_checksum(buf[i], len,0);
11. }
13.2 批量计算
1. /* 批量计算校验和 */
2. void batch_calculate_checksums(void**bufs,size_t*lens,int count){
3. for(int i =0; i < count; i++){
4. ip_checksum(bufs[i], lens[i],0);
5. }
6. }
13.3 缓存友好访问
1. /* 顺序访问,缓存友好 */
2. int sum_sequential(constuint16_t*data,int n){
3. int sum =0;
4. for(int i =0; i < n; i++){
5. sum += data[i];
6. }
7. return sum;
8. }
10. /* 跳跃访问,缓存不友好 */
11. int sum_strided(constuint16_t*data,int n,int stride){
12. int sum =0;
13. for(int i =0; i < n; i += stride){
14. sum += data[i];
15. }
16. return sum;
17. }
13.4 使用SIMD指令
1. #ifdef __AVX2__
2. #include<immintrin.h>
4. int ip_cksum_add_avx2(constvoid*buf,size_t len,int cksum){
5. constuint16_t*p =(constuint16_t*)buf;
6. __m256i sum_vec = _mm256_setzero_si256();
8. /* 每16个uint16_t(32字节)处理一次 */
9. for(size_t i =0; i +16<= len/2; i +=16){
10. __m256i data = _mm256_loadu_si256((__m256i *)&p[i]);
11. sum_vec = _mm256_add_epi16(sum_vec, data);
12. }
14. /* 折叠256位到16位 */
15. /* ... */
17. return cksum;
18. }
19. #endif
13.5 预分配缓冲区
1. /* 错误:每次都malloc */
2. void send_packets(int n){
3. for(int i =0; i < n; i++){
4. uint8_t*buf = malloc(1500);
5. /* 构造数据包 */
6. ip_checksum(buf,1500,0);
7. send(buf);
8. free(buf);
9. }
10. }
12. /* 正确:重用缓冲区 */
13. void send_packets_optimized(int n){
14. uint8_t buf[1500];
15. for(int i =0; i < n; 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. void debug_checksum(void*buf,size_t len){
2. struct ip_hdr *ip =(struct ip_hdr *)buf;
4. /* 打印IP头部 */
5. printf("IP Header:\n");
6. printf(" src: %s\n", ip_ntoa(&ip->ip_src));
7. printf(" dst: %s\n", ip_ntoa(&ip->ip_dst));
8. printf(" proto: %d\n", ip->ip_p);
9. printf(" sum: 0x%04x\n", ntohs(ip->ip_sum));
11. /* 重新计算并比较 */
12. uint16_t original_sum = ip->ip_sum;
13. ip->ip_sum =0;
14. ip_checksum(buf, len,0);
16. printf(" calc sum: 0x%04x\n", ntohs(ip->ip_sum));
18. if(original_sum != ip->ip_sum){
19. printf(" ERROR: Checksum mismatch!\n");
20. }
22. ip->ip_sum = original_sum;/* 恢复 */
23. }
14.2 UDP校验和为0
症状:UDP数据包被丢弃
原因:UDP校验和为0表示禁用,但接收方强制验证
解决:
1. /* 确保UDP校验和不为0 */
2. if(udp->uh_sum ==0){
3. udp->uh_sum =0xffff;
4. }
14.3 奇数长度处理
问题:奇数长度数据可能导致错误
解决:libdnet已自动处理
1. /* src/ip-util.c:234-235 */
2. if(len &1)
3. cksum += htons(*(u_char *)sp <<8);
14.4 分片处理
问题:分片数据包校验和错误
原因:对所有分片计算上层协议校验和
解决:
1. /* 使用IP_CHECKSUM_FRAGMENT标志 */
2. ip_checksum(frag_buf, frag_len, IP_CHECKSUM_FRAGMENT);
14.5 IPv6扩展头部
问题:IPv6校验和不正确
原因:未跳过扩展头部
解决:libdnet的 ip6_checksum()已自动处理
14.6 性能问题
症状:校验和计算太慢
优化:
- 使用硬件CRC32(SCTP)
- 启用SIMD优化
- 批量计算
- 缓存友好的数据布局
14.7 调试工具
1. /* 打印校验和计算过程 */
2. void trace_checksum(constvoid*buf,size_t len){
3. constuint16_t*p =(constuint16_t*)buf;
4. int sum =0;
6. printf("Checksum calculation:\n");
7. for(size_t i =0; i < len/2; i++){
8. printf(" [%2zu] 0x%04x", i, ntohs(p[i]));
9. int old_sum = sum;
10. sum += ntohs(p[i]);
11. printf(" -> sum: 0x%08x\n", sum);
13. if(sum < old_sum){
14. printf(" (carry added)\n");
15. sum +=1;
16. }
17. }
19. if(len %2==1){
20. uint8_t last =((constuint8_t*)buf)[len-1];
21. printf(" [last] 0x%02x00\n", last);
22. sum +=(last <<8);
23. }
25. printf("Final sum: 0x%08x\n", sum);
26. sum =(sum >>16)+(sum &0xFFFF);
27. sum =~(sum +(sum >>16))&0xFFFF;
28. printf("Checksum: 0x%04x\n", 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 send_tcp_syn(constchar*src_ip,constchar*dst_ip,
7. uint16_t src_port,uint16_t dst_port){
8. ip_t*ip;
9. uint8_t packet[64];
10. struct ip_hdr *ip_hdr =(struct ip_hdr *)packet;
11. struct tcp_hdr *tcp_hdr =(struct tcp_hdr *)(packet +20);
12. struct addr src, dst;
14. /* 打开IP接口 */
15. if((ip = ip_open())== NULL){
16. perror("ip_open");
17. return-1;
18. }
20. /* 构造IP头部 */
21. memset(ip_hdr,0,sizeof(*ip_hdr));
22. ip_hdr->ip_v =4;
23. ip_hdr->ip_hl =5;
24. ip_hdr->ip_tos =0;
25. ip_hdr->ip_len = htons(40);/* IP头20 + TCP头20 */
26. ip_hdr->ip_id = rand()&0xffff;
27. ip_hdr->ip_off =0;
28. ip_hdr->ip_ttl =64;
29. ip_hdr->ip_p = IP_PROTO_TCP;
30. ip_hdr->ip_sum =0;
32. /* 转换IP地址 */
33. addr_aton(src_ip,&src);
34. addr_aton(dst_ip,&dst);
35. ip_hdr->ip_src = src.addr_ip;
36. ip_hdr->ip_dst = dst.addr_ip;
38. /* 构造TCP头部 */
39. memset(tcp_hdr,0,sizeof(*tcp_hdr));
40. tcp_hdr->th_sport = htons(src_port);
41. tcp_hdr->th_dport = htons(dst_port);
42. tcp_hdr->th_seq = htonl(rand());
43. tcp_hdr->th_ack =0;
44. tcp_hdr->th_off =5;
45. tcp_hdr->th_flags = TH_SYN;
46. tcp_hdr->th_win = htons(65535);
47. tcp_hdr->th_urp =0;
48. tcp_hdr->th_sum =0;
50. /* 计算校验和 */
51. ip_checksum(packet,40,0);
53. /* 发送数据包 */
54. if(ip_send(ip, 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, src_port, dst_ip, dst_port);
62. printf(" IP checksum: 0x%04x\n", ntohs(ip_hdr->ip_sum));
63. printf(" TCP checksum: 0x%04x\n", ntohs(tcp_hdr->th_sum));
65. ip_close(ip);
66. return0;
67. }
69. int main(int argc,char*argv[]){
70. if(argc !=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 send_tcp_syn(argv[1], argv[2], atoi(argv[3]), 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 send_ping(constchar*dst_ip){
8. ip_t*ip;
9. uint8_t packet[128];
10. struct ip_hdr *ip_hdr =(struct ip_hdr *)packet;
11. struct icmp_hdr *icmp_hdr =(struct icmp_hdr *)(packet +20);
12. struct icmp_msg_echo *echo =(struct icmp_msg_echo *)(packet +24);
13. struct addr dst;
15. if((ip = ip_open())== NULL){
16. perror("ip_open");
17. return-1;
18. }
20. /* 构造IP头部 */
21. memset(ip_hdr,0,sizeof(*ip_hdr));
22. ip_hdr->ip_v =4;
23. ip_hdr->ip_hl =5;
24. ip_hdr->ip_len = htons(28);/* IP头20 + ICMP头4 + Echo数据8 */
25. ip_hdr->ip_id = rand()&0xffff;
26. ip_hdr->ip_ttl =64;
27. ip_hdr->ip_p = IP_PROTO_ICMP;
28. ip_hdr->ip_src = inet_addr("0.0.0.0");/* 由系统填充 */
30. addr_aton(dst_ip,&dst);
31. ip_hdr->ip_dst = dst.addr_ip;
33. /* 构造ICMP Echo请求 */
34. icmp_hdr->icmp_type = ICMP_ECHO;
35. icmp_hdr->icmp_code =0;
36. icmp_hdr->icmp_cksum =0;
38. echo->icmp_id = htons(getpid());
39. echo->icmp_seq = 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, packet,28)<0){
48. perror("ip_send");
49. ip_close(ip);
50. return-1;
51. }
53. printf("Sent ICMP Echo to %s\n", dst_ip);
54. printf(" IP checksum: 0x%04x\n", ntohs(ip_hdr->ip_sum));
55. printf(" ICMP checksum: 0x%04x\n", ntohs(icmp_hdr->icmp_cksum));
57. ip_close(ip);
58. return0;
59. }
61. int main(int argc,char*argv[]){
62. if(argc !=2){
63. fprintf(stderr,"Usage: %s <dst_ip>\n", argv[0]);
64. return1;
65. }
67. srand(time(NULL));
68. return send_ping(argv[1]);
69. }
15.3 验证接收到的数据包
1. #include<dnet.h>
2. #include<stdio.h>
3. #include<string.h>
5. int verify_ip_packet(constuint8_t*packet,size_t len){
6. struct ip_hdr *ip_hdr =(struct ip_hdr *)packet;
7. uint16_t original_sum;
8. int valid =1;
10. /* 检查最小长度 */
11. if(len < IP_HDR_LEN){
12. printf("Packet too short: %zu bytes\n", len);
13. return0;
14. }
16. /* 验证IP头部校验和 */
17. original_sum = ip_hdr->ip_sum;
18. ip_hdr->ip_sum =0;
19. ip_checksum((void*)packet, len,0);
21. if(original_sum != ip_hdr->ip_sum){
22. printf("IP checksum mismatch:\n");
23. printf(" Expected: 0x%04x\n", ntohs(original_sum));
24. printf(" Calculated: 0x%04x\n", ntohs(ip_hdr->ip_sum));
25. valid =0;
26. }
28. /* 恢复原始值 */
29. ip_hdr->ip_sum = original_sum;
31. /* 根据协议验证上层校验和 */
32. int ip_hl = ip_hdr->ip_hl <<2;
33. int payload_len = ntohs(ip_hdr->ip_len)- ip_hl;
34. uint8_t*payload =(uint8_t*)packet + ip_hl;
36. switch(ip_hdr->ip_p){
37. case IP_PROTO_TCP:{
38. struct tcp_hdr *tcp_hdr =(struct tcp_hdr *)payload;
39. uint16_t tcp_sum = tcp_hdr->th_sum;
41. tcp_hdr->th_sum =0;
42. tcp_checksum(ip_hdr, tcp_hdr, payload_len);
44. if(tcp_sum != tcp_hdr->th_sum){
45. printf("TCP checksum mismatch\n");
46. valid =0;
47. }
48. tcp_hdr->th_sum = tcp_sum;
49. break;
50. }
51. case IP_PROTO_UDP:{
52. struct udp_hdr *udp_hdr =(struct udp_hdr *)payload;
54. if(udp_hdr->uh_sum !=0){
55. uint16_t udp_sum = udp_hdr->uh_sum;
57. udp_hdr->uh_sum =0;
58. udp_checksum(ip_hdr, udp_hdr, payload_len);
60. if(udp_sum != udp_hdr->uh_sum){
61. printf("UDP checksum mismatch\n");
62. valid =0;
63. }
64. udp_hdr->uh_sum = udp_sum;
65. }
66. break;
67. }
68. case IP_PROTO_ICMP:{
69. struct icmp_hdr *icmp_hdr =(struct icmp_hdr *)payload;
70. uint16_t icmp_sum = icmp_hdr->icmp_cksum;
72. icmp_hdr->icmp_cksum =0;
73. icmp_checksum(icmp_hdr, payload_len);
75. if(icmp_sum != icmp_hdr->icmp_cksum){
76. printf("ICMP checksum mismatch\n");
77. valid =0;
78. }
79. icmp_hdr->icmp_cksum = icmp_sum;
80. break;
81. }
82. }
84. return valid;
85. }
15.4 使用dnet命令行工具
1. # 构造并计算IP数据包校验和
2. echo "Hello, World!"| dnet ip src 192.168.1.100 dst 192.168.1.200 \
3. proto 6 ttl 64| xxd
5. # 构造TCP段
6. echo "GET / HTTP/1.1\r\n"| dnet tcp sport 12345 dport 80 \
7. flags SYN win 65535| xxd
9. # 组合使用(管道)
10. cat <<EOF | dnet ip src 192.168.1.100 dst 192.168.1.200 proto 6 \
11. | dnet tcp sport 12345 dport 80 flags SYN win 65535 \
12. | xxd
13. EOF
总结
本文档深入分析了libdnet中校验和计算的实现,涵盖了:
- 九种协议的校验和:IPv4、TCP、UDP、ICMP、ICMPv6、IPv6、SCTP
- 两种算法:Internet Checksum和CRC32-C
- 核心函数:
ip_checksum()、tcp_checksum()、udp_checksum()、icmp_checksum()、ip6_checksum() - 底层实现:
ip_cksum_add()、ip_cksum_carry()、_crc32c() - 性能优化:Duff’s Device、SIMD、硬件加速
- 跨平台:字节序处理、对齐问题
- 实际应用:完整的示例代码
通过本文档,读者可以深入理解libdnet的校验和计算机制,并正确应用于网络编程中。
- 公众号:安全狗的自我修养
- vx:2207344074
- http://gitee.com/haidragon
- http://github.com/haidragon
- bilibili:haidragonx
#
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全狗的自我修养 haidragon haidragon《跨平台底层网络库libdnet源码分析系列(九)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论