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

admin 2026-03-26 12:55:35 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入分析了跨平台底层网络库libdnet的路由表访问机制,这是理解mettle后门工具等恶意软件依赖库工作原理的关键。文章首先介绍了路由表的基础概念、条目结构与查找算法。核心部分详细解析了libdnet的路由接口设计,特别是Linux平台的实现,其中包括用于ioctl操作和Netlink通信的双socket混合机制,并给出了routeopen、routeadd、route_delete等关键函数的源码解读。此外,还对比了其在macOS/BSD、Windows等平台的适配逻辑。 综合评分: 85 文章分类: 二进制安全,恶意软件,逆向分析,技术标准,解决方案


cover_image

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

原创

haidragon haidragon

安全狗的自我修养

2026年3月23日 12:18 湖南

官网:http://securitytech.cc

#

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

#

#

路由表访问机制深入分析

目录

  1. 路由表基础概念
  2. libdnet路由接口设计
  3. Linux平台实现
  4. macOS/BSD平台实现
  5. HP-UX平台实现
  6. Windows平台实现
  7. 跨平台对比分析
  8. 实际应用示例
  9. 常见问题与解决方案

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混合机制

  1. ioctl + AF_INET socket:添加/删除路由
  2. Netlink socket:查询路由
  3. 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&nbsp;=&nbsp;socket(AF_INET,&nbsp;SOCK_DGRAM,0))<0)
13. return(route_close(r));

15. /* 创建Netlink socket用于路由查询 */
16. if((r->nlfd&nbsp;=&nbsp;socket(AF_NETLINK,&nbsp;SOCK_RAW,
17. NETLINK_ROUTE))<0)
18. return(route_close(r));

20. /* 绑定Netlink socket */
21. memset(&snl,0,sizeof(snl));
22. snl.nl_family&nbsp;=&nbsp;AF_NETLINK;

24. if(bind(r->nlfd,(struct&nbsp;sockaddr&nbsp;*)&snl,sizeof(snl))<0)
25. return(route_close(r));
26. }
27. return(r);
28. }

关键技术点:

  1. 双socket设计
  • fdAF_INET + SOCK_DGRAM,用于传统ioctl操作
  • nlfdAF_NETLINK + SOCK_RAW,用于现代Netlink通信
  1. Netlink绑定
  • 不需要指定具体PID,内核会自动分配
  • NETLINK_ROUTE表示路由子子系统

3.3 添加路由

实现源码:

1. /* 文件: src/route-linux.c 第71-92行 */
2. int
3. route_add(route_t*r,conststruct&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;rtentry rt;
6. struct&nbsp;addr dst;

8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags&nbsp;=&nbsp;RTF_UP&nbsp;|&nbsp;RTF_GATEWAY;/* 激活 + 网关 */

11. /* 判断是主机路由还是网络路由 */
12. if(ADDR_ISHOST(&entry->route_dst)){
13. rt.rt_flags&nbsp;|=&nbsp;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,&nbsp;SIOCADDRT,&rt));
26. }

宏定义:

1. /* 判断是否为主机地址 */
2. #define&nbsp;ADDR_ISHOST(a)(((a)->addr_type&nbsp;==&nbsp;ADDR_TYPE_IP&nbsp;&&&nbsp;\
3. (a)->addr_bits&nbsp;==&nbsp;IP_ADDR_BITS)||&nbsp;\
4. ((a)->addr_type&nbsp;==&nbsp;ADDR_TYPE_IP6&nbsp;&&&nbsp;\
5. (a)->addr_bits&nbsp;==&nbsp;IP6_ADDR_BITS))

ioctl命令:

  • SIOCADDRT: 添加路由条目
  • SIOCDELRT: 删除路由条目

rtentry结构:

1. /* Linux内核定义 */
2. struct&nbsp;rtentry&nbsp;{
3. unsignedlong&nbsp; rt_pad1;
4. struct&nbsp;sockaddr rt_dst;/* 目标地址 */
5. struct&nbsp;sockaddr rt_gateway;/* 网关地址 */
6. struct&nbsp;sockaddr rt_genmask;/* 子网掩码 */
7. unsignedshort&nbsp;rt_flags;/* 路由标志 */
8. short&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;rt_pad2;
9. unsignedlong&nbsp; rt_pad3;
10. void*rt_pad4;
11. short&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;rt_metric;/* 路由度量 */
12. char*rt_dev;/* 网络接口 */
13. unsignedlong&nbsp; rt_mtu;/* MTU */
14. unsignedlong&nbsp; rt_window;/* 窗口大小 */
15. unsignedshort&nbsp;rt_irtt;/* 往返时间 */
16. };

路由标志位:

1. #define&nbsp;RTF_UP &nbsp; &nbsp; &nbsp;0x0001/* 路由激活 */
2. #define&nbsp;RTF_GATEWAY&nbsp;0x0002/* 网关路由 */
3. #define&nbsp;RTF_HOST &nbsp; &nbsp;0x0004/* 主机路由 */
4. #define&nbsp;RTF_REINSTATE&nbsp;0x0008/* 恢复路由 */
5. #define&nbsp;RTF_DYNAMIC&nbsp;0x0010/* 动态路由 */
6. #define&nbsp;RTF_MODIFIED&nbsp;0x0020/* 修改的路由 */
7. #define&nbsp;RTF_MTU &nbsp; &nbsp;&nbsp;0x0040/* 指定MTU */
8. #define&nbsp;RTF_WINDOW &nbsp;0x0080/* 指定窗口 */
9. #define&nbsp;RTF_IRTT &nbsp; &nbsp;0x0100/* 初始往返时间 */
10. #define&nbsp;RTF_REJECT &nbsp;0x0200/* 拒绝路由 */

3.4 删除路由

实现源码:

1. /* 文件: src/route-linux.c 第94-114行 */
2. int
3. route_delete(route_t*r,conststruct&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;rtentry rt;
6. struct&nbsp;addr dst;

8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags&nbsp;=&nbsp;RTF_UP;/* 仅需要激活标志 */

11. /* 判断是主机路由还是网络路由 */
12. if(ADDR_ISHOST(&entry->route_dst)){
13. rt.rt_flags&nbsp;|=&nbsp;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,&nbsp;SIOCDELRT,&rt));
25. }

关键点:

  • 删除路由不需要网关地址
  • 只需要目标地址和子网掩码
  • 如果存在多条匹配路由,会删除所有匹配项

Linux使用Netlink socket查询路由,这是Linux 2.4+推荐的现代方式。

实现源码:

1. /* 文件: src/route-linux.c 第116-221行 */
2. int
3. route_get(route_t*r,struct&nbsp;route_entry&nbsp;*entry)
4. {
5. staticint&nbsp;seq;/* 序列号,用于匹配请求和响应 */
6. struct&nbsp;nlmsghdr&nbsp;*nmsg;
7. struct&nbsp;rtmsg&nbsp;*rmsg;
8. struct&nbsp;rtattr&nbsp;*rta;
9. struct&nbsp;sockaddr_nl snl;
10. struct&nbsp;iovec iov;
11. struct&nbsp;msghdr msg;
12. u_char buf[512];
13. int&nbsp;i,&nbsp;af,&nbsp;alen;
14. /* 根据地址类型确定地址族 */
15. switch(entry->route_dst.addr_type){
16. case&nbsp;ADDR_TYPE_IP:
17. af&nbsp;=&nbsp;AF_INET;
18. alen&nbsp;=&nbsp;IP_ADDR_LEN;
19. break;
20. case&nbsp;ADDR_TYPE_IP6:
21. af&nbsp;=&nbsp;AF_INET6;
22. alen&nbsp;=&nbsp;IP6_ADDR_LEN;
23. break;
24. default:
25. errno&nbsp;=&nbsp;EINVAL;
26. return(-1);
27. }
28. memset(buf,0,sizeof(buf));
29. /* 构建Netlink消息头 */
30. nmsg&nbsp;=(struct&nbsp;nlmsghdr&nbsp;*)buf;
31. nmsg->nlmsg_len&nbsp;=&nbsp;NLMSG_LENGTH(sizeof(*nmsg))+&nbsp;RTA_LENGTH(alen);
32. nmsg->nlmsg_flags&nbsp;=&nbsp;NLM_F_REQUEST;/* 请求标志 */
33. nmsg->nlmsg_type&nbsp;=&nbsp;RTM_GETROUTE;/* 获取路由 */
34. nmsg->nlmsg_seq&nbsp;=++seq;/* 序列号 */
35. /* 构建路由消息 */
36. rmsg&nbsp;=(struct&nbsp;rtmsg&nbsp;*)(nmsg&nbsp;+1);
37. rmsg->rtm_family&nbsp;=&nbsp;af;/* 地址族 */
38. rmsg->rtm_dst_len&nbsp;=&nbsp;entry->route_dst.addr_bits;/* 目标前缀长度 */
39. /* 添加目标地址属性 */
40. rta&nbsp;=&nbsp;RTM_RTA(rmsg);
41. rta->rta_type&nbsp;=&nbsp;RTA_DST;/* 目标地址属性 */
42. rta->rta_len&nbsp;=&nbsp;RTA_LENGTH(alen);
43. /* XXX - gross hack for default route */
44. if(af&nbsp;==&nbsp;AF_INET&nbsp;&&&nbsp;entry->route_dst.addr_ip&nbsp;==&nbsp;IP_ADDR_ANY){
45. /* 默认路由的特殊处理 */
46. i&nbsp;=&nbsp;htonl(0x60060606);
47. memcpy(RTA_DATA(rta),&i,&nbsp;alen);
48. }else
49. memcpy(RTA_DATA(rta),&nbsp;entry->route_dst.addr_data8,&nbsp;alen);
50. /* 准备发送消息 */
51. memset(&snl,0,sizeof(snl));
52. snl.nl_family&nbsp;=&nbsp;AF_NETLINK;
53. iov.iov_base&nbsp;=&nbsp;nmsg;
54. iov.iov_len&nbsp;=&nbsp;nmsg->nlmsg_len;
55. memset(&msg,0,sizeof(msg));
56. msg.msg_name&nbsp;=&snl;
57. msg.msg_namelen&nbsp;=sizeof(snl);
58. msg.msg_iov&nbsp;=&iov;
59. msg.msg_iovlen&nbsp;=1;
60. /* 发送Netlink请求 */
61. if(sendmsg(r->nlfd,&msg,0)<0)
62. return(-1);
63. /* 接收响应 */
64. iov.iov_base&nbsp;=&nbsp;buf;
65. iov.iov_len&nbsp;=sizeof(buf);
66. if((i&nbsp;=&nbsp;recvmsg(r->nlfd,&msg,0))<=0)
67. return(-1);
68. /* 验证响应 */
69. if(nmsg->nlmsg_len&nbsp;<(int)sizeof(*nmsg)||&nbsp;nmsg->nlmsg_len&nbsp;>&nbsp;i&nbsp;||
70. nmsg->nlmsg_seq&nbsp;!=&nbsp;seq){
71. errno&nbsp;=&nbsp;EINVAL;
72. return(-1);
73. }
74. if(nmsg->nlmsg_type&nbsp;==&nbsp;NLMSG_ERROR)
75. return(-1);
76. i&nbsp;-=&nbsp;NLMSG_LENGTH(sizeof(*nmsg));
77. /* 解析路由属性 */
78. entry->route_gw.addr_type&nbsp;=&nbsp;ADDR_TYPE_NONE;
79. entry->intf_name[0]='\0';
80. for(rta&nbsp;=&nbsp;RTM_RTA(rmsg);&nbsp;RTA_OK(rta,&nbsp;i);&nbsp;rta&nbsp;=&nbsp;RTA_NEXT(rta,&nbsp;i)){
81. if(rta->rta_type&nbsp;==&nbsp;RTA_GATEWAY){
82. /* 网关地址 */
83. entry->route_gw.addr_type&nbsp;=&nbsp;entry->route_dst.addr_type;
84. memcpy(entry->route_gw.addr_data8,&nbsp;RTA_DATA(rta),&nbsp;alen);
85. entry->route_gw.addr_bits&nbsp;=&nbsp;alen&nbsp;*8;
86. }elseif(rta->rta_type&nbsp;==&nbsp;RTA_OIF){
87. /* 出站接口 */
88. char&nbsp;ifbuf[IFNAMSIZ];
89. char*p;
90. int&nbsp;intf_index;
91. intf_index&nbsp;=*(int*)&nbsp;RTA_DATA(rta);
92. p&nbsp;=&nbsp;if_indextoname(intf_index,&nbsp;ifbuf);
93. if(p&nbsp;==&nbsp;NULL)
94. return(-1);
95. strlcpy(entry->intf_name,&nbsp;ifbuf,sizeof(entry->intf_name));
96. }
97. }
98. if(entry->route_gw.addr_type&nbsp;==&nbsp;ADDR_TYPE_NONE){
99. errno&nbsp;=&nbsp;ESRCH;
100. return(-1);
101. }
102. return(0);
103. }

