文章总结: 本文深入分析了跨平台底层网络库libdnet的路由表访问机制,这是理解mettle后门工具等恶意软件依赖库工作原理的关键。文章首先介绍了路由表的基础概念、条目结构与查找算法。核心部分详细解析了libdnet的路由接口设计,特别是Linux平台的实现,其中包括用于ioctl操作和Netlink通信的双socket混合机制,并给出了routeopen、routeadd、route_delete等关键函数的源码解读。此外,还对比了其在macOS/BSD、Windows等平台的适配逻辑。 综合评分: 85 文章分类: 二进制安全,恶意软件,逆向分析,技术标准,解决方案
跨平台底层网络库libdnet源码分析系列(五)
原创
haidragon haidragon
安全狗的自我修养
2026年3月23日 12:18 湖南
官网:http://securitytech.cc
#
源码分析mettle后门工具学习 所使用的依赖库
#
#
路由表访问机制深入分析
目录
- 路由表基础概念
- libdnet路由接口设计
- Linux平台实现
- macOS/BSD平台实现
- HP-UX平台实现
- Windows平台实现
- 跨平台对比分析
- 实际应用示例
- 常见问题与解决方案
1. 路由表基础概念
1.1 路由表简介
路由表是网络核心组件,存储着数据包转发规则。每个条目包含:
- 目标网络/主机:目的地址或网段
- 网关:下一跳地址
- 网络接口:出站接口
- 度量值:路由优先级
- 标志:路由属性
1.2 路由表条目结构
1. /* 文件: include/dnet/route.h 第16-21行 */
2. struct route_entry {
3. char intf_name[INTF_NAME_LEN];/* 网络接口名称 */
4. struct addr route_dst;/* 目标地址(网络或主机) */
5. struct addr route_gw;/* 网关地址 */
6. int metric;/* 路由度量值 */
7. };
字段说明:
intf_name: 出站接口名称,如”eth0″、”en0″route_dst: 目标地址,可以是主机地址(32位)或网络地址(带前缀长度)route_gw: 下一跳网关地址metric: 路由优先级,值越小优先级越高
1.3 路由类型
按目标分类:
- 主机路由:到达特定主机的路由(
/32或/128) - 网络路由:到达网段的路由(如
/24、/64) - 默认路由:兜底路由(
0.0.0.0/0或::/0)
按来源分类:
- 静态路由:手动配置
- 动态路由:路由协议学习(OSPF、BGP等)
- 直连路由:接口直连网络
1.4 路由查找算法
最长前缀匹配:
1. 目标地址:192.168.1.100
3. 候选路由:
4. 1.192.168.1.0/24(匹配24位)
5. 2.192.168.0.0/16(匹配16位)
6. 3.0.0.0.0/0(匹配0位)
8. 选择:192.168.1.0/24(最长前缀匹配)
2. libdnet路由接口设计
2.1 核心API接口
1. /* 文件: include/dnet/route.h 第23-34行 */
2. typedefstruct route_handle route_t;
3. typedefint(*route_handler)(conststruct route_entry * entry,void*arg);
5. /* 路由句柄操作 */
6. route_t*route_open(void);// 打开路由句柄
7. route_t*route_close(route_t*r);// 关闭路由句柄
9. /* 路由表操作 */
10. int route_add(route_t*r,conststruct route_entry *entry);// 添加路由
11. int route_delete(route_t*r,conststruct route_entry *entry);// 删除路由
12. int route_get(route_t*r,struct route_entry *entry);// 查询路由
13. int route_loop(route_t*r, route_handler callback,void*arg);// 遍历路由表
2.2 平台适配机制
libdnet通过编译时检测自动选择对应的平台实现:
configure.ac中的选择逻辑:
1. /* 文件: configure.ac 第291-299行 */
2. dnl Checkfor routing interface.
3. if test "$ac_cv_header_iphlpapi_h"= yes ; then
4. AC_LIBOBJ([route-win32])/* Windows平台 */
5. elif test "$ac_cv_dnet_route_h_has_rt_msghdr"= yes ; then
6. AC_LIBOBJ([route-bsd])/* BSD/macOS route socket方式 */
7. elif test "$ac_cv_dnet_linux_procfs"= yes ; then
8. AC_LIBOBJ([route-linux])/* Linux procfs + netlink方式 */
9. elif test "$ac_cv_header_hpsecurity_h"= yes ; then
10. AC_LIBOBJ([route-hpux])/* HP-UX MIB方式 */
11. else
12. AC_LIBOBJ([route-none])/* 不支持的平台 */
13. fi
2.3 回调函数设计
1. /* 遍历路由表的回调函数 */
2. typedefint(*route_handler)(conststruct route_entry * entry,void*arg);
4. /* 返回值说明:
5. * 0 - 继续遍历
6. * 非0 - 停止遍历
7. */
3. Linux平台实现
3.1 实现架构
Linux平台采用双socket混合机制:
- ioctl + AF_INET socket:添加/删除路由
- Netlink socket:查询路由
- proc文件系统:遍历路由表
核心文件为 src/route-linux.c。
3.2 句柄管理
路由句柄结构:
1. /* 文件: src/route-linux.c 第41-44行 */
2. struct route_handle {
3. int fd;/* AF_INET socket,用于ioctl操作 */
4. int nlfd;/* AF_NETLINK socket,用于Netlink通信 */
5. };
打开句柄:
1. /* 文件: src/route-linux.c 第46-69行 */
2. route_t*
3. route_open(void)
4. {
5. struct sockaddr_nl snl;
6. route_t*r;
8. if((r = calloc(1,sizeof(*r)))!= NULL){
9. r->fd = r->nlfd =-1;
11. /* 创建AF_INET socket用于ioctl */
12. if((r->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
13. return(route_close(r));
15. /* 创建Netlink socket用于路由查询 */
16. if((r->nlfd = socket(AF_NETLINK, SOCK_RAW,
17. NETLINK_ROUTE))<0)
18. return(route_close(r));
20. /* 绑定Netlink socket */
21. memset(&snl,0,sizeof(snl));
22. snl.nl_family = AF_NETLINK;
24. if(bind(r->nlfd,(struct sockaddr *)&snl,sizeof(snl))<0)
25. return(route_close(r));
26. }
27. return(r);
28. }
关键技术点:
- 双socket设计:
fd:AF_INET+SOCK_DGRAM,用于传统ioctl操作nlfd:AF_NETLINK+SOCK_RAW,用于现代Netlink通信
- Netlink绑定:
- 不需要指定具体PID,内核会自动分配
NETLINK_ROUTE表示路由子子系统
3.3 添加路由
实现源码:
1. /* 文件: src/route-linux.c 第71-92行 */
2. int
3. route_add(route_t*r,conststruct route_entry *entry)
4. {
5. struct rtentry rt;
6. struct addr dst;
8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags = RTF_UP | RTF_GATEWAY;/* 激活 + 网关 */
11. /* 判断是主机路由还是网络路由 */
12. if(ADDR_ISHOST(&entry->route_dst)){
13. rt.rt_flags |= RTF_HOST;/* 主机路由 */
14. memcpy(&dst,&entry->route_dst,sizeof(dst));
15. }else
16. addr_net(&entry->route_dst,&dst);/* 网络路由 */
18. /* 填充路由条目 */
19. if(addr_ntos(&dst,&rt.rt_dst)<0||/* 目标地址 */
20. addr_ntos(&entry->route_gw,&rt.rt_gateway)<0||/* 网关 */
21. addr_btos(entry->route_dst.addr_bits,&rt.rt_genmask)<0)/* 子网掩码 */
22. return(-1);
24. /* 执行ioctl添加路由 */
25. return(ioctl(r->fd, SIOCADDRT,&rt));
26. }
宏定义:
1. /* 判断是否为主机地址 */
2. #define ADDR_ISHOST(a)(((a)->addr_type == ADDR_TYPE_IP && \
3. (a)->addr_bits == IP_ADDR_BITS)|| \
4. ((a)->addr_type == ADDR_TYPE_IP6 && \
5. (a)->addr_bits == IP6_ADDR_BITS))
ioctl命令:
SIOCADDRT: 添加路由条目SIOCDELRT: 删除路由条目
rtentry结构:
1. /* Linux内核定义 */
2. struct rtentry {
3. unsignedlong rt_pad1;
4. struct sockaddr rt_dst;/* 目标地址 */
5. struct sockaddr rt_gateway;/* 网关地址 */
6. struct sockaddr rt_genmask;/* 子网掩码 */
7. unsignedshort rt_flags;/* 路由标志 */
8. short rt_pad2;
9. unsignedlong rt_pad3;
10. void*rt_pad4;
11. short rt_metric;/* 路由度量 */
12. char*rt_dev;/* 网络接口 */
13. unsignedlong rt_mtu;/* MTU */
14. unsignedlong rt_window;/* 窗口大小 */
15. unsignedshort rt_irtt;/* 往返时间 */
16. };
路由标志位:
1. #define RTF_UP 0x0001/* 路由激活 */
2. #define RTF_GATEWAY 0x0002/* 网关路由 */
3. #define RTF_HOST 0x0004/* 主机路由 */
4. #define RTF_REINSTATE 0x0008/* 恢复路由 */
5. #define RTF_DYNAMIC 0x0010/* 动态路由 */
6. #define RTF_MODIFIED 0x0020/* 修改的路由 */
7. #define RTF_MTU 0x0040/* 指定MTU */
8. #define RTF_WINDOW 0x0080/* 指定窗口 */
9. #define RTF_IRTT 0x0100/* 初始往返时间 */
10. #define RTF_REJECT 0x0200/* 拒绝路由 */
3.4 删除路由
实现源码:
1. /* 文件: src/route-linux.c 第94-114行 */
2. int
3. route_delete(route_t*r,conststruct route_entry *entry)
4. {
5. struct rtentry rt;
6. struct addr dst;
8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags = RTF_UP;/* 仅需要激活标志 */
11. /* 判断是主机路由还是网络路由 */
12. if(ADDR_ISHOST(&entry->route_dst)){
13. rt.rt_flags |= RTF_HOST;
14. memcpy(&dst,&entry->route_dst,sizeof(dst));
15. }else
16. addr_net(&entry->route_dst,&dst);
18. /* 填充删除参数(只需要目标和掩码) */
19. if(addr_ntos(&dst,&rt.rt_dst)<0||
20. addr_btos(entry->route_dst.addr_bits,&rt.rt_genmask)<0)
21. return(-1);
23. /* 执行ioctl删除路由 */
24. return(ioctl(r->fd, SIOCDELRT,&rt));
25. }
关键点:
- 删除路由不需要网关地址
- 只需要目标地址和子网掩码
- 如果存在多条匹配路由,会删除所有匹配项
3.5 查询路由(Netlink方式)
Linux使用Netlink socket查询路由,这是Linux 2.4+推荐的现代方式。
实现源码:
1. /* 文件: src/route-linux.c 第116-221行 */
2. int
3. route_get(route_t*r,struct route_entry *entry)
4. {
5. staticint seq;/* 序列号,用于匹配请求和响应 */
6. struct nlmsghdr *nmsg;
7. struct rtmsg *rmsg;
8. struct rtattr *rta;
9. struct sockaddr_nl snl;
10. struct iovec iov;
11. struct msghdr msg;
12. u_char buf[512];
13. int i, af, alen;
14. /* 根据地址类型确定地址族 */
15. switch(entry->route_dst.addr_type){
16. case ADDR_TYPE_IP:
17. af = AF_INET;
18. alen = IP_ADDR_LEN;
19. break;
20. case ADDR_TYPE_IP6:
21. af = AF_INET6;
22. alen = IP6_ADDR_LEN;
23. break;
24. default:
25. errno = EINVAL;
26. return(-1);
27. }
28. memset(buf,0,sizeof(buf));
29. /* 构建Netlink消息头 */
30. nmsg =(struct nlmsghdr *)buf;
31. nmsg->nlmsg_len = NLMSG_LENGTH(sizeof(*nmsg))+ RTA_LENGTH(alen);
32. nmsg->nlmsg_flags = NLM_F_REQUEST;/* 请求标志 */
33. nmsg->nlmsg_type = RTM_GETROUTE;/* 获取路由 */
34. nmsg->nlmsg_seq =++seq;/* 序列号 */
35. /* 构建路由消息 */
36. rmsg =(struct rtmsg *)(nmsg +1);
37. rmsg->rtm_family = af;/* 地址族 */
38. rmsg->rtm_dst_len = entry->route_dst.addr_bits;/* 目标前缀长度 */
39. /* 添加目标地址属性 */
40. rta = RTM_RTA(rmsg);
41. rta->rta_type = RTA_DST;/* 目标地址属性 */
42. rta->rta_len = RTA_LENGTH(alen);
43. /* XXX - gross hack for default route */
44. if(af == AF_INET && entry->route_dst.addr_ip == IP_ADDR_ANY){
45. /* 默认路由的特殊处理 */
46. i = htonl(0x60060606);
47. memcpy(RTA_DATA(rta),&i, alen);
48. }else
49. memcpy(RTA_DATA(rta), entry->route_dst.addr_data8, alen);
50. /* 准备发送消息 */
51. memset(&snl,0,sizeof(snl));
52. snl.nl_family = AF_NETLINK;
53. iov.iov_base = nmsg;
54. iov.iov_len = nmsg->nlmsg_len;
55. memset(&msg,0,sizeof(msg));
56. msg.msg_name =&snl;
57. msg.msg_namelen =sizeof(snl);
58. msg.msg_iov =&iov;
59. msg.msg_iovlen =1;
60. /* 发送Netlink请求 */
61. if(sendmsg(r->nlfd,&msg,0)<0)
62. return(-1);
63. /* 接收响应 */
64. iov.iov_base = buf;
65. iov.iov_len =sizeof(buf);
66. if((i = recvmsg(r->nlfd,&msg,0))<=0)
67. return(-1);
68. /* 验证响应 */
69. if(nmsg->nlmsg_len <(int)sizeof(*nmsg)|| nmsg->nlmsg_len > i ||
70. nmsg->nlmsg_seq != seq){
71. errno = EINVAL;
72. return(-1);
73. }
74. if(nmsg->nlmsg_type == NLMSG_ERROR)
75. return(-1);
76. i -= NLMSG_LENGTH(sizeof(*nmsg));
77. /* 解析路由属性 */
78. entry->route_gw.addr_type = ADDR_TYPE_NONE;
79. entry->intf_name[0]='\0';
80. for(rta = RTM_RTA(rmsg); RTA_OK(rta, i); rta = RTA_NEXT(rta, i)){
81. if(rta->rta_type == RTA_GATEWAY){
82. /* 网关地址 */
83. entry->route_gw.addr_type = entry->route_dst.addr_type;
84. memcpy(entry->route_gw.addr_data8, RTA_DATA(rta), alen);
85. entry->route_gw.addr_bits = alen *8;
86. }elseif(rta->rta_type == RTA_OIF){
87. /* 出站接口 */
88. char ifbuf[IFNAMSIZ];
89. char*p;
90. int intf_index;
91. intf_index =*(int*) RTA_DATA(rta);
92. p = if_indextoname(intf_index, ifbuf);
93. if(p == NULL)
94. return(-1);
95. strlcpy(entry->intf_name, ifbuf,sizeof(entry->intf_name));
96. }
97. }
98. if(entry->route_gw.addr_type == ADDR_TYPE_NONE){
99. errno = ESRCH;
100. return(-1);
101. }
102. return(0);
103. }
Netlink消息结构:
1. /* Netlink消息头 */
2. struct nlmsghdr {
3. __u32 nlmsg_len;/* 消息长度 */
4. __u16 nlmsg_type;/* 消息类型 */
5. __u16 nlmsg_flags;/* 消息标志 */
6. __u32 nlmsg_seq;/* 序列号 */
7. __u32 nlmsg_pid;/* 发送进程PID */
8. };
10. /* 路由消息 */
11. struct rtmsg {
12. unsignedchar rtm_family;/* 地址族 */
13. unsignedchar rtm_dst_len;/* 目标前缀长度 */
14. unsignedchar rtm_src_len;/* 源前缀长度 */
15. unsignedchar rtm_tos;/* TOS */
16. unsignedchar rtm_table;/* 路由表ID */
17. unsignedchar rtm_protocol;/* 路由协议 */
18. unsignedchar rtm_scope;/* 路由范围 */
19. unsignedchar rtm_type;/* 路由类型 */
20. unsignedint rtm_flags;/* 路由标志 */
21. };
23. /* 路由属性 */
24. struct rtattr {
25. unsignedshort rta_len;/* 属性长度 */
26. unsignedshort rta_type;/* 属性类型 */
27. };
路由属性类型:
1. #define RTA_DST 0x01/* 目标地址 */
2. #define RTA_SRC 0x02/* 源地址 */
3. #define RTA_IIF 0x03/* 入站接口 */
4. #define RTA_OIF 0x04/* 出站接口 */
5. #define RTA_GATEWAY 0x05/* 网关地址 */
6. #define RTA_PRIORITY 0x06/* 优先级 */
7. #define RTA_PREFSRC 0x07/* 首选源地址 */
8. #define RTA_METRICS 0x08/* 度量值 */
9. #define RTA_MULTIPATH 0x09/* 多路径 */
10. #define RTA_PROTOINFO 0x0b/* 协议信息 */
Netlink消息类型:
1. #define RTM_NEWROUTE 0x24/* 新路由 */
2. #define RTM_DELROUTE 0x25/* 删除路由 */
3. #define RTM_GETROUTE 0x26/* 获取路由 */
默认路由的特殊处理:
1. /* XXX - gross hack for default route */
2. if(af == AF_INET && entry->route_dst.addr_ip == IP_ADDR_ANY){
3. i = htonl(0x60060606);/* 魔术地址 */
4. memcpy(RTA_DATA(rta),&i, alen);
5. }
说明:
- Linux内核对默认路由
0.0.0.0/0有特殊处理 - 需要发送一个特殊的魔术地址
0x60060606 - 这是Linux内核的历史遗留问题
3.6 遍历路由表
Linux支持两种遍历方式:IPv4和IPv6。
IPv4路由表遍历
1. /* 文件: src/route-linux.c 第223-261行 */
2. #define PROC_ROUTE_FILE "/proc/net/route"
4. int
5. route_loop(route_t*r, route_handler callback,void*arg)
6. {
7. FILE*fp;
8. struct route_entry entry;
9. char buf[BUFSIZ];
10. char ifbuf[16];
11. int ret =0;
13. if((fp = fopen(PROC_ROUTE_FILE,"r"))!= NULL){
14. int i, iflags, refcnt, use, metric, mss, win, irtt;
15. uint32_t mask;
17. /* 读取并解析每行 */
18. while(fgets(buf,sizeof(buf), fp)!= NULL){
19. i = sscanf(buf,"%15s %X %X %X %d %d %d %X %d %d %d\n",
20. ifbuf,/* 接口名称 */
21. &entry.route_dst.addr_ip,/* 目标IP */
22. &entry.route_gw.addr_ip,/* 网关IP */
23. &iflags,/* 标志 */
24. &refcnt,/* 引用计数 */
25. &use,/* 使用计数 */
26. &metric,/* 度量值 */
27. &mask,/* 子网掩码 */
28. &mss,/* MSS */
29. &win,/* 窗口 */
30. &irtt);/* IRTT */
32. if(i <11||!(iflags & RTF_UP))
33. continue;/* 跳过未激活的路由 */
35. strlcpy(entry.intf_name, ifbuf,sizeof(entry.intf_name));
37. entry.route_dst.addr_type = entry.route_gw.addr_type = ADDR_TYPE_IP;
39. /* 将掩码转换为前缀长度 */
40. if(addr_mtob(&mask, IP_ADDR_LEN,&entry.route_dst.addr_bits)<0)
41. continue;
43. entry.route_gw.addr_bits = IP_ADDR_BITS;
44. entry.metric = metric;
46. if((ret = callback(&entry, arg))!=0)
47. break;
48. }
49. fclose(fp);
50. }
51. /* ... IPv6处理 ... */
52. return(ret);
53. }
/proc/net/route文件格式:
1. IfaceDestinationGatewayFlagsRefCntUseMetricMask MTU Window IRTT
2. eth0 00000000010011AC000300000000000000
3. eth0 0001A8C000000000000100000FFFFFF000
字段说明:
Iface: 网络接口名称Destination: 目标IP(十六进制,网络字节序)Gateway: 网关IP(十六进制,网络字节序)Flags: 路由标志RefCnt: 引用计数Use: 使用计数Metric: 路由度量值Mask: 子网掩码(十六进制)MTU: 最大传输单元Window: TCP窗口大小IRTT: 初始往返时间
IPv6路由表遍历
1. /* 文件: src/route-linux.c 第262-295行 */
2. #define PROC_IPV6_ROUTE_FILE "/proc/net/ipv6_route"
4. if(ret ==0&&(fp = fopen(PROC_IPV6_ROUTE_FILE,"r"))!= NULL){
5. char s[33], d[8][5], n[8][5];
6. int i, iflags, metric;
7. u_int slen, dlen;
9. while(fgets(buf,sizeof(buf), fp)!= NULL){
10. /* 解析IPv6路由条目 */
11. i = sscanf(buf,"%04s%04s%04s%04s%04s%04s%04s%04s %02x "
12. "%32s %02x %04s%04s%04s%04s%04s%04s%04s%04s "
13. "%x %*x %*x %x %15s",
14. d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7],
15. &dlen, s,&slen,
16. n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7],
17. &metric,&iflags, ifbuf);
19. if(i <21||!(iflags & RTF_UP))
20. continue;
22. strlcpy(entry.intf_name, ifbuf,sizeof(entry.intf_name));
24. /* 构造目标IPv6地址字符串 */
25. snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",
26. d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], dlen);
27. addr_aton(buf,&entry.route_dst);
29. /* 构造下一跳IPv6地址 */
30. snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",
31. n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7],
32. IP6_ADDR_BITS);
33. addr_aton(buf,&entry.route_gw);
34. entry.metric = metric;
36. if((ret = callback(&entry, arg))!=0)
37. break;
38. }
39. fclose(fp);
40. }
/proc/net/ipv6_route文件格式:
1. 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 eth0
2. 20010db8000000000000000000000000400000000000000000000000000000000000 fe8000000000000002cfffefea8fa8b 00000002 eth0
字段格式:
- 第1-8组:目标IPv6地址(每4位十六进制)
- 第9字段:目标前缀长度
- 第10-17组:源IPv6地址
- 第18字段:源前缀长度
- 第19-26组:下一跳IPv6地址
- 第27字段:度量值
- 第28字段:标志
- 第29字段:接口名称
3.7 关闭句柄
1. /* 文件: src/route-linux.c 第299-310行 */
2. route_t*
3. route_close(route_t*r)
4. {
5. if(r != NULL){
6. if(r->fd >=0)
7. close(r->fd);/* 关闭ioctl socket */
8. if(r->nlfd >=0)
9. close(r->nlfd);/* 关闭Netlink socket */
10. free(r);
11. }
12. return(NULL);
13. }
3.8 Linux平台特定问题
权限要求:
- 添加/删除路由需要
CAP_NET_ADMIN能力(root或sudo) - 读取路由表普通用户即可
Netlink vs ioctl:
- ioctl是传统方式,适合添加/删除操作
- Netlink是现代方式,适合查询和高级操作
- libdnet混合使用两者
默认路由问题:
- 默认路由需要特殊处理(魔术地址)
- 这是Linux内核的历史遗留问题
IPv6支持:
- IPv4和IPv6使用不同的proc文件
- IPv6使用Netlink查询
- 两种协议完全独立处理
4. macOS/BSD平台实现
4.1 实现架构
macOS/BSD平台使用路由套接字机制管理路由表,核心文件为 src/route-bsd.c。
三种遍历方式:
- sysctl方式(推荐,macOS/BSD)
- getkerninfo方式(FreeBSD)
- STREAMS方式(Solaris/Irix)
4.2 句柄管理
路由句柄结构:
1. /* 文件: src/route-bsd.c 第81-87行 */
2. struct route_handle {
3. int fd;/* 路由套接字文件描述符 */
4. int seq;/* 序列号,用于匹配请求和响应 */
5. #ifdef HAVE_STREAMS_MIB2
6. int ip_fd;/* MIB2 IP设备描述符(Solaris) */
7. #endif
8. };
打开句柄:
1. /* 文件: src/route-bsd.c 第191-210行 */
2. route_t*
3. route_open(void)
4. {
5. route_t*r;
7. if((r = calloc(1,sizeof(*r)))!= NULL){
8. r->fd =-1;
9. #ifdef HAVE_STREAMS_MIB2
10. /* Solaris MIB2方式 */
11. if((r->ip_fd = open(IP_DEV_NAME, O_RDWR))<0)
12. return(route_close(r));
13. #endif
14. #ifdef HAVE_STREAMS_ROUTE
15. /* STREAMS路由设备 */
16. if((r->fd = open("/dev/route", O_RDWR,0))<0)
17. #else
18. /* 标准BSD/macOS: 创建路由套接字 */
19. if((r->fd = socket(PF_ROUTE, SOCK_RAW, AF_INET))<0)
20. #endif
21. return(route_close(r));
22. }
23. return(r);
24. }
关键技术点:
- PF_ROUTE套接字:
- BSD/macOS特有的套接字族
- 用于内核和用户空间的路由信息交换
SOCK_RAW表示原始路由套接字AF_INET表示只处理IPv4路由
- 序列号机制:
- 用于匹配请求和响应
- 每次发送消息递增
4.3 sockaddr对齐处理
BSD/macOS的路由消息中sockaddr结构需要对齐,但不同平台的对齐规则不同。
对齐宏定义:
1. /* 文件: src/route-bsd.c 第58-79行 */
2. #ifdef __APPLE__
3. #define RT_MSGHDR_ALIGNMENT sizeof(uint32_t)/* macOS: 4字节对齐 */
4. #else
5. #define RT_MSGHDR_ALIGNMENT sizeof(unsignedlong)/* BSD: 8字节对齐 */
6. #endif
8. #if defined(RT_ROUNDUP) && defined(__NetBSD__)
9. #define ROUNDUP(a) RT_ROUNDUP(a)
10. #else
11. #define ROUNDUP(a) \
12. ((a)>0?(1+(((a)-1)|(RT_MSGHDR_ALIGNMENT -1))): RT_MSGHDR_ALIGNMENT)
13. #endif
15. #ifdef HAVE_SOCKADDR_SA_LEN
16. #define NEXTSA(s) \
17. ((struct sockaddr *)((u_char *)(s)+ ROUNDUP((s)->sa_len)))
18. #else
19. #define NEXTSA(s) \
20. ((struct sockaddr *)((u_char *)(s)+ ROUNDUP(sizeof(*(s)))))
21. #endif
问题背景:
1. /*
2. * Unix Network Programming, 3rd edition says that sockaddr structures in
3. * rt_msghdr should be padded so their addresses start on a multiple of
4. * sizeof(u_long). But on 64-bit Mac OS X 10.6 at least, this is false.
5. * Apple's netstat code uses 4-byte padding, not 8-byte. This is relevant
6. * for IPv6 addresses, for which sa_len == 28.
7. */
对齐策略:
- macOS: 使用4字节对齐(uint32_t)
- BSD: 使用8字节对齐(unsigned long)
- NetBSD: 使用系统定义的
RT_ROUNDUP宏
4.4 路由消息处理
核心消息处理函数:
1. /* 文件: src/route-bsd.c 第99-189行 */
2. staticint
3. route_msg(route_t*r,int type,char intf_name[INTF_NAME_LEN],
4. struct addr *dst,struct addr *gw)
5. {
6. struct addr net;
7. struct rt_msghdr *rtm;
8. struct sockaddr *sa;
9. u_char buf[BUFSIZ];
10. pid_t pid;
11. int len;
13. memset(buf,0,sizeof(buf));
15. rtm =(struct rt_msghdr *)buf;
16. rtm->rtm_version = RTM_VERSION;
17. rtm->rtm_type = type;
18. if(type != RTM_DELETE)
19. rtm->rtm_flags = RTF_UP;
20. rtm->rtm_addrs = RTA_DST;/* 包含目标地址 */
21. rtm->rtm_seq =++r->seq;/* 序列号 */
23. /* 目标地址 */
24. sa =(struct sockaddr *)(rtm +1);
25. if(addr_net(dst,&net)<0|| addr_ntos(&net, sa)<0)
26. return(-1);
27. sa = NEXTSA(sa);
29. /* 网关地址 */
30. if(gw != NULL && type != RTM_GET){
31. rtm->rtm_flags |= RTF_GATEWAY;
32. rtm->rtm_addrs |= RTA_GATEWAY;
33. if(addr_ntos(gw, sa)<0)
34. return(-1);
35. sa = NEXTSA(sa);
36. }
38. /* 子网掩码 */
39. if(dst->addr_ip == IP_ADDR_ANY || dst->addr_bits < IP_ADDR_BITS){
40. rtm->rtm_addrs |= RTA_NETMASK;
41. if(addr_btos(dst->addr_bits, sa)<0)
42. return(-1);
43. sa = NEXTSA(sa);
44. }else
45. rtm->rtm_flags |= RTF_HOST;/* 主机路由 */
47. rtm->rtm_msglen =(u_char *)sa - buf;
48. #ifdef DEBUG
49. route_msg_print(rtm);
50. #endif
51. #ifdef HAVE_STREAMS_ROUTE
52. if(ioctl(r->fd, RTSTR_SEND, rtm)<0)
53. return(-1);
54. #else
55. /* 发送路由消息 */
56. if(write(r->fd, buf, rtm->rtm_msglen)<0)
57. return(-1);
59. pid = getpid();
61. /* 接收响应(仅RTM_GET) */
62. while(type == RTM_GET &&(len = read(r->fd, buf,sizeof(buf)))>0){
63. if(len <(int)sizeof(*rtm)){
64. return(-1);
65. }
66. /* 匹配响应 */
67. if(rtm->rtm_type == type && rtm->rtm_pid == pid &&
68. rtm->rtm_seq == r->seq){
69. if(rtm->rtm_errno){
70. errno = rtm->rtm_errno;
71. return(-1);
72. }
73. break;
74. }
75. }
76. #endif
77. /* 解析响应 */
78. if(type == RTM_GET &&(rtm->rtm_addrs &(RTA_DST|RTA_GATEWAY))==
79. (RTA_DST|RTA_GATEWAY)){
80. sa =(struct sockaddr *)(rtm +1);
81. sa = NEXTSA(sa);
83. if(addr_ston(sa, gw)<0|| gw->addr_type != ADDR_TYPE_IP){
84. errno = ESRCH;
85. return(-1);
86. }
88. if(intf_name != NULL){
89. char namebuf[IF_NAMESIZE];
91. if(if_indextoname(rtm->rtm_index, namebuf)== NULL){
92. errno = ESRCH;
93. return(-1);
94. }
95. strlcpy(intf_name, namebuf, INTF_NAME_LEN);
96. }
97. }
98. return(0);
99. }
路由消息类型:
1. #define RTM_ADD 0x1/* 添加路由 */
2. #define RTM_DELETE 0x2/* 删除路由 */
3. #define RTM_CHANGE 0x3/* 修改路由 */
4. #define RTM_GET 0x4/* 获取路由 */
地址标志位:
1. #define RTA_DST 0x1/* 目标地址 */
2. #define RTA_GATEWAY 0x2/* 网关地址 */
3. #define RTA_NETMASK 0x4/* 网络掩码 */
路由标志位:
1. #define RTF_UP 0x1/* 路由激活 */
2. #define RTF_GATEWAY 0x2/* 网关路由 */
3. #define RTF_HOST 0x4/* 主机路由 */
4. #define RTF_REJECT 0x8/* 拒绝路由 */
5. #define RTF_DYNAMIC 0x10/* 动态路由 */
6. #define RTF_MODIFIED 0x20/* 修改的路由 */
4.5 添加路由
1. /* 文件: src/route-bsd.c 第212-223行 */
2. int
3. route_add(route_t*r,conststruct route_entry *entry)
4. {
5. struct route_entry rtent;
7. memcpy(&rtent, entry,sizeof(rtent));
9. if(route_msg(r, RTM_ADD, NULL,&rtent.route_dst,&rtent.route_gw)<0)
10. return(-1);
12. return(0);
13. }
4.6 删除路由
1. /* 文件: src/route-bsd.c 第225-239行 */
2. int
3. route_delete(route_t*r,conststruct route_entry *entry)
4. {
5. struct route_entry rtent;
7. memcpy(&rtent, entry,sizeof(rtent));
9. /* 先查询获取完整信息 */
10. if(route_get(r,&rtent)<0)
11. return(-1);
13. /* 删除路由 */
14. if(route_msg(r, RTM_DELETE, NULL,&rtent.route_dst,&rtent.route_gw)<0)
15. return(-1);
17. return(0);
18. }
关键点:
- 删除前必须先查询获取完整路由信息
- BSD删除路由需要匹配网关地址
4.7 查询路由
1. /* 文件: src/route-bsd.c 第241-250行 */
2. int
3. route_get(route_t*r,struct route_entry *entry)
4. {
5. if(route_msg(r, RTM_GET, entry->intf_name,
6. &entry->route_dst,&entry->route_gw)<0)
7. return(-1);
8. entry->intf_name[0]='\0';
9. entry->metric =0;
11. return(0);
12. }
4.8 遍历路由表
方式1: sysctl方式(推荐)
1. /* 文件: src/route-bsd.c 第281-391行 */
2. #ifdef HAVE_SYS_SYSCTL_H
3. int
4. route_loop(route_t*r, route_handler callback,void*arg)
5. {
6. struct rt_msghdr *rtm;
7. struct route_entry entry;
8. struct sockaddr *sa;
9. char*buf,*lim,*next;
10. int ret;
12. /* sysctl MIB数组 */
13. int mib[6]={ CTL_NET, PF_ROUTE,0,0/* XXX */, NET_RT_DUMP,0};
14. size_t len;
16. /* 第一次调用: 获取所需缓冲区大小 */
17. if(sysctl(mib,6, NULL,&len, NULL,0)<0)
18. return(-1);
20. if(len ==0)
21. return(0);
23. /* 分配缓冲区 */
24. if((buf = malloc(len))== NULL)
25. return(-1);
27. /* 第二次调用: 获取实际数据 */
28. if(sysctl(mib,6, buf,&len, NULL,0)<0){
29. free(buf);
30. return(-1);
31. }
32. lim = buf + len;
33. next = buf;
35. /* 遍历所有路由消息 */
36. for(ret =0; next < lim; next += rtm->rtm_msglen){
37. char namebuf[IF_NAMESIZE];
38. rtm =(struct rt_msghdr *)next;
39. sa =(struct sockaddr *)(rtm +1);
41. /* 获取接口名称 */
42. if(if_indextoname(rtm->rtm_index, namebuf)== NULL)
43. continue;
44. strlcpy(entry.intf_name, namebuf,sizeof(entry.intf_name));
46. /* 解析目标地址 */
47. if((rtm->rtm_addrs & RTA_DST)==0)
48. continue;
49. if(addr_ston(sa,&entry.route_dst)<0)
50. continue;
52. /* 解析网关地址 */
53. if((rtm->rtm_addrs & RTA_GATEWAY)==0)
54. continue;
55. sa = NEXTSA(sa);
56. if(addr_ston_gateway(&entry.route_dst, sa,&entry.route_gw)<0)
57. continue;
59. /* 验证地址类型 */
60. if(entry.route_dst.addr_type != entry.route_gw.addr_type ||
61. (entry.route_dst.addr_type != ADDR_TYPE_IP &&
62. entry.route_dst.addr_type != ADDR_TYPE_IP6))
63. continue;
65. /* 解析子网掩码 */
66. if(rtm->rtm_addrs & RTA_NETMASK){
67. sa = NEXTSA(sa);
68. if(addr_stob(sa,&entry.route_dst.addr_bits)<0)
69. continue;
70. }
72. entry.metric =0;
74. if((ret = callback(&entry, arg))!=0)
75. break;
76. }
77. free(buf);
79. return(ret);
80. }
81. #endif
sysctl MIB数组解析:
1. int mib[6]={
2. CTL_NET,// 网络子系统
3. PF_ROUTE,// 路由子系统
4. 0,// 协议族(0表示所有)
5. 0,// 地址族(0表示所有)
6. NET_RT_DUMP,// 转储路由表
7. 0// 标志(0表示所有路由)
8. };
网关地址特殊处理:
1. /* 文件: src/route-bsd.c 第252-278行 */
2. staticint
3. addr_ston_gateway(conststruct addr *dst,
4. conststruct sockaddr *sa,struct addr *a)
5. {
6. int rc;
8. rc = addr_ston(sa, a);
9. if(rc ==0)
10. return rc;
12. #ifdef HAVE_NET_IF_DL_H
13. # ifdef AF_LINK
14. /* 如果网关是链路层地址,表示同网段路由 */
15. if(sa->sa_family == AF_LINK){
16. memset(a,0,sizeof(*a));
17. a->addr_type = dst->addr_type;/* 使用全0地址 */
18. return(0);
19. }
20. # endif
21. #endif
23. return(-1);
24. }
说明:
- 同网段路由的网关地址可能是
AF_LINK类型 - 这种情况下返回全0地址作为网关
- 表示直接投递,无需网关
方式2: STREAMS MIB2方式(Solaris)
1. /* 文件: src/route-bsd.c 第392-600行 */
2. #elif defined(HAVE_STREAMS_MIB2)
4. int
5. route_loop(route_t*r, route_handler callback,void*arg)
6. {
7. struct route_entry entry;
8. struct strbuf msg;
9. struct T_optmgmt_req *tor;
10. struct T_optmgmt_ack *toa;
11. struct T_error_ack *tea;
12. struct opthdr *opt;
13. u_char buf[8192];
14. int flags, rc, rtable, ret;
16. /* ... 发送MIB2查询请求 ... */
17. /* ... 接收并解析路由表 ... */
19. /* IPv4路由 */
20. for(; rt < rtend; rt++){
21. /* 过滤特定类型 */
22. if((rt->ipRouteInfo.re_ire_type &
23. (IRE_BROADCAST|IRE_ROUTE_REDIRECT|
24. IRE_LOCAL|IRE_ROUTE))!=0||
25. rt->ipRouteNextHop == IP_ADDR_ANY)
26. continue;
28. entry.intf_name[0]='\0';
30. sin.sin_addr.s_addr = rt->ipRouteNextHop;
31. addr_ston((struct sockaddr *)&sin,&entry.route_gw);
33. sin.sin_addr.s_addr = rt->ipRouteDest;
34. addr_ston((struct sockaddr *)&sin,&entry.route_dst);
36. sin.sin_addr.s_addr = rt->ipRouteMask;
37. addr_stob((struct sockaddr *)&sin,&entry.route_dst.addr_bits);
39. entry.metric =0;
41. if((ret = callback(&entry, arg))!=0)
42. return(ret);
43. }
45. /* IPv6路由(类似处理) */
47. return(0);
48. }
MIB2路由类型:
1. #define IRE_BROADCAST 0x01/* 广播路由 */
2. #define IRE_LOCAL 0x02/* 本地路由 */
3. #define IRE_CACHE 0x04/* 缓存路由 */
4. #define IRE_HOST 0x08/* 主机路由 */
5. #define IRE_ROUTE 0x10/* 网络路由 */
6. #define IRE_ROUTE_REDIRECT 0x20/* 重定向路由 */
方式3: getkerninfo方式(FreeBSD)
1. /* 文件: src/route-bsd.c 第307-321行 */
2. #elif defined(HAVE_GETKERNINFO)
3. int len = getkerninfo(KINFO_RT_DUMP,0,0,0);
5. if(len ==0)
6. return(0);
8. if((buf = malloc(len))== NULL)
9. return(-1);
11. if(getkerninfo(KINFO_RT_DUMP,buf,&len,0)<0){
12. free(buf);
13. return(-1);
14. }
15. /* ... 解析路由表 ... */
方式4: 内核内存读取(Tru64)
1. /* 文件: src/route-bsd.c 第601-678行 */
2. #elif defined(HAVE_NET_RADIX_H)
3. /* XXX - Tru64, others? */
5. staticint
6. _kread(int fd,void*addr,void*buf,int len)
7. {
8. if(lseek(fd,(off_t)addr, SEEK_SET)==(off_t)-1L)
9. return(-1);
10. return(read(fd, buf, len)== len ?0:-1);
11. }
13. staticint
14. _radix_walk(int fd,struct radix_node *rn, route_handler callback,void*arg)
15. {
16. /* 遍历radix树 */
17. /* ... */
18. }
20. int
21. route_loop(route_t*r, route_handler callback,void*arg)
22. {
23. struct radix_node_head *rnh, head;
24. struct nlist nl[2];
25. int fd, ret =0;
27. memset(nl,0,sizeof(nl));
28. nl[0].n_name ="radix_node_head";
30. /* 读取内核符号 */
31. if(knlist(nl)<0|| nl[0].n_type ==0||
32. (fd = open("/dev/kmem", O_RDONLY,0))<0)
33. return(-1);
35. /* 遍历radix树 */
36. for(_kread(fd,(void*)nl[0].n_value,&rnh,sizeof(rnh));
37. rnh != NULL; rnh = head.rnh_next){
38. _kread(fd, rnh,&head,sizeof(head));
39. /* XXX - only IPv4 for now... */
40. if(head.rnh_af == AF_INET){
41. if((ret = _radix_walk(fd, head.rnh_treetop,
42. callback, arg))!=0)
43. break;
44. }
45. }
46. close(fd);
47. return(ret);
48. }
49. #endif
4.9 macOS/BSD平台特定问题
sockaddr对齐:
- 不同平台对齐规则不同
- macOS使用4字节对齐
- BSD使用8字节对齐
- 必须正确处理才能避免内存错误
网关地址处理:
- 同网段路由可能返回
AF_LINK类型 - 需要特殊处理转换为全0地址
IPv6支持:
- 部分实现仅支持IPv4
- Solaris MIB2支持IPv6
- 内核读取方式仅支持IPv4
权限要求:
- 添加/删除路由需要root权限
- 遍历路由表普通用户即可
5. HP-UX平台实现
5.1 实现架构
HP-UX平台使用ioctl + MIB方式管理路由表,核心文件为 src/route-hpux.c。
两种方式:
- ioctl方式:添加/删除/查询路由
- MIB方式:遍历路由表
5.2 句柄管理
1. /* 文件: src/route-hpux.c 第31-33行 */
2. struct route_handle {
3. int fd;
4. };
6. route_t*
7. route_open(void)
8. {
9. route_t*r;
11. if((r = calloc(1,sizeof(*r)))!= NULL){
12. if((r->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
13. return(route_close(r));
14. }
15. return(r);
16. }
5.3 添加路由
1. /* 文件: src/route-hpux.c 第47-69行 */
2. int
3. route_add(route_t*r,conststruct route_entry *entry)
4. {
5. struct rtentry rt;
6. struct addr dst;
8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags = RTF_UP | RTF_GATEWAY;
11. if(ADDR_ISHOST(&entry->route_dst)){
12. rt.rt_flags |= RTF_HOST;
13. memcpy(&dst,&entry->route_dst,sizeof(dst));
14. }else
15. addr_net(&entry->route_dst,&dst);
17. /* HP-UX使用rt_subnetmask而不是rt_genmask */
18. if(addr_ntos(&dst,&rt.rt_dst)<0||
19. addr_ntos(&entry->route_gw,&rt.rt_gateway)<0||
20. addr_btom(entry->route_dst.addr_bits,&rt.rt_subnetmask,
21. IP_ADDR_LEN)<0)
22. return(-1);
24. return(ioctl(r->fd, SIOCADDRT,&rt));
25. }
HP-UX特有字段:
rt_subnetmask: 子网掩码(不同于Linux的rt_genmask)
5.4 删除路由
1. /* 文件: src/route-hpux.c 第71-92行 */
2. int
3. route_delete(route_t*r,conststruct route_entry *entry)
4. {
5. struct rtentry rt;
6. struct addr dst;
8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags = RTF_UP;
11. if(ADDR_ISHOST(&entry->route_dst)){
12. rt.rt_flags |= RTF_HOST;
13. memcpy(&dst,&entry->route_dst,sizeof(dst));
14. }else
15. addr_net(&entry->route_dst,&dst);
17. if(addr_ntos(&dst,&rt.rt_dst)<0||
18. addr_btom(entry->route_dst.addr_bits,&rt.rt_subnetmask,
19. IP_ADDR_LEN)<0)
20. return(-1);
22. return(ioctl(r->fd, SIOCDELRT,&rt));
23. }
5.5 查询路由
1. /* 文件: src/route-hpux.c 第94-126行 */
2. int
3. route_get(route_t*r,struct route_entry *entry)
4. {
5. struct rtreq rtr;
7. memset(&rtr,0,sizeof(rtr));
9. /* XXX - gross hack for default route */
10. if(entry->route_dst.addr_ip == IP_ADDR_ANY){
11. rtr.rtr_destaddr = htonl(0x60060606);/* 魔术地址 */
12. rtr.rtr_subnetmask =0xffffffff;
13. }else{
14. memcpy(&rtr.rtr_destaddr,&entry->route_dst.addr_ip,
15. IP_ADDR_LEN);
16. if(entry->route_dst.addr_bits < IP_ADDR_BITS)
17. addr_btom(entry->route_dst.addr_bits,
18. &rtr.rtr_subnetmask, IP_ADDR_LEN);
19. }
20. if(ioctl(r->fd, SIOCGRTENTRY,&rtr)<0)
21. return(-1);
23. if(rtr.rtr_gwayaddr ==0){
24. errno = ESRCH;
25. return(-1);
26. }
27. entry->intf_name[0]='\0';
28. entry->route_gw.addr_type = ADDR_TYPE_IP;
29. entry->route_gw.addr_bits = IP_ADDR_BITS;
30. memcpy(&entry->route_gw.addr_ip, rtr.rtr_gwayaddr, IP_ADDR_LEN);
31. entry->metric =0;
33. return(0);
34. }
HP-UX特有的rtreq结构:
1. struct rtreq {
2. uint32_t rtr_destaddr;/* 目标地址 */
3. uint32_t rtr_subnetmask;/* 子网掩码 */
4. uint32_t rtr_gwayaddr;/* 网关地址 */
5. /* ... 其他字段 ... */
6. };
默认路由处理:
- 与Linux类似,使用魔术地址
0x60060606
5.6 遍历路由表
1. /* 文件: src/route-hpux.c 第128-173行 */
2. #define MAX_RTENTRIES 256/* XXX */
4. int
5. route_loop(route_t*r, route_handler callback,void*arg)
6. {
7. struct nmparms nm;
8. struct route_entry entry;
9. mib_ipRouteEnt rtentries[MAX_RTENTRIES];
10. int fd, i, n, ret;
12. /* 打开MIB设备 */
13. if((fd = open_mib("/dev/ip", O_RDWR,0/* XXX */,0))<0)
14. return(-1);
16. nm.objid = ID_ipRouteTable;
17. nm.buffer = rtentries;
18. n =sizeof(rtentries);
19. nm.len =&n;
21. if(get_mib_info(fd,&nm)<0){
22. close_mib(fd);
23. return(-1);
24. }
25. close_mib(fd);
27. n /=sizeof(*rtentries);
28. ret =0;
30. for(i =0; i < n; i++){
31. /* 过滤路由类型 */
32. if(rtentries[i].Type!= NMDIRECT &&
33. rtentries[i].Type!= NMREMOTE)
34. continue;
36. entry.intf_name[0]='\0';
37. entry.route_dst.addr_type = entry.route_gw.addr_type = ADDR_TYPE_IP;
38. entry.route_dst.addr_bits = entry.route_gw.addr_bits = IP_ADDR_BITS;
39. entry.route_dst.addr_ip = rtentries[i].Dest;
40. addr_mtob(&rtentries[i].Mask, IP_ADDR_LEN,
41. &entry.route_dst.addr_bits);
42. entry.route_gw.addr_ip = rtentries[i].NextHop;
43. entry.metric =0;
45. if((ret = callback(&entry, arg))!=0)
46. break;
47. }
48. return(ret);
49. }
MIB路由类型:
1. #define NMDIRECT 1/* 直连路由 */
2. #define NMREMOTE 2/* 远程路由 */
5.7 HP-UX平台特定问题
特殊结构:
rtreq结构不同于Linuxrt_subnetmask字段名不同于rt_genmask
MIB方式:
- 使用
open_mib/get_mib_info/close_mib - 需要打开
/dev/ip设备
默认路由:
- 同样使用魔术地址处理
IPv6支持:
- 不支持IPv6
6. Windows平台实现
6.1 实现架构
Windows平台使用IP Helper API管理路由表,核心文件为 src/route-win32.c。
两种API版本:
- 传统API(Windows 2000+):
GetIpForwardTable等 - 新API(Vista+):
GetIpForwardTable2等
6.2 句柄管理
1. /* 文件: src/route-win32.c 第30-34行 */
2. struct route_handle {
3. HINSTANCE iphlpapi;/* IP Helper DLL句柄 */
4. MIB_IPFORWARDTABLE *ipftable;/* IPv4路由表缓存 */
5. MIB_IPFORWARD_TABLE2 *ipftable2;/* IPv4/IPv6路由表缓存(Vista+) */
6. };
8. route_t*
9. route_open(void)
10. {
11. route_t*r;
13. r = calloc(1,sizeof(route_t));
14. if(r == NULL)
15. return NULL;
16. r->iphlpapi =GetModuleHandle("iphlpapi.dll");/* 获取DLL句柄 */
18. return r;
19. }
关键技术点:
- 不打开任何系统资源
- 缓存路由表以提高性能
- 动态加载新API函数
6.3 添加路由
1. /* 文件: src/route-win32.c 第49-76行 */
2. int
3. route_add(route_t*route,conststruct route_entry *entry)
4. {
5. MIB_IPFORWARDROW ipfrow;
6. struct addr net;
8. memset(&ipfrow,0,sizeof(ipfrow));
10. /* 获取到网关的最佳接口索引 */
11. if(GetBestInterface(entry->route_gw.addr_ip,
12. &ipfrow.dwForwardIfIndex)!= NO_ERROR)
13. return(-1);
15. /* 计算网络地址 */
16. if(addr_net(&entry->route_dst,&net)<0||
17. net.addr_type != ADDR_TYPE_IP)
18. return(-1);
20. /* 填充路由条目 */
21. ipfrow.dwForwardDest = net.addr_ip;
22. addr_btom(entry->route_dst.addr_bits,
23. &ipfrow.dwForwardMask, IP_ADDR_LEN);
24. ipfrow.dwForwardNextHop = entry->route_gw.addr_ip;
25. ipfrow.dwForwardType =4;/* XXX - next hop != final dest */
26. ipfrow.dwForwardProto =3;/* XXX - MIB_PROTO_NETMGMT */
28. /* 创建路由条目 */
29. if(CreateIpForwardEntry(&ipfrow)!= NO_ERROR)
30. return(-1);
32. return(0);
33. }
IP Helper API详解:
- GetBestInterface:
c DWORDGetBestInterface(DWORD dwDestAddr,/* 目标IP */PDWORD pdwBestIfIndex/* 接收接口索引 */);
- 查找到目标IP的最佳接口
- 自动选择出站接口
- CreateIpForwardEntry:
c DWORDCreateIpForwardEntry(PMIB_IPFORWARDROW pRoute/* 路由条目 */);
- 创建IPv4路由条目
MIB_IPFORWARDROW结构:
1. typedefstruct _MIB_IPFORWARDROW {
2. DWORD dwForwardDest;/* 目标地址 */
3. DWORD dwForwardMask;/* 子网掩码 */
4. DWORD dwForwardPolicy;/* 策略 */
5. DWORD dwForwardNextHop;/* 下一跳地址 */
6. DWORD dwForwardIfIndex;/* 接口索引 */
7. DWORD dwForwardType;/* 路由类型 */
8. DWORD dwForwardProto;/* 路由协议 */
9. DWORD dwForwardAge;/* 路由生存时间 */
10. DWORD dwForwardNextHopAS;/* BGP AS号 */
11. DWORD dwForwardMetric1;/* 度量值1 */
12. DWORD dwForwardMetric2;/* 度量值2 */
13. DWORD dwForwardMetric3;/* 度量值3 */
14. DWORD dwForwardMetric4;/* 度量值4 */
15. DWORD dwForwardMetric5;/* 度量值5 */
16. } MIB_IPFORWARDROW,*PMIB_IPFORWARDROW;
路由类型:
1. #define MIB_IPROUTE_TYPE_OTHER 1/* 其他 */
2. #define MIB_IPROUTE_TYPE_INVALID 2/* 无效 */
3. #define MIB_IPROUTE_TYPE_DIRECT 3/* 直连 */
4. #define MIB_IPROUTE_TYPE_INDIRECT 4/* 间接(网关) */
路由协议:
1. #define MIB_IPPROTO_OTHER 1/* 其他 */
2. #define MIB_IPPROTO_LOCAL 2/* 本地 */
3. #define MIB_IPPROTO_NETMGMT 3/* 网络管理(手动) */
4. #define MIB_IPPROTO_ICMP 4/* ICMP重定向 */
5. #define MIB_IPPROTO_EGP 5/* EGP */
6. #define MIB_IPPROTO_GGP 6/* GGP */
7. #define MIB_IPPROTO_HELLO 7/* HELLO */
8. #define MIB_IPPROTO_RIP 8/* RIP */
9. #define MIB_IPPROTO_IS_IS 9/* IS-IS */
10. #define MIB_IPPROTO_ES_IS 10/* ES-IS */
11. #define MIB_IPPROTO_CISCO 11/* Cisco IGRP */
12. #define MIB_IPPROTO_BBN 12/* BBN SPF IGP */
13. #define MIB_IPPROTO_OSPF 13/* OSPF */
14. #define MIB_IPPROTO_BGP 14/* BGP */
6.4 删除路由
1. /* 文件: src/route-win32.c 第78-101行 */
2. int
3. route_delete(route_t*route,conststruct route_entry *entry)
4. {
5. MIB_IPFORWARDROW ipfrow;
6. DWORD mask;
8. if(entry->route_dst.addr_type != ADDR_TYPE_IP ||
9. GetBestRoute(entry->route_dst.addr_ip,
10. IP_ADDR_ANY,&ipfrow)!= NO_ERROR)
11. return(-1);
13. /* 计算子网掩码 */
14. addr_btom(entry->route_dst.addr_bits,&mask, IP_ADDR_LEN);
16. /* 验证路由匹配 */
17. if(ipfrow.dwForwardDest != entry->route_dst.addr_ip ||
18. ipfrow.dwForwardMask != mask){
19. errno = ENXIO;
20. SetLastError(ERROR_NO_DATA);
21. return(-1);
22. }
23. /* 删除路由 */
24. if(DeleteIpForwardEntry(&ipfrow)!= NO_ERROR)
25. return(-1);
27. return(0);
28. }
DeleteIpForwardEntry API:
1. DWORD DeleteIpForwardEntry(
2. PMIB_IPFORWARDROW pRoute /* 要删除的路由条目 */
3. );
关键点:
- 必须精确匹配目标地址和子网掩码
- 需要先查询获取完整的路由条目
6.5 查询路由
1. /* 文件: src/route-win32.c 第103-140行 */
2. int
3. route_get(route_t*route,struct route_entry *entry)
4. {
5. MIB_IPFORWARDROW ipfrow;
6. DWORD mask;
7. intf_t*intf;
8. struct intf_entry intf_entry;
10. if(entry->route_dst.addr_type != ADDR_TYPE_IP ||
11. GetBestRoute(entry->route_dst.addr_ip,
12. IP_ADDR_ANY,&ipfrow)!= NO_ERROR)
13. return(-1);
15. /* 过滤本地路由 */
16. if(ipfrow.dwForwardProto ==2&&/* XXX - MIB_IPPROTO_LOCAL */
17. (ipfrow.dwForwardNextHop|IP_CLASSA_NET)!=
18. (IP_ADDR_LOOPBACK|IP_CLASSA_NET)&&
19. !IP_LOCAL_GROUP(ipfrow.dwForwardNextHop)){
20. errno = ENXIO;
21. SetLastError(ERROR_NO_DATA);
22. return(-1);
23. }
24. addr_btom(entry->route_dst.addr_bits,&mask, IP_ADDR_LEN);
26. entry->route_gw.addr_type = ADDR_TYPE_IP;
27. entry->route_gw.addr_bits = IP_ADDR_BITS;
28. entry->route_gw.addr_ip = ipfrow.dwForwardNextHop;
29. entry->metric = ipfrow.dwForwardMetric1;
31. /* 查询接口名称 */
32. entry->intf_name[0]='\0';
33. intf = intf_open();
34. if(intf_get_index(intf,&intf_entry,
35. AF_INET, ipfrow.dwForwardIfIndex)==0){
36. strlcpy(entry->intf_name, intf_entry.intf_name,sizeof(entry->intf_name));
37. }
38. intf_close(intf);
40. return(0);
41. }
GetBestRoute API:
1. DWORD GetBestRoute(
2. DWORD dwDestAddr,/* 目标IP */
3. DWORD dwSourceAddr,/* 源IP */
4. PMIB_IPFORWARDROW pBestRoute /* 接收最佳路由 */
5. );
本地路由过滤:
- 回环地址:
127.x.x.x - 本地组播:
224.x.x.x
6.6 遍历路由表
方式1: 传统API(Windows 2000+)
1. /* 文件: src/route-win32.c 第142-197行 */
2. staticint
3. route_loop_getipforwardtable(route_t*r, route_handler callback,void*arg)
4. {
5. struct route_entry entry;
6. intf_t*intf;
7. ULONG len;
8. int i, ret;
10. /* 循环获取路由表,直到缓冲区足够大 */
11. for(len =sizeof(r->ipftable[0]);;){
12. if(r->ipftable)
13. free(r->ipftable);
14. r->ipftable = malloc(len);
15. if(r->ipftable == NULL)
16. return(-1);
17. ret =GetIpForwardTable(r->ipftable,&len, FALSE);
18. if(ret == NO_ERROR)
19. break;
20. elseif(ret != ERROR_INSUFFICIENT_BUFFER)
21. return(-1);
22. }
24. intf = intf_open();
26. ret =0;
27. for(i =0; i <(int)r->ipftable->dwNumEntries; i++){
28. struct intf_entry intf_entry;
30. entry.route_dst.addr_type = ADDR_TYPE_IP;
31. entry.route_dst.addr_bits = IP_ADDR_BITS;
33. entry.route_gw.addr_type = ADDR_TYPE_IP;
34. entry.route_gw.addr_bits = IP_ADDR_BITS;
36. /* 解析路由条目 */
37. entry.route_dst.addr_ip = r->ipftable->table[i].dwForwardDest;
38. addr_mtob(&r->ipftable->table[i].dwForwardMask, IP_ADDR_LEN,
39. &entry.route_dst.addr_bits);
40. entry.route_gw.addr_ip =
41. r->ipftable->table[i].dwForwardNextHop;
42. entry.metric = r->ipftable->table[i].dwForwardMetric1;
44. /* 查询接口名称 */
45. entry.intf_name[0]='\0';
46. intf_entry.intf_len =sizeof(intf_entry);
47. if(intf_get_index(intf,&intf_entry,
48. AF_INET, r->ipftable->table[i].dwForwardIfIndex)==0){
49. strlcpy(entry.intf_name, intf_entry.intf_name,sizeof(entry.intf_name));
50. }
52. if((ret =(*callback)(&entry, arg))!=0)
53. break;
54. }
56. intf_close(intf);
58. return ret;
59. }
GetIpForwardTable API:
1. DWORD GetIpForwardTable(
2. PMIB_IPFORWARDTABLE pIpForwardTable,/* 接收路由表 */
3. PULONG pdwSize,/* 缓冲区大小(输入/输出) */
4. BOOL bOrder /* 是否排序 */
5. );
7. /* MIB_IPFORWARDTABLE结构 */
8. typedefstruct _MIB_IPFORWARDTABLE {
9. DWORD dwNumEntries;/* 条目数量 */
10. MIB_IPFORWARDROW table[1];/* 条目数组(变长) */
11. } MIB_IPFORWARDTABLE,*PMIB_IPFORWARDTABLE;
方式2: 新API(Vista+)
1. /* 文件: src/route-win32.c 第199-254行 */
2. staticint
3. route_loop_getipforwardtable2(GETIPFORWARDTABLE2 GetIpForwardTable2,
4. route_t*r, route_handler callback,void*arg)
5. {
6. struct route_entry entry;
7. intf_t*intf;
8. ULONG i;
9. int ret;
11. /* 获取路由表(Vista+) */
12. ret =GetIpForwardTable2(AF_UNSPEC,&r->ipftable2);
13. if(ret != NO_ERROR)
14. return(-1);
16. intf = intf_open();
18. ret =0;
19. for(i =0; i < r->ipftable2->NumEntries; i++){
20. struct intf_entry intf_entry;
21. MIB_IPFORWARD_ROW2 *row;
22. MIB_IPINTERFACE_ROW ifrow;
23. ULONG metric;
25. row =&r->ipftable2->Table[i];
26. addr_ston((struct sockaddr *)&row->DestinationPrefix.Prefix,&entry.route_dst);
27. entry.route_dst.addr_bits = row->DestinationPrefix.PrefixLength;
28. addr_ston((struct sockaddr *)&row->NextHop,&entry.route_gw);
30. /* 查询接口名称 */
31. entry.intf_name[0]='\0';
32. intf_entry.intf_len =sizeof(intf_entry);
33. if(intf_get_index(intf,&intf_entry,
34. row->DestinationPrefix.Prefix.si_family,
35. row->InterfaceIndex)==0){
36. strlcpy(entry.intf_name, intf_entry.intf_name,sizeof(entry.intf_name));
37. }
39. /* 计算总度量值 */
40. ifrow.Family= row->DestinationPrefix.Prefix.si_family;
41. ifrow.InterfaceLuid= row->InterfaceLuid;
42. ifrow.InterfaceIndex= row->InterfaceIndex;
43. if(GetIpInterfaceEntry(&ifrow)!= NO_ERROR){
44. return(-1);
45. }
46. metric = ifrow.Metric+ row->Metric;
47. if(metric < INT_MAX)
48. entry.metric = metric;
49. else
50. entry.metric = INT_MAX;
52. if((ret =(*callback)(&entry, arg))!=0)
53. break;
54. }
56. intf_close(intf);
58. return ret;
59. }
GetIpForwardTable2 API:
1. /* 动态加载 */
2. typedef DWORD (WINAPI *GETIPFORWARDTABLE2)(
3. ADDRESS_FAMILY Family,/* 地址族(AF_INET, AF_INET6, AF_UNSPEC) */
4. PMIB_IPFORWARD_TABLE2 *Table/* 接收路由表 */
5. );
7. /* MIB_IPFORWARD_TABLE2结构 */
8. typedefstruct _MIB_IPFORWARD_TABLE2 {
9. ULONG NumEntries;/* 条目数量 */
10. MIB_IPFORWARD_ROW2 Table[1];/* 条目数组(变长) */
11. } MIB_IPFORWARD_TABLE2,*PMIB_IPFORWARD_TABLE2;
13. /* MIB_IPFORWARD_ROW2结构 */
14. typedefstruct _MIB_IPFORWARD_ROW2 {
15. NET_LUID InterfaceLuid;/* 接口LUID */
16. NET_IFINDEX InterfaceIndex;/* 接口索引 */
17. SOCKADDR_INET DestinationPrefix;/* 目标前缀 */
18. SOCKADDR_INET NextHop;/* 下一跳 */
19. UCHAR SitePrefixLength;/* 站点前缀长度 */
20. SOCKADDR_INET SitePrefix;/* 站点前缀 */
21. ULONG ValidLifetime;/* 有效生存时间 */
22. ULONG PreferredLifetime;/* 首选生存时间 */
23. ULONG Metric;/* 路由度量 */
24. ULONG Protocol;/* 路由协议 */
25. BOOLEAN Loopback;/* 是否回环 */
26. BOOLEAN AutoconfigureAddress;/* 是否自动配置 */
27. BOOLEAN Publish;/* 是否发布 */
28. BOOLEAN Immortal;/* 是否永久 */
29. ULONG Age;/* 路由年龄 */
30. ULONG Origin;/* 路由来源 */
31. } MIB_IPFORWARD_ROW2,*PMIB_IPFORWARD_ROW2;
动态API选择:
1. /* 文件: src/route-win32.c 第256-270行 */
2. int
3. route_loop(route_t*r, route_handler callback,void*arg)
4. {
5. GETIPFORWARDTABLE2 GetIpForwardTable2;
7. /* GetIpForwardTable2仅Vista及以上可用,动态加载 */
8. GetIpForwardTable2= NULL;
9. if(r->iphlpapi != NULL)
10. GetIpForwardTable2=(GETIPFORWARDTABLE2)GetProcAddress(
11. r->iphlpapi,"GetIpForwardTable2");
13. if(GetIpForwardTable2== NULL)
14. return route_loop_getipforwardtable(r, callback, arg);
15. else
16. return route_loop_getipforwardtable2(GetIpForwardTable2, r, callback, arg);
17. }
新旧API对比:
| 特性 | 传统API | 新API(Vista+) | | — | — | — | | IPv4支持 | ✅ | ✅ | | IPv6支持 | ❌ | ✅ | | 接口标识 | 仅索引 | LUID + 索引 | | 度量计算 | 单个值 | 接口度量 + 路由度量 | | 生存时间 | ❌ | ✅ | | 动态加载 | ❌ | ✅ |
6.7 关闭句柄
1. /* 文件: src/route-win32.c 第272-285行 */
2. route_t*
3. route_close(route_t*r)
4. {
5. if(r != NULL){
6. if(r->iphlpapi != NULL)
7. FreeLibrary(r->iphlpapi);
8. if(r->ipftable != NULL)
9. free(r->ipftable);
10. if(r->ipftable2 != NULL)
11. FreeMibTable(r->ipftable2);/* Vista+ */
12. free(r);
13. }
14. return(NULL);
15. }
FreeMibTable API:
1. /* Vista+ */
2. voidFreeMibTable(
3. PVOID Memory/* 要释放的MIB表内存 */
4. );
6.8 Windows平台特定问题
API版本:
- 传统API仅支持IPv4
- 新API支持IPv4/IPv6
- 动态加载确保向后兼容
DLL依赖:
- 需要链接
iphlpapi.lib - 运行时需要
iphlpapi.dll - Windows 2000及以上版本支持
权限要求:
- 添加/删除路由需要管理员权限
- 读取路由表普通用户即可
IPv6支持:
- 传统API不支持IPv6
- 新API完全支持IPv6
- libdnet使用传统API保证兼容性
接口查询:
- 需要通过
intf_get_index查询接口名称 - 这增加了查询复杂度
7. 跨平台对比分析
7.1 API设计对比
| 功能 | Linux | macOS/BSD | HP-UX | Windows | 无实现 | | — | — | — | — | — | — | | 打开句柄 | 双socket(AF_INET+NETLINK) | PF_ROUTE socket | AF_INET socket | calloc | 返回NULL | | 添加路由 | ioctl(SIOCADDRT) | RTM_ADD | ioctl(SIOCADDRT) | CreateIpForwardEntry | ENOSYS | | 删除路由 | ioctl(SIOCDELRT) | RTM_DELETE | ioctl(SIOCDELRT) | DeleteIpForwardEntry | ENOSYS | | 查询路由 | Netlink RTM_GETROUTE | RTM_GET | ioctl(SIOCGRTENTRY) | GetBestRoute | ENOSYS | | 遍历路由 | /proc/net/route | sysctl | MIB | GetIpForwardTable | ENOSYS | | 关闭句柄 | close双fd | close(fd) | close(fd) | free | free |
7.2 数据结构对比
Linux: rtentry
1. struct rtentry {
2. struct sockaddr rt_dst;/* 目标地址 */
3. struct sockaddr rt_gateway;/* 网关地址 */
4. struct sockaddr rt_genmask;/* 子网掩码 */
5. unsignedshort rt_flags;/* 路由标志 */
6. short rt_metric;/* 路由度量 */
7. char*rt_dev;/* 网络接口 */
8. };
macOS/BSD: rt_msghdr + sockaddr
1. struct rt_msghdr {
2. u_short rtm_msglen;/* 消息长度 */
3. u_char rtm_version;/* 版本 */
4. u_char rtm_type;/* 消息类型 */
5. u_short rtm_index;/* 接口索引 */
6. int rtm_flags;/* 路由标志 */
7. int rtm_addrs;/* 地址掩码 */
8. /* ... */
9. char rtm_data[512];/* 地址数据 */
10. };
HP-UX: rtreq
1. struct rtreq {
2. uint32_t rtr_destaddr;/* 目标地址 */
3. uint32_t rtr_subnetmask;/* 子网掩码 */
4. uint32_t rtr_gwayaddr;/* 网关地址 */
5. };
Windows: MIB_IPFORWARDROW
1. typedefstruct _MIB_IPFORWARDROW {
2. DWORD dwForwardDest;/* 目标地址 */
3. DWORD dwForwardMask;/* 子网掩码 */
4. DWORD dwForwardNextHop;/* 下一跳地址 */
5. DWORD dwForwardIfIndex;/* 接口索引 */
6. DWORD dwForwardType;/* 路由类型 */
7. DWORD dwForwardProto;/* 路由协议 */
8. DWORD dwForwardMetric1;/* 度量值1 */
9. } MIB_IPFORWARDROW;
7.3 操作方式对比
Linux: ioctl + Netlink混合
1. /* 优点 */
2. -添加/删除使用ioctl,简单直接
3. -查询使用Netlink,功能强大
4. -支持IPv4/IPv6
5. - proc文件易于调试
7. /* 缺点 */
8. -双socket增加复杂度
9. -Netlink消息格式复杂
10. -默认路由需要特殊处理
macOS/BSD: 路由消息
1. /* 优点 */
2. -统一的消息机制
3. -支持IPv4/IPv6
4. - sysctl遍历性能高
5. -消息格式灵活
7. /* 缺点 */
8. - sockaddr对齐复杂
9. -需要序列号匹配
10. -消息格式复杂
11. -删除路由需要先查询
HP-UX: ioctl + MIB
1. /* 优点 */
2. - ioctl操作简单
3. - MIB方式统一
5. /* 缺点 */
6. -特有结构(rtreq)
7. -不支持IPv6
8. -默认路由需要特殊处理
Windows: IP Helper API
1. /* 优点 */
2. -高层API,易用
3. -不需要句柄
4. -自动处理接口索引
5. -新API支持IPv6
7. /* 缺点 */
8. -依赖Windows DLL
9. -遍历效率低(全表扫描)
10. -需要管理员权限
11. -接口查询复杂
7.4 性能对比
| 操作 | Linux | macOS/BSD | HP-UX | Windows | | — | — | — | — | — | | 单条获取 | 中(Netlink) | 中(消息交换) | 慢(ioctl) | 慢(API) | | 单条添加 | 快(ioctl) | 中(消息交换) | 快(ioctl) | 快(API) | | 全表遍历 | 快(proc文件) | 最快(sysctl) | 中(MIB) | 中(API) | | 批量操作 | 需多次ioctl | 需多次消息 | 需多次ioctl | 需多次API |
详细分析:
Linux遍历性能:
1. /* /proc/net/route解析 */
2. -文件I/O:快
3. -文本解析:中等
4. -系统调用:少(fopen/fgets/fclose)
5. -支持IPv4/IPv6
6. -适用场景:所有规模
macOS/BSD遍历性能:
1. /* sysctl方式 */
2. -系统调用:1次
3. -内存拷贝:1次
4. -数据格式:二进制,无需解析
5. -支持IPv4/IPv6
6. -适用场景:所有规模,最佳性能
HP-UX遍历性能:
1. /* MIB方式 */
2. -系统调用:2-3次
3. -数据格式:二进制
4. -仅支持IPv4
5. -适用场景:小到中等路由表
Windows遍历性能:
1. /* GetIpForwardTable API */
2. -系统调用:1-2次(缓冲区调整)
3. -数据格式:二进制
4. -缓冲区管理:复杂
5. -传统API仅支持IPv4
6. -新API支持IPv4/IPv6
7. -适用场景:中小路由表
7.5 权限要求对比
| 操作 | Linux | macOS/BSD | HP-UX | Windows | | — | — | — | — | — | | 打开句柄 | 普通用户 | 普通用户 | 普通用户 | 普通用户 | | 读取路由 | 普通用户 | 普通用户 | 普通用户 | 普通用户 | | 添加路由 | root/CAPNETADMIN | root | root | 管理员 | | 删除路由 | root/CAPNETADMIN | root | root | 管理员 | | 遍历路由 | 普通用户 | 普通用户 | 普通用户 | 普通用户 |
7.6 IPv6支持对比
| 平台 | IPv4 | IPv6 | 备注 | | — | — | — | — | | Linux | ✅ | ✅ | 完全支持,双socket设计 | | macOS/BSD | ✅ | ✅ | 完全支持,sysctl方式 | | HP-UX | ✅ | ❌ | 不支持IPv6 | | Windows | ✅ | ✅ | 新API支持IPv6 |
7.7 代码复杂度对比
| 文件 | 行数 | 复杂度 | 主要难点 | | — | — | — | — | | route-linux.c | 311 | 高 | 双socket、Netlink消息、双proc文件 | | route-bsd.c | 702 | 最高 | 4种遍历方式、sockaddr对齐、序列号 | | route-hpux.c | 185 | 中 | MIB方式、特殊结构 | | route-win32.c | 286 | 低 | API简单、动态加载、缓冲区管理 |
7.8 维护性分析
Linux (route-linux.c):
1. /* 维护难点 */
2. -双socket设计
3. -Netlink消息格式变化
4. -/proc文件格式变化
5. -默认路由特殊处理
7. /* 优点 */
8. -代码结构清晰
9. -IPv4/IPv6分离处理
10. -文档完善
macOS/BSD (route-bsd.c):
1. /* 维护难点 */
2. -4种遍历方式
3. - sockaddr对齐规则变化
4. -不同BSD版本差异
5. -路由消息格式变化
6. -网关地址特殊处理
8. /* 优点 */
9. - sysctl方式稳定
10. -逻辑清晰
HP-UX (route-hpux.c):
1. /* 维护难点 */
2. -平台特有结构
3. - MIB接口变化
4. -默认路由特殊处理
6. /* 优点 */
7. -代码简单
8. -变化较少
Windows (route-win32.c):
1. /* 维护难点 */
2. - API版本更新频繁
3. -新版API不向后兼容
4. -Windows版本差异
6. /* 优点 */
7. -代码简单
8. -微软文档完善
9. -动态加载确保兼容性
7.9 平台特性对比
| 特性 | Linux | macOS/BSD | HP-UX | Windows | | — | — | — | — | — | | IPv4支持 | ✅ | ✅ | ✅ | ✅ | | IPv6支持 | ✅ | ✅ | ❌ | ✅(新API) | | 静态路由 | ✅ | ✅ | ✅ | ✅ | | 动态路由 | ✅ | ✅ | ✅ | ✅ | | 默认路由 | ✅(特殊处理) | ✅ | ✅(特殊处理) | ✅ | | 度量值 | ✅ | ❌ | ❌ | ✅ | | 接口指定 | ✅(自动) | ✅(自动) | ❌ | ✅(自动) | | 批量操作 | ❌ | ❌ | ❌ | ❌ | | 原子操作 | ✅ | ✅ | ✅ | ✅ |
8. 实际应用示例
8.1 基本用法
添加路由:
1. #include<stdio.h>
2. #include<dnet.h>
4. int main(void)
5. {
6. route_t*route;
7. struct route_entry entry;
9. /* 打开路由句柄 */
10. if((route = route_open())== NULL){
11. perror("route_open");
12. return1;
13. }
15. /* 设置路由条目 */
16. addr_pton("192.168.2.0/24",&entry.route_dst);/* 目标网络 */
17. addr_pton("192.168.1.1",&entry.route_gw);/* 网关 */
19. /* 添加路由 */
20. if(route_add(route,&entry)<0){
21. perror("route_add");
22. route_close(route);
23. return1;
24. }
26. printf("Route added successfully\n");
28. /* 关闭句柄 */
29. route_close(route);
30. return0;
31. }
删除路由:
1. int main(void)
2. {
3. route_t*route;
4. struct route_entry entry;
6. if((route = route_open())== NULL){
7. perror("route_open");
8. return1;
9. }
11. /* 只需要目标地址 */
12. addr_pton("192.168.2.0/24",&entry.route_dst);
14. /* 删除路由 */
15. if(route_delete(route,&entry)<0){
16. perror("route_delete");
17. route_close(route);
18. return1;
19. }
21. printf("Route deleted\n");
22. route_close(route);
23. return0;
24. }
8.2 路由表查看器
1. #include<stdio.h>
2. #include<stdlib.h>
3. #include<dnet.h>
5. /* 回调函数: 打印每个路由条目 */
6. staticint print_route(conststruct route_entry *entry,void*arg)
7. {
8. constchar*dst_type;
9. char dst_str[64], gw_str[64];
11. /* 判断是主机路由还是网络路由 */
12. if(entry->route_dst.addr_bits == IP_ADDR_BITS ||
13. entry->route_dst.addr_bits == IP6_ADDR_BITS){
14. dst_type ="host";
15. }else{
16. dst_type ="net ";
17. }
19. /* 格式化地址 */
20. snprintf(dst_str,sizeof(dst_str),"%s/%d",
21. addr_ntoa(&entry->route_dst),
22. entry->route_dst.addr_bits);
23. snprintf(gw_str,sizeof(gw_str),"%s",
24. addr_ntoa(&entry->route_gw));
26. printf("%-20s %-6s %-20s %-10s metric=%d\n",
27. dst_str, dst_type, gw_str, entry->intf_name, entry.metric);
28. return0;
29. }
31. int main(void)
32. {
33. route_t*route;
35. /* 打开路由句柄 */
36. if((route = route_open())== NULL){
37. perror("route_open");
38. return1;
39. }
41. printf("%-20s %-6s %-20s %-10s %s\n",
42. "Destination","Type","Gateway","Interface","Metric");
43. printf("---------------------------------------------------------\n");
45. /* 遍历并打印所有路由条目 */
46. if(route_loop(route, print_route, NULL)<0){
47. perror("route_loop");
48. route_close(route);
49. return1;
50. }
52. /* 关闭句柄 */
53. route_close(route);
54. return0;
55. }
8.3 路由查询工具
1. #include<stdio.h>
2. #include<stdlib.h>
3. #include<dnet.h>
5. int main(int argc,char*argv[])
6. {
7. route_t*route;
8. struct route_entry entry;
9. constchar*dst_str;
11. if(argc !=2){
12. fprintf(stderr,"Usage: %s <destination>\n", argv[0]);
13. return1;
14. }
16. /* 解析目标地址 */
17. if(addr_pton(argv[1],&entry.route_dst)<0){
18. fprintf(stderr,"Invalid destination address\n");
19. return1;
20. }
22. /* 打开路由句柄 */
23. if((route = route_open())== NULL){
24. perror("route_open");
25. return1;
26. }
28. /* 查询路由 */
29. if(route_get(route,&entry)<0){
30. perror("route_get");
31. route_close(route);
32. return1;
33. }
35. /* 打印结果 */
36. dst_str = addr_ntoa(&entry.route_dst);
37. printf("Destination: %s\n", dst_str);
38. printf("Gateway: %s\n", addr_ntoa(&entry.route_gw));
39. printf("Interface: %s\n", entry.intf_name);
40. printf("Metric: %d\n", entry.metric);
42. /* 关闭句柄 */
43. route_close(route);
44. return0;
45. }
8.4 使用dnet命令行工具
查看路由表:
1. # 显示所有路由条目
2. $ ./test/dnet/dnet route show
3. DestinationGateway
4. 0.0.0.0192.168.1.1
5. 192.168.1.00.0.0.0
6. 192.168.2.0192.168.1.2
添加路由:
1. # 添加路由
2. $ sudo ./test/dnet/dnet route add 192.168.2.0/24192.168.1.2
3. add net 192.168.2.0/24: gateway 192.168.1.2
5. # 添加主机路由
6. $ sudo ./test/dnet/dnet route add 192.168.2.100192.168.1.2
7. add host 192.168.2.100: gateway 192.168.1.2
删除路由:
1. # 删除路由
2. $ sudo ./test/dnet/dnet route delete 192.168.2.0/24
3. delete net 192.168.2.0/24
查询路由:
1. # 查询路由
2. $ ./test/dnet/dnet route get 192.168.2.100
3. get host 192.168.2.100: gateway 192.168.1.2
8.5 路由监控工具
1. #include<stdio.h>
2. #include<stdlib.h>
3. #include<time.h>
4. #include<dnet.h>
6. /* 路由条目缓存 */
7. #define MAX_ROUTES 1024
8. staticstruct route_entry cache[MAX_ROUTES];
9. staticint cache_count =0;
11. /* 比较路由条目 */
12. staticint route_cmp(conststruct route_entry *a,
13. conststruct route_entry *b)
14. {
15. int cmp;
17. cmp = addr_cmp(&a->route_dst,&b->route_dst);
18. if(cmp !=0)
19. return cmp;
21. cmp = addr_cmp(&a->route_gw,&b->route_gw);
22. if(cmp !=0)
23. return cmp;
25. return strcmp(a->intf_name, b->intf_name);
26. }
28. /* 添加条目到缓存 */
29. staticvoid add_route(conststruct route_entry *entry)
30. {
31. if(cache_count < MAX_ROUTES){
32. cache[cache_count]=*entry;
33. cache_count++;
34. }
35. }
37. /* 查找条目 */
38. staticint find_route(conststruct route_entry *entry)
39. {
40. for(int i =0; i < cache_count; i++){
41. if(route_cmp(&cache[i], entry)==0)
42. return i;
43. }
44. return-1;
45. }
47. /* 回调函数: 检测变化 */
48. staticint detect_changes(conststruct route_entry *entry,void*arg)
49. {
50. int idx = find_route(entry);
51. time_t now = time(NULL);
53. if(idx <0){
54. /* 新路由 */
55. printf("[%s] NEW: %s/%d -> %s via %s\n",
56. ctime(&now),
57. addr_ntoa(&entry->route_dst),
58. entry->route_dst.addr_bits,
59. addr_ntoa(&entry->route_gw),
60. entry->intf_name);
61. add_route(entry);
62. }
63. /* 可以添加其他变化检测... */
65. return0;
66. }
68. int main(void)
69. {
70. route_t*route;
72. /* 打开路由句柄 */
73. if((route = route_open())== NULL){
74. perror("route_open");
75. return1;
76. }
78. printf("Route Monitor (Ctrl+C to exit)\n");
80. /* 定期检查 */
81. while(1){
82. route_loop(route, detect_changes, NULL);
83. sleep(5);/* 5秒间隔 */
84. }
86. route_close(route);
87. return0;
88. }
8.6 路由查找器
1. #include<stdio.h>
2. #include<stdlib.h>
3. #include<dnet.h>
5. /* 路由条目 */
6. struct route_ctx {
7. struct route_entry entry;
8. int found;
9. };
11. /* 回调函数: 查找最长前缀匹配 */
12. staticint find_best_match(conststruct route_entry *entry,void*arg)
13. {
14. struct route_ctx *ctx =(struct route_ctx *)arg;
15. int dst_bits = ctx->entry.route_dst.addr_bits;
17. /* 检查地址类型 */
18. if(entry->route_dst.addr_type != ctx->entry.route_dst.addr_type)
19. return0;
21. /* 检查是否匹配 */
22. if(addr_blong(&entry->route_dst,&ctx->entry.route_dst)){
23. /* 更长前缀匹配优先 */
24. if(!ctx->found ||
25. entry->route_dst.addr_bits > ctx->entry.route_dst.addr_bits){
26. ctx->entry =*entry;
27. ctx->found =1;
28. }
29. }
31. return0;
32. }
34. int main(int argc,char*argv[])
35. {
36. route_t*route;
37. struct route_ctx ctx;
39. if(argc !=2){
40. fprintf(stderr,"Usage: %s <destination>\n", argv[0]);
41. return1;
42. }
44. /* 解析目标地址 */
45. if(addr_pton(argv[1],&ctx.entry.route_dst)<0){
46. fprintf(stderr,"Invalid destination address\n");
47. return1;
48. }
49. ctx.found =0;
51. /* 打开路由句柄 */
52. if((route = route_open())== NULL){
53. perror("route_open");
54. return1;
55. }
57. /* 遍历路由表查找最长前缀匹配 */
58. if(route_loop(route, find_best_match,&ctx)<0){
59. perror("route_loop");
60. route_close(route);
61. return1;
62. }
64. /* 输出结果 */
65. if(ctx.found){
66. printf("Destination: %s\n", argv[1]);
67. printf("Gateway: %s\n", addr_ntoa(&ctx.entry.route_gw));
68. printf("Interface: %s\n", ctx.entry.intf_name);
69. printf("Metric: %d\n", ctx.entry.metric);
70. }else{
71. printf("No route found for %s\n", argv[1]);
72. }
74. /* 关闭句柄 */
75. route_close(route);
76. return0;
77. }
9. 常见问题与解决方案
9.1 权限问题
问题:添加/删除路由失败,errno=EPERM
原因:
- Linux: 需要root权限或CAPNETADMIN能力
- macOS/BSD/HP-UX: 需要root权限
- Windows: 需要管理员权限
解决方案:
1. # Linux: 使用sudo
2. sudo ./your_program
4. # 设置CAP_NET_ADMIN能力
5. sudo setcap cap_net_admin+ep ./your_program
7. # Windows: 以管理员身份运行
8. # 右键 -> 以管理员身份运行
代码检查:
1. if(route_add(route,&entry)<0){
2. if(errno == EPERM){
3. fprintf(stderr,"Error: Need root/administrator privileges\n");
4. fprintf(stderr,"Please run with sudo or as administrator\n");
5. }
6. perror("route_add");
7. return-1;
8. }
9.2 路由不存在
问题:route_get失败,errno=ESRCH
原因:
- 路由条目不存在
- 目标地址不匹配任何路由
解决方案:
1. /* 尝试ping触发路由 */
2. system("ping -c 1 192.168.2.100 > /dev/null 2>&1");
4. /* 等待路由建立 */
5. sleep(1);
7. /* 再次尝试获取 */
8. if(route_get(route,&entry)<0&& errno == ESRCH){
9. fprintf(stderr,"Route not found\n");
10. return-1;
11. }
9.3 默认路由问题
问题:查询默认路由失败
原因:
- Linux和HP-UX需要特殊处理默认路由
- 魔术地址不正确
解决方案:
1. /* Linux/HP-UX默认路由魔术地址 */
2. #define DEFAULT_ROUTE_MAGIC 0x60060606
4. /* 使用0.0.0.0/0表示默认路由 */
5. addr_pton("0.0.0.0/0",&entry.route_dst);
7. /* 查询 */
8. if(route_get(route,&entry)<0){
9. perror("route_get");
10. return-1;
11. }
9.4 IPv6支持
问题:某些平台不支持IPv6路由
原因:
- HP-UX不支持IPv6
- Windows传统API不支持IPv6
解决方案:
1. /* 检查平台IPv6支持 */
2. if(addr_pton("::1",&tmp)<0){
3. fprintf(stderr,"IPv6 not supported on this platform\n");
4. return-1;
5. }
7. /* Windows: 使用新API */
8. #ifdef _WIN32
9. /* 动态加载GetIpForwardTable2 */
10. GETIPFORWARDTABLE2 pGetIpForwardTable2;
11. pGetIpForwardTable2 =(GETIPFORWARDTABLE2)
12. GetProcAddress(iphlpapi,"GetIpForwardTable2");
14. if(pGetIpForwardTable2 == NULL){
15. fprintf(stderr,"IPv6 requires Windows Vista or later\n");
16. }
17. #endif
9.5 macOS/BSD sockaddr对齐
问题:macOS上出现内存错误
原因:
- sockaddr对齐处理不正确
- IPv6地址长度28字节需要特殊对齐
解决方案:
1. /* 使用正确的对齐宏 */
2. #ifdef __APPLE__
3. #define RT_MSGHDR_ALIGNMENT sizeof(uint32_t)/* 4字节对齐 */
4. #else
5. #define RT_MSGHDR_ALIGNMENT sizeof(unsignedlong)/* 8字节对齐 */
6. #endif
8. #define ROUNDUP(a) \
9. ((a)>0?(1+(((a)-1)|(RT_MSGHDR_ALIGNMENT -1))): \
10. RT_MSGHDR_ALIGNMENT)
9.6 Windows API错误
问题:Windows平台操作失败
常见错误码:
| 错误码 | 含义 | 解决方案 | | — | — | — | | ERRORACCESSDENIED | 权限不足 | 以管理员身份运行 | | ERRORINVALIDPARAMETER | 参数无效 | 检查地址格式 | | ERRORNOTSUPPORTED | 操作不支持 | 检查Windows版本 | | ERRORNODATA | 路由不存在 | 检查目标地址 |
调试代码:
1. void print_last_error(constchar*operation)
2. {
3. DWORD error =GetLastError();
4. if(error != NO_ERROR){
5. LPSTR msg = NULL;
6. FormatMessageA(
7. FORMAT_MESSAGE_ALLOCATE_BUFFER |
8. FORMAT_MESSAGE_FROM_SYSTEM |
9. FORMAT_MESSAGE_IGNORE_INSERTS,
10. NULL, error,0,(LPSTR)&msg,0, NULL);
12. fprintf(stderr,"%s failed: %s (Error %lu)\n",
13. operation, msg, error);
14. LocalFree(msg);
15. }
16. }
18. /* 使用 */
19. if(CreateIpForwardEntry(&ipfrow)!= NO_ERROR){
20. print_last_error("CreateIpForwardEntry");
21. return-1;
22. }
9.7 性能问题
问题:route_loop遍历慢
原因:
- Windows: 每次都全表扫描
- Linux: /proc文件解析效率低
解决方案:
使用缓存:
1. /* 路由表缓存 */
2. struct route_cache {
3. struct route_entry *entries;
4. int count;
5. time_t timestamp;
6. time_t ttl;/* 缓存生存时间 */
7. };
9. staticstruct route_cache cache ={NULL,0,0,60};/* 60秒缓存 */
11. /* 带缓存的遍历 */
12. int route_loop_cached(route_t*r, route_handler callback,void*arg)
13. {
14. time_t now = time(NULL);
16. /* 检查缓存是否有效 */
17. if(cache.entries != NULL &&(now - cache.timestamp)< cache.ttl){
18. /* 使用缓存 */
19. for(int i =0; i < cache.count; i++){
20. if(callback(&cache.entries[i], arg)!=0)
21. break;
22. }
23. return0;
24. }
26. /* 重建缓存... */
27. /* 类似ARP缓存的实现 */
28. }
9.8 网关地址为0
问题:macOS/BSD上网关地址为0
原因:
- 同网段路由的网关可能是
AF_LINK类型 - libdnet将其转换为全0地址
解决方案:
1. /* 检查网关是否为全0 */
2. if(addr_cmp(&entry.route_gw,&zero_addr)==0){
3. /* 这是同网段路由,直接发送到目标 */
4. printf("Direct route to %s\n", addr_ntoa(&entry.route_dst));
5. }else{
6. /* 这是网关路由,发送到网关 */
7. printf("Route via gateway %s\n", addr_ntoa(&entry.route_gw));
8. }
9.9 路由度量值
问题:不同平台度量值含义不同
原因:
- Linux: 单个度量值
- Windows: 多个度量值(Metric1-5)
- macOS/BSD: 不支持度量值
解决方案:
1. /* Linux/HP-UX */
2. entry.metric = ipfrow.dwForwardMetric1;
4. /* Windows: 综合度量 */
5. #ifdef _WIN32
6. /* 新API: 接口度量 + 路由度量 */
7. metric = ifrow.Metric+ row->Metric;
8. entry.metric =(metric < INT_MAX)? metric : INT_MAX;
9. #endif
9.10 调试技巧
启用详细日志:
1. #define ROUTE_DEBUG
3. #ifdef ROUTE_DEBUG
4. #define route_log(fmt,...) \
5. fprintf(stderr,"[ROUTE] " fmt "\n",##__VA_ARGS__)
6. #else
7. #define route_log(fmt,...)do{}while(0)
8. #endif
10. /* 使用 */
11. route_log("Adding route: dst=%s/%d, gw=%s",
12. addr_ntoa(&entry.route_dst),
13. entry.route_dst.addr_bits,
14. addr_ntoa(&entry.route_gw));
查看系统路由表:
1. # Linux
2. ip route show
3. netstat -rn
5. # macOS/BSD
6. netstat -rn
7. route -n get <destination>
9. # Windows
10. route print
11. netstat -rn
13. # HP-UX
14. netstat -rn
抓包分析:
1. # Linux
2. sudo tcpdump -i eth0 -nn 'ip proto 2'# IGMP
3. sudo tcpdump -i eth0 -nn icmp
5. # macOS/BSD
6. sudo tcpdump -i en0 -nn icmp
8. # Windows
9. # 使用Wireshark
附录A: 相关系统调用和API参考
Linux ioctl命令
| 命令 | 描述 | 参数 | | — | — | — | | SIOCADDRT | 添加路由 | struct rtentry * | | SIOCDELRT | 删除路由 | struct rtentry * |
Linux Netlink消息类型
| 类型 | 值 | 描述 | | — | — | — | | RTM_NEWROUTE | 0x24 | 新路由 | | RTM_DELROUTE | 0x25 | 删除路由 | | RTM_GETROUTE | 0x26 | 获取路由 |
BSD路由消息类型
| 类型 | 值 | 描述 | | — | — | — | | RTM_ADD | 0x1 | 添加路由 | | RTM_DELETE | 0x2 | 删除路由 | | RTM_CHANGE | 0x3 | 修改路由 | | RTM_GET | 0x4 | 获取路由 |
Windows IP Helper API
| API | 描述 | | — | — | | GetIpForwardTable | 获取IPv4路由表 | | GetIpForwardTable2 | 获取IPv4/IPv6路由表(Vista+) | | CreateIpForwardEntry | 创建IPv4路由 | | DeleteIpForwardEntry | 删除IPv4路由 | | GetBestRoute | 获取最佳路由 | | GetBestInterface | 获取最佳接口 |
附录B: 参考资料
RFC文档
- RFC 791: Internet Protocol
- RFC 2461: Neighbor Discovery for IPv6
系统文档
- Linux:
man7netlink,man7rtnetlink - FreeBSD:
man4route - macOS:
man4route
相关工具
- Linux:
ip route,route,netstat,tcpdump - macOS/BSD:
route,netstat,tcpdump - Windows:
route,netsh,netstat,Wireshark
文档版本: 1.0最后更新: 2026作者: libdnet源码分析适用版本: libdnet 1.13
- 公众号:安全狗的自我修养
- vx:2207344074
- http://gitee.com/haidragon
- http://github.com/haidragon
- bilibili:haidragonx
#
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全狗的自我修养 haidragon haidragon《跨平台底层网络库libdnet源码分析系列(五)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论