跨平台底层网络库libdnet源码分析系列(十六)
官网:http://securitytech.cc
源码分析mettle后门工具学习 所使用的依赖库

第 16 章:IPv6 完整支持探析
16.1 IPv6 协议概述
16.1.1 IPv6 的产生背景
随着互联网的飞速发展,IPv4 地址空间逐渐枯竭。IPv6(Internet Protocol version 6)作为 IPv4 的继任者,由 IETF 于 1995 年标准化(RFC 2460),提供了以下关键改进:
- 巨大的地址空间:从 IPv4 的 32 位扩展到 128 位,提供 2^128 个地址
- 简化的报文头:固定 40 字节头部,提高路由处理效率
- 内置安全性:原生支持 IPsec
- 自动配置:无状态地址自动配置(SLAAC)
- 更好的 QoS 支持:流标签字段支持
16.1.2 libdnet 中的 IPv6 支持架构
libdnet 库在底层网络编程层面提供了完整的 IPv6 支持,主要包含以下模块:
IPv6支持模块结构├──IPv6地址处理(addr.c, addr-util.c)├──IPv6报文头封装(ip6.h)├──ICMPv6协议支持(icmpv6.h)├──IPv6校验和计算(ip6.c)└──跨平台接口适配(intf-win32.c, intf-linux.c 等)
16.2 IPv6 数据结构深度分析
16.2.1 IPv6 地址结构定义
源码位置: include/dnet/ip6.h
关键技术点:
- 紧凑内存布局:使用
__packed__属性确保结构体无内存对齐填充 - 灵活的访问方式:可通过
data数组直接访问原始字节 - 跨平台兼容:支持大端和小端字节序
16.2.2 IPv6 报文头结构
源码分析:
struct ip6_hdr {union{struct ip6_hdr_ctl {uint32_t ip6_un1_flow;/* 20 位流标识符 + 8 位流量类别 */uint16_t ip6_un1_plen;/* 有效载荷长度 */uint8_t ip6_un1_nxt;/* 下一个头部类型 */uint8_t ip6_un1_hlim;/* 跳数限制 */} ip6_un1;uint8_t ip6_un2_vfc;/* 4 位版本 + 4 位流量类别高 4 位 */} ip6_ctlun;ip6_addr_t ip6_src;/* 源 IPv6 地址 */ip6_addr_t ip6_dst;/* 目的 IPv6 地址 */} __attribute__((__packed__));
字段详解:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16.2.3 统一的地址结构
源码位置: include/dnet/addr.h
struct addr {uint16_t addr_type;/* 地址类型:ADDR_TYPE_IP6 = 3 */uint16_t addr_bits;/* 地址前缀长度 */union{eth_addr_t __eth;ip_addr_t __ip;ip6_addr_t __ip6;/* IPv6 地址 */uint8_t __data8[16];uint16_t __data16[8];uint32_t __data32[4];} __addr_u;};
设计优势:
- 统一接口:通过
addr_type区分 IPv4/IPv6/MAC 地址 - 灵活访问:支持按 8 位、16 位、32 位、64 位访问地址数据
- 多播支持:可方便地判断组播地址(首字节为 0xFF)
16.3 IPv6 核心功能实现分析
16.3.1 IPv6 报文头快速打包函数
源码位置: include/dnet/ip6.h
staticinlinevoid ip6_pack_hdr(void*buf,uint8_t c,uint32_t l,uint16_t plen,uint8_t nxt,uint8_t hlim,void*src,void*dst){struct ip6_hdr {uint32_t ip6_v_c_l;/* 版本 + 流量类别 + 流标签 */uint32_t ip6_plen_nxt_hlim;/* 载荷长度 + 下一头部 + 跳限制 */ip6_addr_t ip6_src;ip6_addr_t ip6_dst;}*hdr =(struct ip6_hdr *)buf;hdr->ip6_v_c_l =(6<<28)|(c <<20)|(IP6_FLOWLABEL_MASK & l);hdr->ip6_plen_nxt_hlim =(htons(plen)<<16)|(nxt <<8)| hlim;memcpy(&hdr->ip6_src, src, IP6_ADDR_LEN);memcpy(&hdr->ip6_dst, dst, IP6_ADDR_LEN);}
实现要点:
- 内联优化:使用
inline减少函数调用开销 - 字节序转换:
plen使用htons()转换为网络字节序 - 位域操作:精确控制每个字段的位偏移
- 内存拷贝:直接使用
memcpy复制 128 位地址
16.3.2 IPv6 地址字符串转换
(1)IPv6 地址转字符串(ip6_ntop)
源码位置: src/addr-util.c
char*ip6_ntop(constip6_addr_t*ip6,char*dst,size_t len){uint16_t data[IP6_ADDR_LEN /2];struct{int base, len;} best, cur;char*p = dst;int i;// 复制到 16 位数组(网络字节序)for(i =0; i < IP6_ADDR_LEN /2; i++){data[i]= ip6->data[2* i]<<8;data[i]|= ip6->data[2* i +1];}// 查找最长的连续零段(RFC 5952)best.len = cur.len =0;best.base = cur.base =-1;for(i =0; i < IP6_ADDR_LEN; i +=2){if(data[i /2]==0){if(cur.base ==-1){cur.base = i;cur.len =0;}elsecur.len +=2;}else{if(cur.base !=-1){if(best.base ==-1|| cur.len > best.len)best = cur;cur.base =-1;}}}// 生成压缩格式的 IPv6 字符串if(best.base !=-1&& best.len <2)best.base =-1;if(best.base ==0)*p++=':';for(i =0; i < IP6_ADDR_LEN; i +=2){if(i == best.base){*p++=':';i += best.len;}elseif(i ==12&& best.base ==0&&(best.len ==10||(best.len ==8&& data[5]==0xffff))){// IPv4 映射地址 (::ffff:10.0.0.1)if(ip_ntop((ip_addr_t*)&data[6], p, len -(p - dst))== NULL)return(NULL);return(dst);}elsep += sprintf(p,"%x:", data[i /2]);}// 处理末尾字符if(best.base +2+ best.len == IP6_ADDR_LEN){*p ='\0';}elsep[-1]='\0';return(dst);}
算法亮点:
- 零压缩算法:自动查找最长连续零段并使用
::压缩 - RFC 5952 合规:遵循 IPv6 地址文本表示最佳实践
- IPv4 映射支持:自动识别并转换为
::ffff:x.x.x.x格式 - 边界检查:确保输出缓冲区足够大(至少 46 字节)
(2)字符串转 IPv6 地址(ip6_pton)
int ip6_pton(constchar*p,ip6_addr_t*ip6){uint16_t data[8],*u =(uint16_t*)ip6->data;int i, j, n, z =-1;// z 记录 :: 的位置char*ep;long l;if(*p ==':')p++;for(n =0; n <8; n++){l = strtol(p,&ep,16);if(ep == p){// 遇到 :: 压缩标记if(ep[0]==':'&& z ==-1){z = n;p++;}elseif(ep[0]=='\0'){break;}else{return(-1);}}elseif(ep[0]=='.'&& n <=6){// IPv4 后缀格式(如 ::ffff:192.168.1.1)if(ip_pton(p,(ip_addr_t*)(data + n))<0)return(-1);n +=2;ep ="";break;}elseif(l >=0&& l <=0xffff){data[n]= htons((uint16_t)l);if(ep[0]=='\0'){n++;break;}elseif(ep[0]!=':'|| ep[1]=='\0')return(-1);p = ep +1;}elsereturn(-1);}// 验证格式并填充零段if(n ==0||*ep !='\0'||(z ==-1&& n !=8))return(-1);// 复制 :: 之前的部分for(i =0; i < z; i++){u[i]= data[i];}// 填充零段while(i <8-(n - z -1)){u[i++]=0;}// 复制 :: 之后的部分for(j = z +1; i <8; i++, j++){u[i]= data[j];}return(0);}
关键技术:
- 灵活的输入解析:支持完整格式、压缩格式、IPv4 映射格式
- 错误检测:严格验证输入格式,防止非法地址
- 零填充算法:正确计算并填充
::代表的零段
16.3.3 IPv6 校验和计算
源码位置: src/ip6.c
void ip6_checksum(void*buf,size_t len){struct ip6_hdr *ip6 =(struct ip6_hdr *)buf;struct ip6_ext_hdr *ext;u_char *p, nxt;int i, sum;nxt = ip6->ip6_nxt;// 跳过所有扩展头部for(i = IP6_HDR_LEN; IP6_IS_EXT(nxt); i +=(ext->ext_len +1)<<3){if(i >=(int)len)return;ext =(struct ip6_ext_hdr *)((u_char *)buf + i);nxt = ext->ext_nxt;}p =(u_char *)buf + i;len -= i;// 根据下一头部类型计算校验和if(nxt == IP_PROTO_TCP){struct tcp_hdr *tcp =(struct tcp_hdr *)p;if(len >= TCP_HDR_LEN){tcp->th_sum =0;sum = ip_cksum_add(tcp, len,0)+ htons(nxt +(u_short)len);sum = ip_cksum_add(&ip6->ip6_src,32, sum);// 伪头部tcp->th_sum = ip_cksum_carry(sum);}}elseif(nxt == IP_PROTO_UDP){struct udp_hdr *udp =(struct udp_hdr *)p;if(len >= UDP_HDR_LEN){udp->uh_sum =0;sum = ip_cksum_add(udp, len,0)+ htons(nxt +(u_short)len);sum = ip_cksum_add(&ip6->ip6_src,32, sum);if((udp->uh_sum = ip_cksum_carry(sum))==0)udp->uh_sum =0xffff;// UDP 校验和不能为 0}}elseif(nxt == IP_PROTO_ICMPV6){struct icmp_hdr *icmp =(struct icmp_hdr *)p;if(len >= ICMP_HDR_LEN){icmp->icmp_cksum =0;sum = ip_cksum_add(icmp, len,0)+ htons(nxt +(u_short)len);sum = ip_cksum_add(&ip6->ip6_src,32, sum);icmp->icmp_cksum = ip_cksum_carry(sum);}}}
IPv6 校验和特点:
- 伪头部包含源和目的地址:增强端到端完整性检查
- 扩展头部处理:自动跳过 Hop-by-Hop、Routing、Fragment 等扩展头
- UDP 特殊处理:校验和为 0 时设置为 0xFFFF(RFC 2460 要求)
- 16 位反码求和:使用标准的 Internet 校验和算法
16.4 ICMPv6 协议支持
16.4.1 ICMPv6 头部结构
源码位置: include/dnet/icmpv6.h
struct icmpv6_hdr {uint8_t icmpv6_type;/* 消息类型 */uint8_t icmpv6_code;/* 子代码 */uint16_t icmpv6_cksum;/* 校验和 */};
16.4.2 ICMPv6 重要消息类型
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16.4.3 邻居发现协议(NDP)支持
NDP 消息结构:
struct icmpv6_msg_nd {uint32_t icmpv6_flags;/* NS/NA 标志位 */ip6_addr_t icmpv6_target;/* 目标地址 */uint8_t icmpv6_option_type;/* 选项类型 */uint8_t icmpv6_option_length;/* 选项长度 */eth_addr_t icmpv6_mac;/* MAC 地址 */};
NDP 核心功能:
- 无 ARP:使用 ICMPv6 NS/NA 消息替代 ARP
- 重复地址检测(DAD):发送 NS 消息检测地址冲突
- 路由器发现:RS/RA 消息实现无状态自动配置
16.5 IPv6 扩展头部机制
16.5.1 扩展头部结构
源码分析:
struct ip6_ext_hdr {uint8_t ext_nxt;/* 下一个头部类型 */uint8_t ext_len;/* 长度(以 8 字节为单位,不包括前 8 字节) */union{struct ip6_ext_data_routing routing;struct ip6_ext_data_fragment fragment;} ext_data;} __attribute__((__packed__));
16.5.2 推荐的扩展头部顺序
根据 RFC 2460 第 4.1 节:
IPv6基本头部↓Hop-by-HopOptions(IP_PROTO_HOPOPTS)↓DestinationOptions(IP_PROTO_DSTOPTS)-第一个↓RoutingHeader(IP_PROTO_ROUTING)↓FragmentHeader(IP_PROTO_FRAGMENT)↓AuthenticationHeader(IP_PROTO_AH)↓EncapsulatingSecurityPayload(IP_PROTO_ESP)↓DestinationOptions(IP_PROTO_DSTOPTS)-第二个↓上层协议头部(TCP/UDP/ICMPv6等)
16.5.3 Fragment 扩展头部
16.6 特殊 IPv6 地址
16.6.1 预定义地址常量
源码位置: include/dnet/ip6.h
16.6.2 常见特殊地址
|
|
|
|
|---|---|---|
:: |
|
|
::1 |
|
|
::ffff:x.x.x.x |
|
|
fe80::/10 |
|
|
ff00::/8 |
|
|
2000::/3 |
|
|
16.7 跨平台 IPv6 实现差异
16.7.1 Windows平台 IPv6 支持
特点:
- 使用 Winsock 2 API 提供 IPv6 支持
- 需要链接
ws2_32.lib和iphlpapi.lib - Raw Socket 权限限制(需管理员权限)
- 不支持某些低级 IPv6 操作
示例代码片段( src/ip-win32.c 风格):
16.7.2 Linux 平台 IPv6 支持
特点:
- 完整的 Raw Socket 支持
- 需要 CAPNETRAW 能力或 root 权限
- 支持 IPv6 扩展头部操作
- 内核提供丰富的 IPv6 选项
示例代码:
16.7.3 BSD 平台 IPv6 支持
特点:
- 最早的 IPv6 实现之一
- 提供高级 IPv6 Socket 选项
- 支持 BPF(Berkeley Packet Filter)过滤 IPv6 包
16.8 实战案例:IPv6 Ping 工具实现
16.8.1 完整代码示例
/** ipv6_ping.c - IPv6 Ping 工具* 功能:向目标 IPv6 地址发送 ICMPv6 Echo Request 并接收响应* 编译:gcc -o ipv6_ping ipv6_ping.c -ldnet* 运行:ipv6_ping 2001:db8::1*/#include<stdio.h>#include<stdlib.h>#include<string.h>#include"dnet.h"#define ICMPV6_ECHO_REQUEST 128#define ICMPV6_ECHO_REPLY 129int main(int argc,char*argv[]){struct ip6_hdr *ip6;struct icmpv6_hdr *icmp;char packet[IP6_HDR_LEN + ICMP_HDR_LEN +64];ip6_addr_t dst;struct addr src_addr, dst_addr;char addr_str[INET6_ADDRSTRLEN];int sock;if(argc !=2){fprintf(stderr,"用法:%s <目标 IPv6 地址>\n", argv[0]);exit(EXIT_FAILURE);}/* 解析目标地址 */if(ip6_pton(argv[1],&dst)<0){fprintf(stderr,"无效的 IPv6 地址:%s\n", argv[1]);exit(EXIT_FAILURE);}/* 获取本地 IPv6 地址 */struct intf_entry intf;strlcpy(intf.intf_name,"eth0",sizeof(intf.intf_name));struct intf_handle *intf_hdl = intf_open();if(intf_get(intf_hdl,&intf)<0){fprintf(stderr,"无法获取网卡信息\n");intf_close(intf_hdl);exit(EXIT_FAILURE);}/* 构造 IPv6 头部 */memset(packet,0,sizeof(packet));ip6 =(struct ip6_hdr *)packet;ip6_pack_hdr(packet,0,/* 流量类别 */0,/* 流标签 */ICMP_HDR_LEN +64,/* 载荷长度 */IP_PROTO_ICMPV6,/* 下一头部 */64,/* 跳限制 */intf.intf_addr.addr_ip6.data,/* 源地址 */dst.data);/* 目的地址 *//* 构造 ICMPv6 Echo Request */icmp =(struct icmpv6_hdr *)(packet + IP6_HDR_LEN);icmp->icmpv6_type = ICMPV6_ECHO_REQUEST;icmp->icmpv6_code =0;icmp->icmpv6_cksum =0;/* 设置 Echo 数据 */struct icmpv6_msg_echo *echo =(struct icmpv6_msg_echo *)(icmp +1);echo->icmpv6_id = htons(getpid()&0xFFFF);echo->icmpv6_seq = htons(1);/* 填充数据 */memset(echo->icmpv6_data,'A',64);/* 计算 ICMPv6 校验和 */ip6_checksum(packet,sizeof(packet));/* 发送数据包 */struct ip6_handle *ip6_hdl = ip6_open();if(ip6_send(ip6_hdl, packet,sizeof(packet))<0){fprintf(stderr,"发送失败\n");ip6_close(ip6_hdl);exit(EXIT_FAILURE);}printf("已发送 ICMPv6 Echo Request 到 %s\n", argv[1]);/* 接收响应(简化版) */// 实际应用中需要使用 select/poll 等待响应ip6_close(ip6_hdl);intf_close(intf_hdl);return0;}
16.8.2 编译和运行
Windows 编译:
gcc -I../include -L../src/.libs -o ipv6_ping.exe ipv6_ping.c -ldnet -lws2_32 -liphlpapi
Linux 编译:
gcc -o ipv6_ping ipv6_ping.c -ldnet
运行示例:
# Windows(需要管理员权限)ipv6_ping.exe 2001:db8::1# Linux(需要 root 权限)sudo ./ipv6_ping 2001:db8::1
16.9 IPv6 编程最佳实践
16.9.1 地址处理最佳实践
- 始终检查地址类型:
if(addr->addr_type == ADDR_TYPE_IP6){// 处理 IPv6}elseif(addr->addr_type == ADDR_TYPE_IP){// 处理 IPv4}
- 使用统一的地址转换函数:
char buf[INET6_ADDRSTRLEN];addr_ntop(addr, buf,sizeof(buf));// 自动处理 IPv4/IPv6
- 正确处理地址前缀长度:
struct addr network;addr_net(addr,&network);// 计算网络地址
16.9.2 校验和计算注意事项
- 必须在发送前计算:
ip6_checksum(packet, packet_len);
- UDP 特殊处理:校验和为 0 时自动设置为 0xFFFF
- 扩展头部自动跳过:无需手动处理
16.9.3 跨平台兼容性建议
- 使用 libdnet 抽象层:避免直接使用平台特定 API
- 测试地址族支持:
- 处理权限差异:Windows 需要管理员,Linux 需要 CAPNETRAW
16.10 IPv6 安全考虑
16.10.1 常见安全风险
- 扩展头部攻击:恶意构造的扩展头链可能导致拒绝服务
- NDP 欺骗:伪造 NS/NA 消息进行中间人攻击
- 组播监听滥用:利用 IPv6 组播进行网络侦察
16.10.2 防护建议
- 验证扩展头部链:限制最大扩展头数量
- 实施 RA Guard:防止 rogue 路由器
- 使用 SEND 协议:安全邻居发现(RFC 3971)
16.11 总结与展望
16.11.1 libdnet IPv6 支持特性总结
✅ 完整的 IPv6 头部封装✅ ICMPv6 协议支持✅ 邻居发现协议(NDP)✅ 扩展头部处理✅ 跨平台地址转换✅ 自动校验和计算✅ IPv4 映射地址支持
16.11.2 IPv6 发展趋势
- IPv6 普及率持续增长:全球已超过 30% 的互联网流量使用 IPv6
- IPv6-only 网络兴起:移动运营商率先部署
- IoT 设备默认 IPv6:6LoWPAN 等低功耗协议推动
- 5G 核心网基于 IPv6:网络切片依赖 IPv6 扩展头
16.11.3 进一步学习资源
- RFC 文档:
- RFC 8200: IPv6 规范
- RFC 4443: ICMPv6
- RFC 4861: 邻居发现
- RFC 5952: IPv6 地址文本表示
- 实验环境:
- GNS3 / EVE-NG 网络模拟器
- Wireshark IPv6 抓包分析
- Linux IPv6 协议栈源码研究
下一章预告:第 17 章将深入分析 libdnet 在 Windows 和 Linux 平台上的内核交互机制,揭示 Raw Socket 在不同操作系统中的实现差异。
-
公众号:安全狗的自我修养
-
vx:2207344074
-
http://gitee.com/haidragon
-
http://github.com/haidragon
-
bilibili:haidragonx
-


夜雨聆风