Netlink消息结构:

1. /* Netlink消息头 */
2. struct&nbsp;nlmsghdr&nbsp;{
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&nbsp;rtmsg&nbsp;{
12. unsignedchar&nbsp;rtm_family;/* 地址族 */
13. unsignedchar&nbsp;rtm_dst_len;/* 目标前缀长度 */
14. unsignedchar&nbsp;rtm_src_len;/* 源前缀长度 */
15. unsignedchar&nbsp;rtm_tos;/* TOS */
16. unsignedchar&nbsp;rtm_table;/* 路由表ID */
17. unsignedchar&nbsp;rtm_protocol;/* 路由协议 */
18. unsignedchar&nbsp;rtm_scope;/* 路由范围 */
19. unsignedchar&nbsp;rtm_type;/* 路由类型 */
20. unsignedint&nbsp; rtm_flags;/* 路由标志 */
21. };

23. /* 路由属性 */
24. struct&nbsp;rtattr&nbsp;{
25. unsignedshort&nbsp;rta_len;/* 属性长度 */
26. unsignedshort&nbsp;rta_type;/* 属性类型 */
27. };

路由属性类型:

1. #define&nbsp;RTA_DST &nbsp; &nbsp; &nbsp; &nbsp;0x01/* 目标地址 */
2. #define&nbsp;RTA_SRC &nbsp; &nbsp; &nbsp; &nbsp;0x02/* 源地址 */
3. #define&nbsp;RTA_IIF &nbsp; &nbsp; &nbsp; &nbsp;0x03/* 入站接口 */
4. #define&nbsp;RTA_OIF &nbsp; &nbsp; &nbsp; &nbsp;0x04/* 出站接口 */
5. #define&nbsp;RTA_GATEWAY &nbsp; &nbsp;0x05/* 网关地址 */
6. #define&nbsp;RTA_PRIORITY &nbsp;&nbsp;0x06/* 优先级 */
7. #define&nbsp;RTA_PREFSRC &nbsp; &nbsp;0x07/* 首选源地址 */
8. #define&nbsp;RTA_METRICS &nbsp; &nbsp;0x08/* 度量值 */
9. #define&nbsp;RTA_MULTIPATH &nbsp;0x09/* 多路径 */
10. #define&nbsp;RTA_PROTOINFO &nbsp;0x0b/* 协议信息 */

Netlink消息类型:

1. #define&nbsp;RTM_NEWROUTE &nbsp;&nbsp;0x24/* 新路由 */
2. #define&nbsp;RTM_DELROUTE &nbsp;&nbsp;0x25/* 删除路由 */
3. #define&nbsp;RTM_GETROUTE &nbsp;&nbsp;0x26/* 获取路由 */

默认路由的特殊处理:

1. /* XXX - gross hack for default route */
2. if(af&nbsp;==&nbsp;AF_INET&nbsp;&&&nbsp;entry->route_dst.addr_ip&nbsp;==&nbsp;IP_ADDR_ANY){
3. i&nbsp;=&nbsp;htonl(0x60060606);/* 魔术地址 */
4. memcpy(RTA_DATA(rta),&i,&nbsp;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&nbsp;PROC_ROUTE_FILE &nbsp;"/proc/net/route"

4. int
5. route_loop(route_t*r,&nbsp;route_handler callback,void*arg)
6. {
7. FILE*fp;
8. struct&nbsp;route_entry entry;
9. char&nbsp;buf[BUFSIZ];
10. char&nbsp;ifbuf[16];
11. int&nbsp;ret&nbsp;=0;

13. if((fp&nbsp;=&nbsp;fopen(PROC_ROUTE_FILE,"r"))!=&nbsp;NULL){
14. int&nbsp;i,&nbsp;iflags,&nbsp;refcnt,&nbsp;use,&nbsp;metric,&nbsp;mss,&nbsp;win,&nbsp;irtt;
15. uint32_t&nbsp;mask;

17. /* 读取并解析每行 */
18. while(fgets(buf,sizeof(buf),&nbsp;fp)!=&nbsp;NULL){
19. i&nbsp;=&nbsp;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&nbsp;<11||!(iflags&nbsp;&&nbsp;RTF_UP))
33. continue;/* 跳过未激活的路由 */

35. strlcpy(entry.intf_name,&nbsp;ifbuf,sizeof(entry.intf_name));

37. entry.route_dst.addr_type&nbsp;=&nbsp;entry.route_gw.addr_type&nbsp;=&nbsp;ADDR_TYPE_IP;

39. /* 将掩码转换为前缀长度 */
40. if(addr_mtob(&mask,&nbsp;IP_ADDR_LEN,&entry.route_dst.addr_bits)<0)
41. continue;

43. entry.route_gw.addr_bits&nbsp;=&nbsp;IP_ADDR_BITS;
44. entry.metric&nbsp;=&nbsp;metric;

46. if((ret&nbsp;=&nbsp;callback(&entry,&nbsp;arg))!=0)
47. break;
48. }
49. fclose(fp);
50. }
51. /* ... IPv6处理 ... */
52. return(ret);
53. }

/proc/net/route文件格式:

1. IfaceDestinationGatewayFlagsRefCntUseMetricMask&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;MTU &nbsp;Window&nbsp; IRTT
2. eth0 &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;00000000010011AC000300000000000000
3. eth0 &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;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&nbsp;PROC_IPV6_ROUTE_FILE &nbsp;"/proc/net/ipv6_route"

4. if(ret&nbsp;==0&&(fp&nbsp;=&nbsp;fopen(PROC_IPV6_ROUTE_FILE,"r"))!=&nbsp;NULL){
5. char&nbsp;s[33],&nbsp;d[8][5],&nbsp;n[8][5];
6. int&nbsp;i,&nbsp;iflags,&nbsp;metric;
7. u_int slen,&nbsp;dlen;

9. while(fgets(buf,sizeof(buf),&nbsp;fp)!=&nbsp;NULL){
10. /* 解析IPv6路由条目 */
11. i&nbsp;=&nbsp;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],&nbsp;d[1],&nbsp;d[2],&nbsp;d[3],&nbsp;d[4],&nbsp;d[5],&nbsp;d[6],&nbsp;d[7],
15. &dlen,&nbsp;s,&slen,
16. n[0],&nbsp;n[1],&nbsp;n[2],&nbsp;n[3],&nbsp;n[4],&nbsp;n[5],&nbsp;n[6],&nbsp;n[7],
17. &metric,&iflags,&nbsp;ifbuf);

19. if(i&nbsp;<21||!(iflags&nbsp;&&nbsp;RTF_UP))
20. continue;

22. strlcpy(entry.intf_name,&nbsp;ifbuf,sizeof(entry.intf_name));

24. /* 构造目标IPv6地址字符串 */
25. snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",
26. d[0],&nbsp;d[1],&nbsp;d[2],&nbsp;d[3],&nbsp;d[4],&nbsp;d[5],&nbsp;d[6],&nbsp;d[7],&nbsp;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],&nbsp;n[1],&nbsp;n[2],&nbsp;n[3],&nbsp;n[4],&nbsp;n[5],&nbsp;n[6],&nbsp;n[7],
32. IP6_ADDR_BITS);
33. addr_aton(buf,&entry.route_gw);
34. entry.metric&nbsp;=&nbsp;metric;

36. if((ret&nbsp;=&nbsp;callback(&entry,&nbsp;arg))!=0)
37. break;
38. }
39. fclose(fp);
40. }

/proc/net/ipv6_route文件格式:

1. 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&nbsp;eth0
2. 20010db8000000000000000000000000400000000000000000000000000000000000&nbsp;fe8000000000000002cfffefea8fa8b&nbsp;00000002&nbsp;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&nbsp;!=&nbsp;NULL){
6. if(r->fd&nbsp;>=0)
7. close(r->fd);/* 关闭ioctl socket */
8. if(r->nlfd&nbsp;>=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

三种遍历方式:

  1. sysctl方式(推荐,macOS/BSD)
  2. getkerninfo方式(FreeBSD)
  3. STREAMS方式(Solaris/Irix)

4.2 句柄管理

路由句柄结构:

1. /* 文件: src/route-bsd.c 第81-87行 */
2. struct&nbsp;route_handle&nbsp;{
3. int&nbsp; fd;/* 路由套接字文件描述符 */
4. int&nbsp; seq;/* 序列号,用于匹配请求和响应 */
5. #ifdef&nbsp;HAVE_STREAMS_MIB2
6. int&nbsp; 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&nbsp;=&nbsp;calloc(1,sizeof(*r)))!=&nbsp;NULL){
8. r->fd&nbsp;=-1;
9. #ifdef&nbsp;HAVE_STREAMS_MIB2
10. /* Solaris MIB2方式 */
11. if((r->ip_fd&nbsp;=&nbsp;open(IP_DEV_NAME,&nbsp;O_RDWR))<0)
12. return(route_close(r));
13. #endif
14. #ifdef&nbsp;HAVE_STREAMS_ROUTE
15. /* STREAMS路由设备 */
16. if((r->fd&nbsp;=&nbsp;open("/dev/route",&nbsp;O_RDWR,0))<0)
17. #else
18. /* 标准BSD/macOS: 创建路由套接字 */
19. if((r->fd&nbsp;=&nbsp;socket(PF_ROUTE,&nbsp;SOCK_RAW,&nbsp;AF_INET))<0)
20. #endif
21. return(route_close(r));
22. }
23. return(r);
24. }

关键技术点:

  1. PF_ROUTE套接字
  • BSD/macOS特有的套接字族
  • 用于内核和用户空间的路由信息交换
  • SOCK_RAW表示原始路由套接字
  • AF_INET表示只处理IPv4路由
  1. 序列号机制
  • 用于匹配请求和响应
  • 每次发送消息递增

4.3 sockaddr对齐处理

BSD/macOS的路由消息中sockaddr结构需要对齐,但不同平台的对齐规则不同。

对齐宏定义:

1. /* 文件: src/route-bsd.c 第58-79行 */
2. #ifdef&nbsp;__APPLE__
3. #define&nbsp;RT_MSGHDR_ALIGNMENT&nbsp;sizeof(uint32_t)/* macOS: 4字节对齐 */
4. #else
5. #define&nbsp;RT_MSGHDR_ALIGNMENT&nbsp;sizeof(unsignedlong)/* BSD: 8字节对齐 */
6. #endif

8. #if&nbsp;defined(RT_ROUNDUP) && defined(__NetBSD__)
9. #define&nbsp;ROUNDUP(a)&nbsp;RT_ROUNDUP(a)
10. #else
11. #define&nbsp;ROUNDUP(a)&nbsp;\
12. ((a)>0?(1+(((a)-1)|(RT_MSGHDR_ALIGNMENT&nbsp;-1))):&nbsp;RT_MSGHDR_ALIGNMENT)
13. #endif

15. #ifdef&nbsp;HAVE_SOCKADDR_SA_LEN
16. #define&nbsp;NEXTSA(s)&nbsp;\
17. ((struct&nbsp;sockaddr&nbsp;*)((u_char&nbsp;*)(s)+&nbsp;ROUNDUP((s)->sa_len)))
18. #else
19. #define&nbsp;NEXTSA(s)&nbsp;\
20. ((struct&nbsp;sockaddr&nbsp;*)((u_char&nbsp;*)(s)+&nbsp;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&nbsp;type,char&nbsp;intf_name[INTF_NAME_LEN],
4. struct&nbsp;addr&nbsp;*dst,struct&nbsp;addr&nbsp;*gw)
5. {
6. struct&nbsp;addr net;
7. struct&nbsp;rt_msghdr&nbsp;*rtm;
8. struct&nbsp;sockaddr&nbsp;*sa;
9. u_char buf[BUFSIZ];
10. pid_t&nbsp;pid;
11. int&nbsp;len;

13. memset(buf,0,sizeof(buf));

15. rtm&nbsp;=(struct&nbsp;rt_msghdr&nbsp;*)buf;
16. rtm->rtm_version&nbsp;=&nbsp;RTM_VERSION;
17. rtm->rtm_type&nbsp;=&nbsp;type;
18. if(type&nbsp;!=&nbsp;RTM_DELETE)
19. rtm->rtm_flags&nbsp;=&nbsp;RTF_UP;
20. rtm->rtm_addrs&nbsp;=&nbsp;RTA_DST;/* 包含目标地址 */
21. rtm->rtm_seq&nbsp;=++r->seq;/* 序列号 */

23. /* 目标地址 */
24. sa&nbsp;=(struct&nbsp;sockaddr&nbsp;*)(rtm&nbsp;+1);
25. if(addr_net(dst,&net)<0||&nbsp;addr_ntos(&net,&nbsp;sa)<0)
26. return(-1);
27. sa&nbsp;=&nbsp;NEXTSA(sa);

29. /* 网关地址 */
30. if(gw&nbsp;!=&nbsp;NULL&nbsp;&&&nbsp;type&nbsp;!=&nbsp;RTM_GET){
31. rtm->rtm_flags&nbsp;|=&nbsp;RTF_GATEWAY;
32. rtm->rtm_addrs&nbsp;|=&nbsp;RTA_GATEWAY;
33. if(addr_ntos(gw,&nbsp;sa)<0)
34. return(-1);
35. sa&nbsp;=&nbsp;NEXTSA(sa);
36. }

38. /* 子网掩码 */
39. if(dst->addr_ip&nbsp;==&nbsp;IP_ADDR_ANY&nbsp;||&nbsp;dst->addr_bits&nbsp;<&nbsp;IP_ADDR_BITS){
40. rtm->rtm_addrs&nbsp;|=&nbsp;RTA_NETMASK;
41. if(addr_btos(dst->addr_bits,&nbsp;sa)<0)
42. return(-1);
43. sa&nbsp;=&nbsp;NEXTSA(sa);
44. }else
45. rtm->rtm_flags&nbsp;|=&nbsp;RTF_HOST;/* 主机路由 */

47. rtm->rtm_msglen&nbsp;=(u_char&nbsp;*)sa&nbsp;-&nbsp;buf;
48. #ifdef&nbsp;DEBUG
49. route_msg_print(rtm);
50. #endif
51. #ifdef&nbsp;HAVE_STREAMS_ROUTE
52. if(ioctl(r->fd,&nbsp;RTSTR_SEND,&nbsp;rtm)<0)
53. return(-1);
54. #else
55. /* 发送路由消息 */
56. if(write(r->fd,&nbsp;buf,&nbsp;rtm->rtm_msglen)<0)
57. return(-1);

59. pid&nbsp;=&nbsp;getpid();

61. /* 接收响应(仅RTM_GET) */
62. while(type&nbsp;==&nbsp;RTM_GET&nbsp;&&(len&nbsp;=&nbsp;read(r->fd,&nbsp;buf,sizeof(buf)))>0){
63. if(len&nbsp;<(int)sizeof(*rtm)){
64. return(-1);
65. }
66. /* 匹配响应 */
67. if(rtm->rtm_type&nbsp;==&nbsp;type&nbsp;&&&nbsp;rtm->rtm_pid&nbsp;==&nbsp;pid&nbsp;&&
68. rtm->rtm_seq&nbsp;==&nbsp;r->seq){
69. if(rtm->rtm_errno){
70. errno&nbsp;=&nbsp;rtm->rtm_errno;
71. return(-1);
72. }
73. break;
74. }
75. }
76. #endif
77. /* 解析响应 */
78. if(type&nbsp;==&nbsp;RTM_GET&nbsp;&&(rtm->rtm_addrs&nbsp;&(RTA_DST|RTA_GATEWAY))==
79. (RTA_DST|RTA_GATEWAY)){
80. sa&nbsp;=(struct&nbsp;sockaddr&nbsp;*)(rtm&nbsp;+1);
81. sa&nbsp;=&nbsp;NEXTSA(sa);

83. if(addr_ston(sa,&nbsp;gw)<0||&nbsp;gw->addr_type&nbsp;!=&nbsp;ADDR_TYPE_IP){
84. errno&nbsp;=&nbsp;ESRCH;
85. return(-1);
86. }

88. if(intf_name&nbsp;!=&nbsp;NULL){
89. char&nbsp;namebuf[IF_NAMESIZE];

91. if(if_indextoname(rtm->rtm_index,&nbsp;namebuf)==&nbsp;NULL){
92. errno&nbsp;=&nbsp;ESRCH;
93. return(-1);
94. }
95. strlcpy(intf_name,&nbsp;namebuf,&nbsp;INTF_NAME_LEN);
96. }
97. }
98. return(0);
99. }

路由消息类型:

1. #define&nbsp;RTM_ADD &nbsp; &nbsp; &nbsp;0x1/* 添加路由 */
2. #define&nbsp;RTM_DELETE &nbsp;&nbsp;0x2/* 删除路由 */
3. #define&nbsp;RTM_CHANGE &nbsp;&nbsp;0x3/* 修改路由 */
4. #define&nbsp;RTM_GET &nbsp; &nbsp; &nbsp;0x4/* 获取路由 */

地址标志位:

1. #define&nbsp;RTA_DST &nbsp; &nbsp; &nbsp;0x1/* 目标地址 */
2. #define&nbsp;RTA_GATEWAY &nbsp;0x2/* 网关地址 */
3. #define&nbsp;RTA_NETMASK &nbsp;0x4/* 网络掩码 */

路由标志位:

1. #define&nbsp;RTF_UP &nbsp; &nbsp; &nbsp;&nbsp;0x1/* 路由激活 */
2. #define&nbsp;RTF_GATEWAY &nbsp;0x2/* 网关路由 */
3. #define&nbsp;RTF_HOST &nbsp; &nbsp;&nbsp;0x4/* 主机路由 */
4. #define&nbsp;RTF_REJECT &nbsp;&nbsp;0x8/* 拒绝路由 */
5. #define&nbsp;RTF_DYNAMIC &nbsp;0x10/* 动态路由 */
6. #define&nbsp;RTF_MODIFIED&nbsp;0x20/* 修改的路由 */

4.5 添加路由

1. /* 文件: src/route-bsd.c 第212-223行 */
2. int
3. route_add(route_t*r,conststruct&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;route_entry rtent;

7. memcpy(&rtent,&nbsp;entry,sizeof(rtent));

9. if(route_msg(r,&nbsp;RTM_ADD,&nbsp;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&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;route_entry rtent;

7. memcpy(&rtent,&nbsp;entry,sizeof(rtent));

9. /* 先查询获取完整信息 */
10. if(route_get(r,&rtent)<0)
11. return(-1);

13. /* 删除路由 */
14. if(route_msg(r,&nbsp;RTM_DELETE,&nbsp;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&nbsp;route_entry&nbsp;*entry)
4. {
5. if(route_msg(r,&nbsp;RTM_GET,&nbsp;entry->intf_name,
6. &entry->route_dst,&entry->route_gw)<0)
7. return(-1);
8. entry->intf_name[0]='\0';
9. entry->metric&nbsp;=0;

11. return(0);
12. }

4.8 遍历路由表

方式1: sysctl方式(推荐)

1. /* 文件: src/route-bsd.c 第281-391行 */
2. #ifdef&nbsp;HAVE_SYS_SYSCTL_H
3. int
4. route_loop(route_t*r,&nbsp;route_handler callback,void*arg)
5. {
6. struct&nbsp;rt_msghdr&nbsp;*rtm;
7. struct&nbsp;route_entry entry;
8. struct&nbsp;sockaddr&nbsp;*sa;
9. char*buf,*lim,*next;
10. int&nbsp;ret;

12. /* sysctl MIB数组 */
13. int&nbsp;mib[6]={&nbsp;CTL_NET,&nbsp;PF_ROUTE,0,0/* XXX */,&nbsp;NET_RT_DUMP,0};
14. size_t&nbsp;len;

16. /* 第一次调用: 获取所需缓冲区大小 */
17. if(sysctl(mib,6,&nbsp;NULL,&len,&nbsp;NULL,0)<0)
18. return(-1);

20. if(len&nbsp;==0)
21. return(0);

23. /* 分配缓冲区 */
24. if((buf&nbsp;=&nbsp;malloc(len))==&nbsp;NULL)
25. return(-1);

27. /* 第二次调用: 获取实际数据 */
28. if(sysctl(mib,6,&nbsp;buf,&len,&nbsp;NULL,0)<0){
29. free(buf);
30. return(-1);
31. }
32. lim&nbsp;=&nbsp;buf&nbsp;+&nbsp;len;
33. next&nbsp;=&nbsp;buf;

35. /* 遍历所有路由消息 */
36. for(ret&nbsp;=0;&nbsp;next&nbsp;<&nbsp;lim;&nbsp;next&nbsp;+=&nbsp;rtm->rtm_msglen){
37. char&nbsp;namebuf[IF_NAMESIZE];
38. rtm&nbsp;=(struct&nbsp;rt_msghdr&nbsp;*)next;
39. sa&nbsp;=(struct&nbsp;sockaddr&nbsp;*)(rtm&nbsp;+1);

41. /* 获取接口名称 */
42. if(if_indextoname(rtm->rtm_index,&nbsp;namebuf)==&nbsp;NULL)
43. continue;
44. strlcpy(entry.intf_name,&nbsp;namebuf,sizeof(entry.intf_name));

46. /* 解析目标地址 */
47. if((rtm->rtm_addrs&nbsp;&&nbsp;RTA_DST)==0)
48. continue;
49. if(addr_ston(sa,&entry.route_dst)<0)
50. continue;

52. /* 解析网关地址 */
53. if((rtm->rtm_addrs&nbsp;&&nbsp;RTA_GATEWAY)==0)
54. continue;
55. sa&nbsp;=&nbsp;NEXTSA(sa);
56. if(addr_ston_gateway(&entry.route_dst,&nbsp;sa,&entry.route_gw)<0)
57. continue;

59. /* 验证地址类型 */
60. if(entry.route_dst.addr_type&nbsp;!=&nbsp;entry.route_gw.addr_type&nbsp;||
61. (entry.route_dst.addr_type&nbsp;!=&nbsp;ADDR_TYPE_IP&nbsp;&&
62. entry.route_dst.addr_type&nbsp;!=&nbsp;ADDR_TYPE_IP6))
63. continue;

65. /* 解析子网掩码 */
66. if(rtm->rtm_addrs&nbsp;&&nbsp;RTA_NETMASK){
67. sa&nbsp;=&nbsp;NEXTSA(sa);
68. if(addr_stob(sa,&entry.route_dst.addr_bits)<0)
69. continue;
70. }

72. entry.metric&nbsp;=0;

74. if((ret&nbsp;=&nbsp;callback(&entry,&nbsp;arg))!=0)
75. break;
76. }
77. free(buf);

79. return(ret);
80. }
81. #endif

sysctl MIB数组解析:

1. int&nbsp;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&nbsp;addr&nbsp;*dst,
4. conststruct&nbsp;sockaddr&nbsp;*sa,struct&nbsp;addr&nbsp;*a)
5. {
6. int&nbsp;rc;

8. rc&nbsp;=&nbsp;addr_ston(sa,&nbsp;a);
9. if(rc&nbsp;==0)
10. return&nbsp;rc;

12. #ifdef&nbsp;HAVE_NET_IF_DL_H
13. # ifdef AF_LINK
14. /* 如果网关是链路层地址,表示同网段路由 */
15. if(sa->sa_family&nbsp;==&nbsp;AF_LINK){
16. memset(a,0,sizeof(*a));
17. a->addr_type&nbsp;=&nbsp;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&nbsp;defined(HAVE_STREAMS_MIB2)

4. int
5. route_loop(route_t*r,&nbsp;route_handler callback,void*arg)
6. {
7. struct&nbsp;route_entry entry;
8. struct&nbsp;strbuf msg;
9. struct&nbsp;T_optmgmt_req&nbsp;*tor;
10. struct&nbsp;T_optmgmt_ack&nbsp;*toa;
11. struct&nbsp;T_error_ack&nbsp;*tea;
12. struct&nbsp;opthdr&nbsp;*opt;
13. u_char buf[8192];
14. int&nbsp;flags,&nbsp;rc,&nbsp;rtable,&nbsp;ret;

16. /* ... 发送MIB2查询请求 ... */
17. /* ... 接收并解析路由表 ... */

19. /* IPv4路由 */
20. for(;&nbsp;rt&nbsp;<&nbsp;rtend;&nbsp;rt++){
21. /* 过滤特定类型 */
22. if((rt->ipRouteInfo.re_ire_type&nbsp;&
23. (IRE_BROADCAST|IRE_ROUTE_REDIRECT|
24. IRE_LOCAL|IRE_ROUTE))!=0||
25. rt->ipRouteNextHop&nbsp;==&nbsp;IP_ADDR_ANY)
26. continue;

28. entry.intf_name[0]='\0';

30. sin.sin_addr.s_addr&nbsp;=&nbsp;rt->ipRouteNextHop;
31. addr_ston((struct&nbsp;sockaddr&nbsp;*)&sin,&entry.route_gw);

33. sin.sin_addr.s_addr&nbsp;=&nbsp;rt->ipRouteDest;
34. addr_ston((struct&nbsp;sockaddr&nbsp;*)&sin,&entry.route_dst);

36. sin.sin_addr.s_addr&nbsp;=&nbsp;rt->ipRouteMask;
37. addr_stob((struct&nbsp;sockaddr&nbsp;*)&sin,&entry.route_dst.addr_bits);

39. entry.metric&nbsp;=0;

41. if((ret&nbsp;=&nbsp;callback(&entry,&nbsp;arg))!=0)
42. return(ret);
43. }

45. /* IPv6路由(类似处理) */

47. return(0);
48. }

MIB2路由类型:

1. #define&nbsp;IRE_BROADCAST &nbsp; &nbsp;0x01/* 广播路由 */
2. #define&nbsp;IRE_LOCAL &nbsp; &nbsp; &nbsp; &nbsp;0x02/* 本地路由 */
3. #define&nbsp;IRE_CACHE &nbsp; &nbsp; &nbsp; &nbsp;0x04/* 缓存路由 */
4. #define&nbsp;IRE_HOST &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x08/* 主机路由 */
5. #define&nbsp;IRE_ROUTE &nbsp; &nbsp; &nbsp; &nbsp;0x10/* 网络路由 */
6. #define&nbsp;IRE_ROUTE_REDIRECT&nbsp;0x20/* 重定向路由 */

方式3: getkerninfo方式(FreeBSD)

1. /* 文件: src/route-bsd.c 第307-321行 */
2. #elif&nbsp;defined(HAVE_GETKERNINFO)
3. int&nbsp;len&nbsp;=&nbsp;getkerninfo(KINFO_RT_DUMP,0,0,0);

5. if(len&nbsp;==0)
6. return(0);

8. if((buf&nbsp;=&nbsp;malloc(len))==&nbsp;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&nbsp;defined(HAVE_NET_RADIX_H)
3. /* XXX - Tru64, others? */

5. staticint
6. _kread(int&nbsp;fd,void*addr,void*buf,int&nbsp;len)
7. {
8. if(lseek(fd,(off_t)addr,&nbsp;SEEK_SET)==(off_t)-1L)
9. return(-1);
10. return(read(fd,&nbsp;buf,&nbsp;len)==&nbsp;len&nbsp;?0:-1);
11. }

13. staticint
14. _radix_walk(int&nbsp;fd,struct&nbsp;radix_node&nbsp;*rn,&nbsp;route_handler callback,void*arg)
15. {
16. /* 遍历radix树 */
17. /* ... */
18. }

20. int
21. route_loop(route_t*r,&nbsp;route_handler callback,void*arg)
22. {
23. struct&nbsp;radix_node_head&nbsp;*rnh,&nbsp;head;
24. struct&nbsp;nlist nl[2];
25. int&nbsp;fd,&nbsp;ret&nbsp;=0;

27. memset(nl,0,sizeof(nl));
28. nl[0].n_name&nbsp;="radix_node_head";

30. /* 读取内核符号 */
31. if(knlist(nl)<0||&nbsp;nl[0].n_type&nbsp;==0||
32. (fd&nbsp;=&nbsp;open("/dev/kmem",&nbsp;O_RDONLY,0))<0)
33. return(-1);

35. /* 遍历radix树 */
36. for(_kread(fd,(void*)nl[0].n_value,&rnh,sizeof(rnh));
37. rnh&nbsp;!=&nbsp;NULL;&nbsp;rnh&nbsp;=&nbsp;head.rnh_next){
38. _kread(fd,&nbsp;rnh,&head,sizeof(head));
39. /* XXX - only IPv4 for now... */
40. if(head.rnh_af&nbsp;==&nbsp;AF_INET){
41. if((ret&nbsp;=&nbsp;_radix_walk(fd,&nbsp;head.rnh_treetop,
42. callback,&nbsp;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

两种方式:

  1. ioctl方式:添加/删除/查询路由
  2. MIB方式:遍历路由表

5.2 句柄管理

1. /* 文件: src/route-hpux.c 第31-33行 */
2. struct&nbsp;route_handle&nbsp;{
3. int&nbsp; fd;
4. };

6. route_t*
7. route_open(void)
8. {
9. route_t*r;

11. if((r&nbsp;=&nbsp;calloc(1,sizeof(*r)))!=&nbsp;NULL){
12. if((r->fd&nbsp;=&nbsp;socket(AF_INET,&nbsp;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&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;rtentry rt;
6. struct&nbsp;addr dst;

8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags&nbsp;=&nbsp;RTF_UP&nbsp;|&nbsp;RTF_GATEWAY;

11. if(ADDR_ISHOST(&entry->route_dst)){
12. rt.rt_flags&nbsp;|=&nbsp;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,&nbsp;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&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;rtentry rt;
6. struct&nbsp;addr dst;

8. memset(&rt,0,sizeof(rt));
9. rt.rt_flags&nbsp;=&nbsp;RTF_UP;

11. if(ADDR_ISHOST(&entry->route_dst)){
12. rt.rt_flags&nbsp;|=&nbsp;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,&nbsp;SIOCDELRT,&rt));
23. }

5.5 查询路由

1. /* 文件: src/route-hpux.c 第94-126行 */
2. int
3. route_get(route_t*r,struct&nbsp;route_entry&nbsp;*entry)
4. {
5. struct&nbsp;rtreq rtr;

7. memset(&rtr,0,sizeof(rtr));

9. /* XXX - gross hack for default route */
10. if(entry->route_dst.addr_ip&nbsp;==&nbsp;IP_ADDR_ANY){
11. rtr.rtr_destaddr&nbsp;=&nbsp;htonl(0x60060606);/* 魔术地址 */
12. rtr.rtr_subnetmask&nbsp;=0xffffffff;
13. }else{
14. memcpy(&rtr.rtr_destaddr,&entry->route_dst.addr_ip,
15. IP_ADDR_LEN);
16. if(entry->route_dst.addr_bits&nbsp;<&nbsp;IP_ADDR_BITS)
17. addr_btom(entry->route_dst.addr_bits,
18. &rtr.rtr_subnetmask,&nbsp;IP_ADDR_LEN);
19. }
20. if(ioctl(r->fd,&nbsp;SIOCGRTENTRY,&rtr)<0)
21. return(-1);

23. if(rtr.rtr_gwayaddr&nbsp;==0){
24. errno&nbsp;=&nbsp;ESRCH;
25. return(-1);
26. }
27. entry->intf_name[0]='\0';
28. entry->route_gw.addr_type&nbsp;=&nbsp;ADDR_TYPE_IP;
29. entry->route_gw.addr_bits&nbsp;=&nbsp;IP_ADDR_BITS;
30. memcpy(&entry->route_gw.addr_ip,&nbsp;rtr.rtr_gwayaddr,&nbsp;IP_ADDR_LEN);
31. entry->metric&nbsp;=0;

33. return(0);
34. }

HP-UX特有的rtreq结构:

1. struct&nbsp;rtreq&nbsp;{
2. uint32_t&nbsp;rtr_destaddr;/* 目标地址 */
3. uint32_t&nbsp;rtr_subnetmask;/* 子网掩码 */
4. uint32_t&nbsp;rtr_gwayaddr;/* 网关地址 */
5. /* ... 其他字段 ... */
6. };

默认路由处理:

  • 与Linux类似,使用魔术地址 0x60060606

5.6 遍历路由表

1. /* 文件: src/route-hpux.c 第128-173行 */
2. #define&nbsp;MAX_RTENTRIES &nbsp;256/* XXX */

4. int
5. route_loop(route_t*r,&nbsp;route_handler callback,void*arg)
6. {
7. struct&nbsp;nmparms nm;
8. struct&nbsp;route_entry entry;
9. mib_ipRouteEnt rtentries[MAX_RTENTRIES];
10. int&nbsp;fd,&nbsp;i,&nbsp;n,&nbsp;ret;

12. /* 打开MIB设备 */
13. if((fd&nbsp;=&nbsp;open_mib("/dev/ip",&nbsp;O_RDWR,0/* XXX */,0))<0)
14. return(-1);

16. nm.objid&nbsp;=&nbsp;ID_ipRouteTable;
17. nm.buffer&nbsp;=&nbsp;rtentries;
18. n&nbsp;=sizeof(rtentries);
19. nm.len&nbsp;=&n;

21. if(get_mib_info(fd,&nm)<0){
22. close_mib(fd);
23. return(-1);
24. }
25. close_mib(fd);

27. n&nbsp;/=sizeof(*rtentries);
28. ret&nbsp;=0;

30. for(i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;n;&nbsp;i++){
31. /* 过滤路由类型 */
32. if(rtentries[i].Type!=&nbsp;NMDIRECT&nbsp;&&
33. rtentries[i].Type!=&nbsp;NMREMOTE)
34. continue;

36. entry.intf_name[0]='\0';
37. entry.route_dst.addr_type&nbsp;=&nbsp;entry.route_gw.addr_type&nbsp;=&nbsp;ADDR_TYPE_IP;
38. entry.route_dst.addr_bits&nbsp;=&nbsp;entry.route_gw.addr_bits&nbsp;=&nbsp;IP_ADDR_BITS;
39. entry.route_dst.addr_ip&nbsp;=&nbsp;rtentries[i].Dest;
40. addr_mtob(&rtentries[i].Mask,&nbsp;IP_ADDR_LEN,
41. &entry.route_dst.addr_bits);
42. entry.route_gw.addr_ip&nbsp;=&nbsp;rtentries[i].NextHop;
43. entry.metric&nbsp;=0;

45. if((ret&nbsp;=&nbsp;callback(&entry,&nbsp;arg))!=0)
46. break;
47. }
48. return(ret);
49. }

MIB路由类型:

1. #define&nbsp;NMDIRECT &nbsp;&nbsp;1/* 直连路由 */
2. #define&nbsp;NMREMOTE &nbsp;&nbsp;2/* 远程路由 */

5.7 HP-UX平台特定问题

特殊结构:

  • rtreq结构不同于Linux
  • rt_subnetmask字段名不同于 rt_genmask

MIB方式:

  • 使用 open_mibget_mib_infoclose_mib
  • 需要打开 /dev/ip设备

默认路由:

  • 同样使用魔术地址处理

IPv6支持:

  • 不支持IPv6

6. Windows平台实现

6.1 实现架构

Windows平台使用IP Helper API管理路由表,核心文件为 src/route-win32.c

两种API版本:

  1. 传统API(Windows 2000+): GetIpForwardTable
  2. 新API(Vista+): GetIpForwardTable2

6.2 句柄管理

1. /* 文件: src/route-win32.c 第30-34行 */
2. struct&nbsp;route_handle&nbsp;{
3. HINSTANCE iphlpapi;/* IP Helper DLL句柄 */
4. MIB_IPFORWARDTABLE&nbsp;*ipftable;/* IPv4路由表缓存 */
5. MIB_IPFORWARD_TABLE2&nbsp;*ipftable2;/* IPv4/IPv6路由表缓存(Vista+) */
6. };

8. route_t*
9. route_open(void)
10. {
11. route_t*r;

13. r&nbsp;=&nbsp;calloc(1,sizeof(route_t));
14. if(r&nbsp;==&nbsp;NULL)
15. return&nbsp;NULL;
16. r->iphlpapi&nbsp;=GetModuleHandle("iphlpapi.dll");/* 获取DLL句柄 */

18. return&nbsp;r;
19. }

关键技术点:

  • 不打开任何系统资源
  • 缓存路由表以提高性能
  • 动态加载新API函数

6.3 添加路由

1. /* 文件: src/route-win32.c 第49-76行 */
2. int
3. route_add(route_t*route,conststruct&nbsp;route_entry&nbsp;*entry)
4. {
5. MIB_IPFORWARDROW ipfrow;
6. struct&nbsp;addr net;

8. memset(&ipfrow,0,sizeof(ipfrow));

10. /* 获取到网关的最佳接口索引 */
11. if(GetBestInterface(entry->route_gw.addr_ip,
12. &ipfrow.dwForwardIfIndex)!=&nbsp;NO_ERROR)
13. return(-1);

15. /* 计算网络地址 */
16. if(addr_net(&entry->route_dst,&net)<0||
17. net.addr_type&nbsp;!=&nbsp;ADDR_TYPE_IP)
18. return(-1);

20. /* 填充路由条目 */
21. ipfrow.dwForwardDest&nbsp;=&nbsp;net.addr_ip;
22. addr_btom(entry->route_dst.addr_bits,
23. &ipfrow.dwForwardMask,&nbsp;IP_ADDR_LEN);
24. ipfrow.dwForwardNextHop&nbsp;=&nbsp;entry->route_gw.addr_ip;
25. ipfrow.dwForwardType&nbsp;=4;/* XXX - next hop != final dest */
26. ipfrow.dwForwardProto&nbsp;=3;/* XXX - MIB_PROTO_NETMGMT */

28. /* 创建路由条目 */
29. if(CreateIpForwardEntry(&ipfrow)!=&nbsp;NO_ERROR)
30. return(-1);

32. return(0);
33. }

IP Helper API详解:

  1. GetBestInterface: c DWORDGetBestInterface(DWORD dwDestAddr,/* 目标IP */PDWORD pdwBestIfIndex/* 接收接口索引 */);
  • 查找到目标IP的最佳接口
  • 自动选择出站接口
  1. CreateIpForwardEntry: c DWORDCreateIpForwardEntry(PMIB_IPFORWARDROW pRoute/* 路由条目 */);
  • 创建IPv4路由条目

MIB_IPFORWARDROW结构:

1. typedefstruct&nbsp;_MIB_IPFORWARDROW&nbsp;{
2. DWORD &nbsp; &nbsp;dwForwardDest;/* 目标地址 */
3. DWORD &nbsp; &nbsp;dwForwardMask;/* 子网掩码 */
4. DWORD &nbsp; &nbsp;dwForwardPolicy;/* 策略 */
5. DWORD &nbsp; &nbsp;dwForwardNextHop;/* 下一跳地址 */
6. DWORD &nbsp; &nbsp;dwForwardIfIndex;/* 接口索引 */
7. DWORD &nbsp; &nbsp;dwForwardType;/* 路由类型 */
8. DWORD &nbsp; &nbsp;dwForwardProto;/* 路由协议 */
9. DWORD &nbsp; &nbsp;dwForwardAge;/* 路由生存时间 */
10. DWORD &nbsp; &nbsp;dwForwardNextHopAS;/* BGP AS号 */
11. DWORD &nbsp; &nbsp;dwForwardMetric1;/* 度量值1 */
12. DWORD &nbsp; &nbsp;dwForwardMetric2;/* 度量值2 */
13. DWORD &nbsp; &nbsp;dwForwardMetric3;/* 度量值3 */
14. DWORD &nbsp; &nbsp;dwForwardMetric4;/* 度量值4 */
15. DWORD &nbsp; &nbsp;dwForwardMetric5;/* 度量值5 */
16. }&nbsp;MIB_IPFORWARDROW,*PMIB_IPFORWARDROW;

路由类型:

1. #define&nbsp;MIB_IPROUTE_TYPE_OTHER &nbsp; &nbsp;1/* 其他 */
2. #define&nbsp;MIB_IPROUTE_TYPE_INVALID &nbsp;2/* 无效 */
3. #define&nbsp;MIB_IPROUTE_TYPE_DIRECT &nbsp;&nbsp;3/* 直连 */
4. #define&nbsp;MIB_IPROUTE_TYPE_INDIRECT&nbsp;4/* 间接(网关) */

路由协议:

1. #define&nbsp;MIB_IPPROTO_OTHER &nbsp; &nbsp;1/* 其他 */
2. #define&nbsp;MIB_IPPROTO_LOCAL &nbsp; &nbsp;2/* 本地 */
3. #define&nbsp;MIB_IPPROTO_NETMGMT &nbsp;3/* 网络管理(手动) */
4. #define&nbsp;MIB_IPPROTO_ICMP &nbsp; &nbsp;&nbsp;4/* ICMP重定向 */
5. #define&nbsp;MIB_IPPROTO_EGP &nbsp; &nbsp; &nbsp;5/* EGP */
6. #define&nbsp;MIB_IPPROTO_GGP &nbsp; &nbsp; &nbsp;6/* GGP */
7. #define&nbsp;MIB_IPPROTO_HELLO &nbsp; &nbsp;7/* HELLO */
8. #define&nbsp;MIB_IPPROTO_RIP &nbsp; &nbsp; &nbsp;8/* RIP */
9. #define&nbsp;MIB_IPPROTO_IS_IS &nbsp; &nbsp;9/* IS-IS */
10. #define&nbsp;MIB_IPPROTO_ES_IS &nbsp; &nbsp;10/* ES-IS */
11. #define&nbsp;MIB_IPPROTO_CISCO &nbsp; &nbsp;11/* Cisco IGRP */
12. #define&nbsp;MIB_IPPROTO_BBN &nbsp; &nbsp; &nbsp;12/* BBN SPF IGP */
13. #define&nbsp;MIB_IPPROTO_OSPF &nbsp; &nbsp;&nbsp;13/* OSPF */
14. #define&nbsp;MIB_IPPROTO_BGP &nbsp; &nbsp; &nbsp;14/* BGP */

6.4 删除路由

1. /* 文件: src/route-win32.c 第78-101行 */
2. int
3. route_delete(route_t*route,conststruct&nbsp;route_entry&nbsp;*entry)
4. {
5. MIB_IPFORWARDROW ipfrow;
6. DWORD mask;

8. if(entry->route_dst.addr_type&nbsp;!=&nbsp;ADDR_TYPE_IP&nbsp;||
9. GetBestRoute(entry->route_dst.addr_ip,
10. IP_ADDR_ANY,&ipfrow)!=&nbsp;NO_ERROR)
11. return(-1);

13. /* 计算子网掩码 */
14. addr_btom(entry->route_dst.addr_bits,&mask,&nbsp;IP_ADDR_LEN);

16. /* 验证路由匹配 */
17. if(ipfrow.dwForwardDest&nbsp;!=&nbsp;entry->route_dst.addr_ip&nbsp;||
18. ipfrow.dwForwardMask&nbsp;!=&nbsp;mask){
19. errno&nbsp;=&nbsp;ENXIO;
20. SetLastError(ERROR_NO_DATA);
21. return(-1);
22. }
23. /* 删除路由 */
24. if(DeleteIpForwardEntry(&ipfrow)!=&nbsp;NO_ERROR)
25. return(-1);

27. return(0);
28. }

DeleteIpForwardEntry API:

1. DWORD&nbsp;DeleteIpForwardEntry(
2. PMIB_IPFORWARDROW pRoute &nbsp;/* 要删除的路由条目 */
3. );

关键点:

  • 必须精确匹配目标地址和子网掩码
  • 需要先查询获取完整的路由条目

6.5 查询路由

1. /* 文件: src/route-win32.c 第103-140行 */
2. int
3. route_get(route_t*route,struct&nbsp;route_entry&nbsp;*entry)
4. {
5. MIB_IPFORWARDROW ipfrow;
6. DWORD mask;
7. intf_t*intf;
8. struct&nbsp;intf_entry intf_entry;

10. if(entry->route_dst.addr_type&nbsp;!=&nbsp;ADDR_TYPE_IP&nbsp;||
11. GetBestRoute(entry->route_dst.addr_ip,
12. IP_ADDR_ANY,&ipfrow)!=&nbsp;NO_ERROR)
13. return(-1);

15. /* 过滤本地路由 */
16. if(ipfrow.dwForwardProto&nbsp;==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&nbsp;=&nbsp;ENXIO;
21. SetLastError(ERROR_NO_DATA);
22. return(-1);
23. }
24. addr_btom(entry->route_dst.addr_bits,&mask,&nbsp;IP_ADDR_LEN);

26. entry->route_gw.addr_type&nbsp;=&nbsp;ADDR_TYPE_IP;
27. entry->route_gw.addr_bits&nbsp;=&nbsp;IP_ADDR_BITS;
28. entry->route_gw.addr_ip&nbsp;=&nbsp;ipfrow.dwForwardNextHop;
29. entry->metric&nbsp;=&nbsp;ipfrow.dwForwardMetric1;

31. /* 查询接口名称 */
32. entry->intf_name[0]='\0';
33. intf&nbsp;=&nbsp;intf_open();
34. if(intf_get_index(intf,&intf_entry,
35. AF_INET,&nbsp;ipfrow.dwForwardIfIndex)==0){
36. strlcpy(entry->intf_name,&nbsp;intf_entry.intf_name,sizeof(entry->intf_name));
37. }
38. intf_close(intf);

40. return(0);
41. }

GetBestRoute API:

1. DWORD&nbsp;GetBestRoute(
2. DWORD dwDestAddr,/* 目标IP */
3. DWORD dwSourceAddr,/* 源IP */
4. PMIB_IPFORWARDROW pBestRoute &nbsp;&nbsp;/* 接收最佳路由 */
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,&nbsp;route_handler callback,void*arg)
4. {
5. struct&nbsp;route_entry entry;
6. intf_t*intf;
7. ULONG len;
8. int&nbsp;i,&nbsp;ret;

10. /* 循环获取路由表,直到缓冲区足够大 */
11. for(len&nbsp;=sizeof(r->ipftable[0]);;){
12. if(r->ipftable)
13. free(r->ipftable);
14. r->ipftable&nbsp;=&nbsp;malloc(len);
15. if(r->ipftable&nbsp;==&nbsp;NULL)
16. return(-1);
17. ret&nbsp;=GetIpForwardTable(r->ipftable,&len,&nbsp;FALSE);
18. if(ret&nbsp;==&nbsp;NO_ERROR)
19. break;
20. elseif(ret&nbsp;!=&nbsp;ERROR_INSUFFICIENT_BUFFER)
21. return(-1);
22. }

24. intf&nbsp;=&nbsp;intf_open();

26. ret&nbsp;=0;
27. for(i&nbsp;=0;&nbsp;i&nbsp;<(int)r->ipftable->dwNumEntries;&nbsp;i++){
28. struct&nbsp;intf_entry intf_entry;

30. entry.route_dst.addr_type&nbsp;=&nbsp;ADDR_TYPE_IP;
31. entry.route_dst.addr_bits&nbsp;=&nbsp;IP_ADDR_BITS;

33. entry.route_gw.addr_type&nbsp;=&nbsp;ADDR_TYPE_IP;
34. entry.route_gw.addr_bits&nbsp;=&nbsp;IP_ADDR_BITS;

36. /* 解析路由条目 */
37. entry.route_dst.addr_ip&nbsp;=&nbsp;r->ipftable->table[i].dwForwardDest;
38. addr_mtob(&r->ipftable->table[i].dwForwardMask,&nbsp;IP_ADDR_LEN,
39. &entry.route_dst.addr_bits);
40. entry.route_gw.addr_ip&nbsp;=
41. r->ipftable->table[i].dwForwardNextHop;
42. entry.metric&nbsp;=&nbsp;r->ipftable->table[i].dwForwardMetric1;

44. /* 查询接口名称 */
45. entry.intf_name[0]='\0';
46. intf_entry.intf_len&nbsp;=sizeof(intf_entry);
47. if(intf_get_index(intf,&intf_entry,
48. AF_INET,&nbsp;r->ipftable->table[i].dwForwardIfIndex)==0){
49. strlcpy(entry.intf_name,&nbsp;intf_entry.intf_name,sizeof(entry.intf_name));
50. }

52. if((ret&nbsp;=(*callback)(&entry,&nbsp;arg))!=0)
53. break;
54. }

56. intf_close(intf);

58. return&nbsp;ret;
59. }

GetIpForwardTable API:

1. DWORD&nbsp;GetIpForwardTable(
2. PMIB_IPFORWARDTABLE pIpForwardTable,/* 接收路由表 */
3. PULONG pdwSize,/* 缓冲区大小(输入/输出) */
4. BOOL bOrder &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* 是否排序 */
5. );

7. /* MIB_IPFORWARDTABLE结构 */
8. typedefstruct&nbsp;_MIB_IPFORWARDTABLE&nbsp;{
9. DWORD &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dwNumEntries;/* 条目数量 */
10. MIB_IPFORWARDROW table[1];/* 条目数组(变长) */
11. }&nbsp;MIB_IPFORWARDTABLE,*PMIB_IPFORWARDTABLE;

方式2: 新API(Vista+)

1. /* 文件: src/route-win32.c 第199-254行 */
2. staticint
3. route_loop_getipforwardtable2(GETIPFORWARDTABLE2&nbsp;GetIpForwardTable2,
4. route_t*r,&nbsp;route_handler callback,void*arg)
5. {
6. struct&nbsp;route_entry entry;
7. intf_t*intf;
8. ULONG i;
9. int&nbsp;ret;

11. /* 获取路由表(Vista+) */
12. ret&nbsp;=GetIpForwardTable2(AF_UNSPEC,&r->ipftable2);
13. if(ret&nbsp;!=&nbsp;NO_ERROR)
14. return(-1);

16. intf&nbsp;=&nbsp;intf_open();

18. ret&nbsp;=0;
19. for(i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;r->ipftable2->NumEntries;&nbsp;i++){
20. struct&nbsp;intf_entry intf_entry;
21. MIB_IPFORWARD_ROW2&nbsp;*row;
22. MIB_IPINTERFACE_ROW ifrow;
23. ULONG metric;

25. row&nbsp;=&r->ipftable2->Table[i];
26. addr_ston((struct&nbsp;sockaddr&nbsp;*)&row->DestinationPrefix.Prefix,&entry.route_dst);
27. entry.route_dst.addr_bits&nbsp;=&nbsp;row->DestinationPrefix.PrefixLength;
28. addr_ston((struct&nbsp;sockaddr&nbsp;*)&row->NextHop,&entry.route_gw);

30. /* 查询接口名称 */
31. entry.intf_name[0]='\0';
32. intf_entry.intf_len&nbsp;=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,&nbsp;intf_entry.intf_name,sizeof(entry.intf_name));
37. }

39. /* 计算总度量值 */
40. ifrow.Family=&nbsp;row->DestinationPrefix.Prefix.si_family;
41. ifrow.InterfaceLuid=&nbsp;row->InterfaceLuid;
42. ifrow.InterfaceIndex=&nbsp;row->InterfaceIndex;
43. if(GetIpInterfaceEntry(&ifrow)!=&nbsp;NO_ERROR){
44. return(-1);
45. }
46. metric&nbsp;=&nbsp;ifrow.Metric+&nbsp;row->Metric;
47. if(metric&nbsp;<&nbsp;INT_MAX)
48. entry.metric&nbsp;=&nbsp;metric;
49. else
50. entry.metric&nbsp;=&nbsp;INT_MAX;

52. if((ret&nbsp;=(*callback)(&entry,&nbsp;arg))!=0)
53. break;
54. }

56. intf_close(intf);

58. return&nbsp;ret;
59. }

GetIpForwardTable2 API:

1. /* 动态加载 */
2. typedef&nbsp;DWORD&nbsp;(WINAPI&nbsp;*GETIPFORWARDTABLE2)(
3. ADDRESS_FAMILY&nbsp;Family,/* 地址族(AF_INET, AF_INET6, AF_UNSPEC) */
4. PMIB_IPFORWARD_TABLE2&nbsp;*Table/* 接收路由表 */
5. );

7. /* MIB_IPFORWARD_TABLE2结构 */
8. typedefstruct&nbsp;_MIB_IPFORWARD_TABLE2&nbsp;{
9. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NumEntries;/* 条目数量 */
10. MIB_IPFORWARD_ROW2 &nbsp; &nbsp; &nbsp;Table[1];/* 条目数组(变长) */
11. }&nbsp;MIB_IPFORWARD_TABLE2,*PMIB_IPFORWARD_TABLE2;

13. /* MIB_IPFORWARD_ROW2结构 */
14. typedefstruct&nbsp;_MIB_IPFORWARD_ROW2&nbsp;{
15. NET_LUID &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;InterfaceLuid;/* 接口LUID */
16. NET_IFINDEX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;InterfaceIndex;/* 接口索引 */
17. SOCKADDR_INET &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;DestinationPrefix;/* 目标前缀 */
18. SOCKADDR_INET &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NextHop;/* 下一跳 */
19. UCHAR &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;SitePrefixLength;/* 站点前缀长度 */
20. SOCKADDR_INET &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;SitePrefix;/* 站点前缀 */
21. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ValidLifetime;/* 有效生存时间 */
22. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;PreferredLifetime;/* 首选生存时间 */
23. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Metric;/* 路由度量 */
24. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Protocol;/* 路由协议 */
25. BOOLEAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Loopback;/* 是否回环 */
26. BOOLEAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;AutoconfigureAddress;/* 是否自动配置 */
27. BOOLEAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Publish;/* 是否发布 */
28. BOOLEAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Immortal;/* 是否永久 */
29. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Age;/* 路由年龄 */
30. ULONG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Origin;/* 路由来源 */
31. }&nbsp;MIB_IPFORWARD_ROW2,*PMIB_IPFORWARD_ROW2;

动态API选择:

1. /* 文件: src/route-win32.c 第256-270行 */
2. int
3. route_loop(route_t*r,&nbsp;route_handler callback,void*arg)
4. {
5. GETIPFORWARDTABLE2&nbsp;GetIpForwardTable2;

7. /* GetIpForwardTable2仅Vista及以上可用,动态加载 */
8. GetIpForwardTable2=&nbsp;NULL;
9. if(r->iphlpapi&nbsp;!=&nbsp;NULL)
10. GetIpForwardTable2=(GETIPFORWARDTABLE2)GetProcAddress(
11. r->iphlpapi,"GetIpForwardTable2");

13. if(GetIpForwardTable2==&nbsp;NULL)
14. return&nbsp;route_loop_getipforwardtable(r,&nbsp;callback,&nbsp;arg);
15. else
16. return&nbsp;route_loop_getipforwardtable2(GetIpForwardTable2,&nbsp;r,&nbsp;callback,&nbsp;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&nbsp;!=&nbsp;NULL){
6. if(r->iphlpapi&nbsp;!=&nbsp;NULL)
7. FreeLibrary(r->iphlpapi);
8. if(r->ipftable&nbsp;!=&nbsp;NULL)
9. free(r->ipftable);
10. if(r->ipftable2&nbsp;!=&nbsp;NULL)
11. FreeMibTable(r->ipftable2);/* Vista+ */
12. free(r);
13. }
14. return(NULL);
15. }

FreeMibTable API:

1. /* Vista+ */
2. voidFreeMibTable(
3. PVOID&nbsp;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&nbsp;rtentry&nbsp;{
2. struct&nbsp;sockaddr rt_dst;/* 目标地址 */
3. struct&nbsp;sockaddr rt_gateway;/* 网关地址 */
4. struct&nbsp;sockaddr rt_genmask;/* 子网掩码 */
5. unsignedshort&nbsp; rt_flags;/* 路由标志 */
6. short&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;rt_metric;/* 路由度量 */
7. char*rt_dev;/* 网络接口 */
8. };

macOS/BSD: rt_msghdr + sockaddr

1. struct&nbsp;rt_msghdr&nbsp;{
2. u_short rtm_msglen;/* 消息长度 */
3. u_char &nbsp;rtm_version;/* 版本 */
4. u_char &nbsp;rtm_type;/* 消息类型 */
5. u_short rtm_index;/* 接口索引 */
6. int&nbsp; &nbsp; &nbsp;rtm_flags;/* 路由标志 */
7. int&nbsp; &nbsp; &nbsp;rtm_addrs;/* 地址掩码 */
8. /* ... */
9. char&nbsp; &nbsp; rtm_data[512];/* 地址数据 */
10. };

HP-UX: rtreq

1. struct&nbsp;rtreq&nbsp;{
2. uint32_t&nbsp;rtr_destaddr;/* 目标地址 */
3. uint32_t&nbsp;rtr_subnetmask;/* 子网掩码 */
4. uint32_t&nbsp;rtr_gwayaddr;/* 网关地址 */
5. };

Windows: MIB_IPFORWARDROW

1. typedefstruct&nbsp;_MIB_IPFORWARDROW&nbsp;{
2. DWORD dwForwardDest;/* 目标地址 */
3. DWORD dwForwardMask;/* 子网掩码 */
4. DWORD dwForwardNextHop;/* 下一跳地址 */
5. DWORD dwForwardIfIndex;/* 接口索引 */
6. DWORD dwForwardType;/* 路由类型 */
7. DWORD dwForwardProto;/* 路由协议 */
8. DWORD dwForwardMetric1;/* 度量值1 */
9. }&nbsp;MIB_IPFORWARDROW;

7.3 操作方式对比

Linux: ioctl + Netlink混合

1. /* 优点 */
2. -添加/删除使用ioctl,简单直接
3. -查询使用Netlink,功能强大
4. -支持IPv4/IPv6
5. -&nbsp;proc文件易于调试

7. /* 缺点 */
8. -双socket增加复杂度
9. -Netlink消息格式复杂
10. -默认路由需要特殊处理

macOS/BSD: 路由消息

1. /* 优点 */
2. -统一的消息机制
3. -支持IPv4/IPv6
4. -&nbsp;sysctl遍历性能高
5. -消息格式灵活

7. /* 缺点 */
8. -&nbsp;sockaddr对齐复杂
9. -需要序列号匹配
10. -消息格式复杂
11. -删除路由需要先查询

HP-UX: ioctl + MIB

1. /* 优点 */
2. -&nbsp;ioctl操作简单
3. -&nbsp;MIB方式统一

5. /* 缺点 */
6. -特有结构(rtreq)
7. -不支持IPv6
8. -默认路由需要特殊处理

Windows: IP Helper API

1. /* 优点 */
2. -高层API,易用
3. -不需要句柄
4. -自动处理接口索引
5. -新API支持IPv6

7. /* 缺点 */
8. -依赖Windows&nbsp;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. -&nbsp;sockaddr对齐规则变化
4. -不同BSD版本差异
5. -路由消息格式变化
6. -网关地址特殊处理

8. /* 优点 */
9. -&nbsp;sysctl方式稳定
10. -逻辑清晰

HP-UX (route-hpux.c):

1. /* 维护难点 */
2. -平台特有结构
3. -&nbsp;MIB接口变化
4. -默认路由特殊处理

6. /* 优点 */
7. -代码简单
8. -变化较少

Windows (route-win32.c):

1. /* 维护难点 */
2. -&nbsp;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&nbsp;main(void)
5. {
6. route_t*route;
7. struct&nbsp;route_entry entry;

9. /* 打开路由句柄 */
10. if((route&nbsp;=&nbsp;route_open())==&nbsp;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&nbsp;main(void)
2. {
3. route_t*route;
4. struct&nbsp;route_entry entry;

6. if((route&nbsp;=&nbsp;route_open())==&nbsp;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&nbsp;print_route(conststruct&nbsp;route_entry&nbsp;*entry,void*arg)
7. {
8. constchar*dst_type;
9. char&nbsp;dst_str[64],&nbsp;gw_str[64];

11. /* 判断是主机路由还是网络路由 */
12. if(entry->route_dst.addr_bits&nbsp;==&nbsp;IP_ADDR_BITS&nbsp;||
13. entry->route_dst.addr_bits&nbsp;==&nbsp;IP6_ADDR_BITS){
14. dst_type&nbsp;="host";
15. }else{
16. dst_type&nbsp;="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,&nbsp;dst_type,&nbsp;gw_str,&nbsp;entry->intf_name,&nbsp;entry.metric);
28. return0;
29. }

31. int&nbsp;main(void)
32. {
33. route_t*route;

35. /* 打开路由句柄 */
36. if((route&nbsp;=&nbsp;route_open())==&nbsp;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,&nbsp;print_route,&nbsp;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&nbsp;main(int&nbsp;argc,char*argv[])
6. {
7. route_t*route;
8. struct&nbsp;route_entry entry;
9. constchar*dst_str;

11. if(argc&nbsp;!=2){
12. fprintf(stderr,"Usage: %s <destination>\n",&nbsp;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&nbsp;=&nbsp;route_open())==&nbsp;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&nbsp;=&nbsp;addr_ntoa(&entry.route_dst);
37. printf("Destination: %s\n",&nbsp;dst_str);
38. printf("Gateway: &nbsp; &nbsp; %s\n",&nbsp;addr_ntoa(&entry.route_gw));
39. printf("Interface: &nbsp; %s\n",&nbsp;entry.intf_name);
40. printf("Metric: &nbsp; &nbsp; &nbsp;%d\n",&nbsp;entry.metric);

42. /* 关闭句柄 */
43. route_close(route);
44. return0;
45. }

8.4 使用dnet命令行工具

查看路由表:

1. # 显示所有路由条目
2. $&nbsp;./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&nbsp;./test/dnet/dnet route add&nbsp;192.168.2.0/24192.168.1.2
3. add net&nbsp;192.168.2.0/24:&nbsp;gateway&nbsp;192.168.1.2

5. # 添加主机路由
6. $ sudo&nbsp;./test/dnet/dnet route add&nbsp;192.168.2.100192.168.1.2
7. add host&nbsp;192.168.2.100:&nbsp;gateway&nbsp;192.168.1.2

删除路由:

1. # 删除路由
2. $ sudo&nbsp;./test/dnet/dnet route delete&nbsp;192.168.2.0/24
3. delete net&nbsp;192.168.2.0/24

查询路由:

1. # 查询路由
2. $&nbsp;./test/dnet/dnet route get&nbsp;192.168.2.100
3. get host&nbsp;192.168.2.100:&nbsp;gateway&nbsp;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&nbsp;MAX_ROUTES&nbsp;1024
8. staticstruct&nbsp;route_entry cache[MAX_ROUTES];
9. staticint&nbsp;cache_count&nbsp;=0;

11. /* 比较路由条目 */
12. staticint&nbsp;route_cmp(conststruct&nbsp;route_entry&nbsp;*a,
13. conststruct&nbsp;route_entry&nbsp;*b)
14. {
15. int&nbsp;cmp;

17. cmp&nbsp;=&nbsp;addr_cmp(&a->route_dst,&b->route_dst);
18. if(cmp&nbsp;!=0)
19. return&nbsp;cmp;

21. cmp&nbsp;=&nbsp;addr_cmp(&a->route_gw,&b->route_gw);
22. if(cmp&nbsp;!=0)
23. return&nbsp;cmp;

25. return&nbsp;strcmp(a->intf_name,&nbsp;b->intf_name);
26. }

28. /* 添加条目到缓存 */
29. staticvoid&nbsp;add_route(conststruct&nbsp;route_entry&nbsp;*entry)
30. {
31. if(cache_count&nbsp;<&nbsp;MAX_ROUTES){
32. cache[cache_count]=*entry;
33. cache_count++;
34. }
35. }

37. /* 查找条目 */
38. staticint&nbsp;find_route(conststruct&nbsp;route_entry&nbsp;*entry)
39. {
40. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;cache_count;&nbsp;i++){
41. if(route_cmp(&cache[i],&nbsp;entry)==0)
42. return&nbsp;i;
43. }
44. return-1;
45. }

47. /* 回调函数: 检测变化 */
48. staticint&nbsp;detect_changes(conststruct&nbsp;route_entry&nbsp;*entry,void*arg)
49. {
50. int&nbsp;idx&nbsp;=&nbsp;find_route(entry);
51. time_t&nbsp;now&nbsp;=&nbsp;time(NULL);

53. if(idx&nbsp;<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&nbsp;main(void)
69. {
70. route_t*route;

72. /* 打开路由句柄 */
73. if((route&nbsp;=&nbsp;route_open())==&nbsp;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,&nbsp;detect_changes,&nbsp;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&nbsp;route_ctx&nbsp;{
7. struct&nbsp;route_entry entry;
8. int&nbsp;found;
9. };

11. /* 回调函数: 查找最长前缀匹配 */
12. staticint&nbsp;find_best_match(conststruct&nbsp;route_entry&nbsp;*entry,void*arg)
13. {
14. struct&nbsp;route_ctx&nbsp;*ctx&nbsp;=(struct&nbsp;route_ctx&nbsp;*)arg;
15. int&nbsp;dst_bits&nbsp;=&nbsp;ctx->entry.route_dst.addr_bits;

17. /* 检查地址类型 */
18. if(entry->route_dst.addr_type&nbsp;!=&nbsp;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&nbsp;||
25. entry->route_dst.addr_bits&nbsp;>&nbsp;ctx->entry.route_dst.addr_bits){
26. ctx->entry&nbsp;=*entry;
27. ctx->found&nbsp;=1;
28. }
29. }

31. return0;
32. }

34. int&nbsp;main(int&nbsp;argc,char*argv[])
35. {
36. route_t*route;
37. struct&nbsp;route_ctx ctx;

39. if(argc&nbsp;!=2){
40. fprintf(stderr,"Usage: %s <destination>\n",&nbsp;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&nbsp;=0;

51. /* 打开路由句柄 */
52. if((route&nbsp;=&nbsp;route_open())==&nbsp;NULL){
53. perror("route_open");
54. return1;
55. }

57. /* 遍历路由表查找最长前缀匹配 */
58. if(route_loop(route,&nbsp;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",&nbsp;argv[1]);
67. printf("Gateway: &nbsp; &nbsp; %s\n",&nbsp;addr_ntoa(&ctx.entry.route_gw));
68. printf("Interface: &nbsp; %s\n",&nbsp;ctx.entry.intf_name);
69. printf("Metric: &nbsp; &nbsp; &nbsp;%d\n",&nbsp;ctx.entry.metric);
70. }else{
71. printf("No route found for %s\n",&nbsp;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&nbsp;./your_program

4. # 设置CAP_NET_ADMIN能力
5. sudo setcap cap_net_admin+ep&nbsp;./your_program

7. # Windows: 以管理员身份运行
8. # 右键 -> 以管理员身份运行

代码检查:

1. if(route_add(route,&entry)<0){
2. if(errno&nbsp;==&nbsp;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&&&nbsp;errno&nbsp;==&nbsp;ESRCH){
9. fprintf(stderr,"Route not found\n");
10. return-1;
11. }

9.3 默认路由问题

问题:查询默认路由失败

原因:

  • Linux和HP-UX需要特殊处理默认路由
  • 魔术地址不正确

解决方案:

1. /* Linux/HP-UX默认路由魔术地址 */
2. #define&nbsp;DEFAULT_ROUTE_MAGIC &nbsp;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&nbsp;_WIN32
9. /* 动态加载GetIpForwardTable2 */
10. GETIPFORWARDTABLE2 pGetIpForwardTable2;
11. pGetIpForwardTable2&nbsp;=(GETIPFORWARDTABLE2)
12. GetProcAddress(iphlpapi,"GetIpForwardTable2");

14. if(pGetIpForwardTable2&nbsp;==&nbsp;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&nbsp;__APPLE__
3. #define&nbsp;RT_MSGHDR_ALIGNMENT&nbsp;sizeof(uint32_t)/* 4字节对齐 */
4. #else
5. #define&nbsp;RT_MSGHDR_ALIGNMENT&nbsp;sizeof(unsignedlong)/* 8字节对齐 */
6. #endif

8. #define&nbsp;ROUNDUP(a)&nbsp;\
9. ((a)>0?(1+(((a)-1)|(RT_MSGHDR_ALIGNMENT&nbsp;-1))):&nbsp;\
10. RT_MSGHDR_ALIGNMENT)

9.6 Windows API错误

问题:Windows平台操作失败

常见错误码:

| 错误码 | 含义 | 解决方案 | | — | — | — | | ERRORACCESSDENIED | 权限不足 | 以管理员身份运行 | | ERRORINVALIDPARAMETER | 参数无效 | 检查地址格式 | | ERRORNOTSUPPORTED | 操作不支持 | 检查Windows版本 | | ERRORNODATA | 路由不存在 | 检查目标地址 |

调试代码:

1. void&nbsp;print_last_error(constchar*operation)
2. {
3. DWORD error&nbsp;=GetLastError();
4. if(error&nbsp;!=&nbsp;NO_ERROR){
5. LPSTR msg&nbsp;=&nbsp;NULL;
6. FormatMessageA(
7. FORMAT_MESSAGE_ALLOCATE_BUFFER&nbsp;|
8. FORMAT_MESSAGE_FROM_SYSTEM&nbsp;|
9. FORMAT_MESSAGE_IGNORE_INSERTS,
10. NULL,&nbsp;error,0,(LPSTR)&msg,0,&nbsp;NULL);

12. fprintf(stderr,"%s failed: %s (Error %lu)\n",
13. operation,&nbsp;msg,&nbsp;error);
14. LocalFree(msg);
15. }
16. }

18. /* 使用 */
19. if(CreateIpForwardEntry(&ipfrow)!=&nbsp;NO_ERROR){
20. print_last_error("CreateIpForwardEntry");
21. return-1;
22. }

9.7 性能问题

问题:route_loop遍历慢

原因:

  • Windows: 每次都全表扫描
  • Linux: /proc文件解析效率低

解决方案:

使用缓存:

1. /* 路由表缓存 */
2. struct&nbsp;route_cache&nbsp;{
3. struct&nbsp;route_entry&nbsp;*entries;
4. int&nbsp;count;
5. time_t&nbsp;timestamp;
6. time_t&nbsp;ttl;/* 缓存生存时间 */
7. };

9. staticstruct&nbsp;route_cache cache&nbsp;={NULL,0,0,60};/* 60秒缓存 */

11. /* 带缓存的遍历 */
12. int&nbsp;route_loop_cached(route_t*r,&nbsp;route_handler callback,void*arg)
13. {
14. time_t&nbsp;now&nbsp;=&nbsp;time(NULL);

16. /* 检查缓存是否有效 */
17. if(cache.entries&nbsp;!=&nbsp;NULL&nbsp;&&(now&nbsp;-&nbsp;cache.timestamp)<&nbsp;cache.ttl){
18. /* 使用缓存 */
19. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;cache.count;&nbsp;i++){
20. if(callback(&cache.entries[i],&nbsp;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",&nbsp;addr_ntoa(&entry.route_dst));
5. }else{
6. /* 这是网关路由,发送到网关 */
7. printf("Route via gateway %s\n",&nbsp;addr_ntoa(&entry.route_gw));
8. }

9.9 路由度量值

问题:不同平台度量值含义不同

原因:

  • Linux: 单个度量值
  • Windows: 多个度量值(Metric1-5)
  • macOS/BSD: 不支持度量值

解决方案:

1. /* Linux/HP-UX */
2. entry.metric&nbsp;=&nbsp;ipfrow.dwForwardMetric1;

4. /* Windows: 综合度量 */
5. #ifdef&nbsp;_WIN32
6. /* 新API: 接口度量 + 路由度量 */
7. metric&nbsp;=&nbsp;ifrow.Metric+&nbsp;row->Metric;
8. entry.metric&nbsp;=(metric&nbsp;<&nbsp;INT_MAX)?&nbsp;metric&nbsp;:&nbsp;INT_MAX;
9. #endif

9.10 调试技巧

启用详细日志:

1. #define&nbsp;ROUTE_DEBUG

3. #ifdef&nbsp;ROUTE_DEBUG
4. #define&nbsp;route_log(fmt,...)&nbsp;\
5. fprintf(stderr,"[ROUTE] "&nbsp;fmt&nbsp;"\n",##__VA_ARGS__)
6. #else
7. #define&nbsp;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&nbsp;-rn

5. # macOS/BSD
6. netstat&nbsp;-rn
7. route&nbsp;-n get&nbsp;<destination>

9. # Windows
10. route print
11. netstat&nbsp;-rn

13. # HP-UX
14. netstat&nbsp;-rn

抓包分析:

1. # Linux
2. sudo tcpdump&nbsp;-i eth0&nbsp;-nn&nbsp;'ip proto 2'# IGMP
3. sudo tcpdump&nbsp;-i eth0&nbsp;-nn icmp

5. # macOS/BSD
6. sudo tcpdump&nbsp;-i en0&nbsp;-nn icmp

8. # Windows
9. # 使用Wireshark

附录A: 相关系统调用和API参考

Linux ioctl命令

| 命令 | 描述 | 参数 | | — | — | — | | SIOCADDRT | 添加路由 | struct rtentry * | | SIOCDELRT | 删除路由 | struct rtentry * |

| 类型 | 值 | 描述 | | — | — | — | | 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: man7netlinkman7rtnetlink
  • FreeBSD: man4route
  • macOS: man4route

相关工具

  • Linux: ip routeroutenetstattcpdump
  • macOS/BSD: routenetstattcpdump
  • Windows: routenetshnetstatWireshark

文档版本: 1.0最后更新: 2026作者: libdnet源码分析适用版本: libdnet 1.13

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

#


免责声明:

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

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

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

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

评论:0   参与:  0