乐于分享
好东西不私藏

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

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

网: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
  2. 候选路由:
  3. 1.192.168.1.0/24(匹配24位)
  4. 2.192.168.0.0/16(匹配16位)
  5. 3.0.0.0.0/0(匹配0位)
  6. 选择: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);
  4. /* 路由句柄操作 */
  5. route_t*route_open(void);// 打开路由句柄
  6. route_t*route_close(route_t*r);// 关闭路由句柄
  7. /* 路由表操作 */
  8. int route_add(route_t*r,conststruct route_entry *entry);// 添加路由
  9. int route_delete(route_t*r,conststruct route_entry *entry);// 删除路由
  10. int route_get(route_t*r,struct route_entry *entry);// 查询路由
  11. 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);
  3. /* 返回值说明:
  4.  * 0 - 继续遍历
  5.  * 非0 - 停止遍历
  6.  */

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;
  7. if((= calloc(1,sizeof(*r)))!= NULL){
  8.         r->fd = r->nlfd =-1;
  9. /* 创建AF_INET socket用于ioctl */
  10. if((r->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
  11. return(route_close(r));
  12. /* 创建Netlink socket用于路由查询 */
  13. if((r->nlfd = socket(AF_NETLINK, SOCK_RAW,
  14.                 NETLINK_ROUTE))<0)
  15. return(route_close(r));
  16. /* 绑定Netlink socket */
  17.         memset(&snl,0,sizeof(snl));
  18.         snl.nl_family = AF_NETLINK;
  19. if(bind(r->nlfd,(struct sockaddr *)&snl,sizeof(snl))<0)
  20. return(route_close(r));
  21. }
  22. return(r);
  23. }

关键技术点:

  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 route_entry *entry)
  4. {
  5. struct rtentry rt;
  6. struct addr dst;
  7.     memset(&rt,0,sizeof(rt));
  8.     rt.rt_flags = RTF_UP | RTF_GATEWAY;/* 激活 + 网关 */
  9. /* 判断是主机路由还是网络路由 */
  10. if(ADDR_ISHOST(&entry->route_dst)){
  11.         rt.rt_flags |= RTF_HOST;/* 主机路由 */
  12.         memcpy(&dst,&entry->route_dst,sizeof(dst));
  13. }else
  14.         addr_net(&entry->route_dst,&dst);/* 网络路由 */
  15. /* 填充路由条目 */
  16. if(addr_ntos(&dst,&rt.rt_dst)<0||/* 目标地址 */
  17.         addr_ntos(&entry->route_gw,&rt.rt_gateway)<0||/* 网关 */
  18.         addr_btos(entry->route_dst.addr_bits,&rt.rt_genmask)<0)/* 子网掩码 */
  19. return(-1);
  20. /* 执行ioctl添加路由 */
  21. return(ioctl(r->fd, SIOCADDRT,&rt));
  22. }

宏定义:

  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;
  7.     memset(&rt,0,sizeof(rt));
  8.     rt.rt_flags = RTF_UP;/* 仅需要激活标志 */
  9. /* 判断是主机路由还是网络路由 */
  10. if(ADDR_ISHOST(&entry->route_dst)){
  11.         rt.rt_flags |= RTF_HOST;
  12.         memcpy(&dst,&entry->route_dst,sizeof(dst));
  13. }else
  14.         addr_net(&entry->route_dst,&dst);
  15. /* 填充删除参数(只需要目标和掩码) */
  16. if(addr_ntos(&dst,&rt.rt_dst)<0||
  17.         addr_btos(entry->route_dst.addr_bits,&rt.rt_genmask)<0)
  18. return(-1);
  19. /* 执行ioctl删除路由 */
  20. return(ioctl(r->fd, SIOCDELRT,&rt));
  21. }

关键点:

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

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((= 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(== 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. };
  9. /* 路由消息 */
  10. struct rtmsg {
  11. unsignedchar rtm_family;/* 地址族 */
  12. unsignedchar rtm_dst_len;/* 目标前缀长度 */
  13. unsignedchar rtm_src_len;/* 源前缀长度 */
  14. unsignedchar rtm_tos;/* TOS */
  15. unsignedchar rtm_table;/* 路由表ID */
  16. unsignedchar rtm_protocol;/* 路由协议 */
  17. unsignedchar rtm_scope;/* 路由范围 */
  18. unsignedchar rtm_type;/* 路由类型 */
  19. unsignedint  rtm_flags;/* 路由标志 */
  20. };
  21. /* 路由属性 */
  22. struct rtattr {
  23. unsignedshort rta_len;/* 属性长度 */
  24. unsignedshort rta_type;/* 属性类型 */
  25. };

路由属性类型:

  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"
  3. int
  4. route_loop(route_t*r, route_handler callback,void*arg)
  5. {
  6. FILE*fp;
  7. struct route_entry entry;
  8. char buf[BUFSIZ];
  9. char ifbuf[16];
  10. int ret =0;
  11. if((fp = fopen(PROC_ROUTE_FILE,"r"))!= NULL){
  12. int i, iflags, refcnt, use, metric, mss, win, irtt;
  13. uint32_t mask;
  14. /* 读取并解析每行 */
  15. while(fgets(buf,sizeof(buf), fp)!= NULL){
  16.             i = sscanf(buf,"%15s %X %X %X %d %d %d %X %d %d %d\n",
  17.                 ifbuf,/* 接口名称 */
  18. &entry.route_dst.addr_ip,/* 目标IP */
  19. &entry.route_gw.addr_ip,/* 网关IP */
  20. &iflags,/* 标志 */
  21. &refcnt,/* 引用计数 */
  22. &use,/* 使用计数 */
  23. &metric,/* 度量值 */
  24. &mask,/* 子网掩码 */
  25. &mss,/* MSS */
  26. &win,/* 窗口 */
  27. &irtt);/* IRTT */
  28. if(<11||!(iflags & RTF_UP))
  29. continue;/* 跳过未激活的路由 */
  30.             strlcpy(entry.intf_name, ifbuf,sizeof(entry.intf_name));
  31.             entry.route_dst.addr_type = entry.route_gw.addr_type = ADDR_TYPE_IP;
  32. /* 将掩码转换为前缀长度 */
  33. if(addr_mtob(&mask, IP_ADDR_LEN,&entry.route_dst.addr_bits)<0)
  34. continue;
  35.             entry.route_gw.addr_bits = IP_ADDR_BITS;
  36.             entry.metric = metric;
  37. if((ret = callback(&entry, arg))!=0)
  38. break;
  39. }
  40.         fclose(fp);
  41. }
  42. /* ... IPv6处理 ... */
  43. return(ret);
  44. }

/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"
  3. if(ret ==0&&(fp = fopen(PROC_IPV6_ROUTE_FILE,"r"))!= NULL){
  4. char s[33], d[8][5], n[8][5];
  5. int i, iflags, metric;
  6.     u_int slen, dlen;
  7. while(fgets(buf,sizeof(buf), fp)!= NULL){
  8. /* 解析IPv6路由条目 */
  9.         i = sscanf(buf,"%04s%04s%04s%04s%04s%04s%04s%04s %02x "
  10. "%32s %02x %04s%04s%04s%04s%04s%04s%04s%04s "
  11. "%x %*x %*x %x %15s",
  12.             d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7],
  13. &dlen, s,&slen,
  14.             n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7],
  15. &metric,&iflags, ifbuf);
  16. if(<21||!(iflags & RTF_UP))
  17. continue;
  18.         strlcpy(entry.intf_name, ifbuf,sizeof(entry.intf_name));
  19. /* 构造目标IPv6地址字符串 */
  20.         snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",
  21.             d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], dlen);
  22.         addr_aton(buf,&entry.route_dst);
  23. /* 构造下一跳IPv6地址 */
  24.         snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",
  25.             n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7],
  26.             IP6_ADDR_BITS);
  27.         addr_aton(buf,&entry.route_gw);
  28.         entry.metric = metric;
  29. if((ret = callback(&entry, arg))!=0)
  30. break;
  31. }
  32.     fclose(fp);
  33. }

/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(!= 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

三种遍历方式:

  1. sysctl方式(推荐,macOS/BSD)
  2. getkerninfo方式(FreeBSD)
  3. 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;
  6. if((= calloc(1,sizeof(*r)))!= NULL){
  7.         r->fd =-1;
  8. #ifdef HAVE_STREAMS_MIB2
  9. /* Solaris MIB2方式 */
  10. if((r->ip_fd = open(IP_DEV_NAME, O_RDWR))<0)
  11. return(route_close(r));
  12. #endif
  13. #ifdef HAVE_STREAMS_ROUTE
  14. /* STREAMS路由设备 */
  15. if((r->fd = open("/dev/route", O_RDWR,0))<0)
  16. #else
  17. /* 标准BSD/macOS: 创建路由套接字 */
  18. if((r->fd = socket(PF_ROUTE, SOCK_RAW, AF_INET))<0)
  19. #endif
  20. return(route_close(r));
  21. }
  22. return(r);
  23. }

关键技术点:

  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 __APPLE__
  3. #define RT_MSGHDR_ALIGNMENT sizeof(uint32_t)/* macOS: 4字节对齐 */
  4. #else
  5. #define RT_MSGHDR_ALIGNMENT sizeof(unsignedlong)/* BSD: 8字节对齐 */
  6. #endif
  7. #if defined(RT_ROUNDUP) && defined(__NetBSD__)
  8. #define ROUNDUP(a) RT_ROUNDUP(a)
  9. #else
  10. #define ROUNDUP(a) \
  11. ((a)>0?(1+(((a)-1)|(RT_MSGHDR_ALIGNMENT -1))): RT_MSGHDR_ALIGNMENT)
  12. #endif
  13. #ifdef HAVE_SOCKADDR_SA_LEN
  14. #define NEXTSA(s) \
  15. ((struct sockaddr *)((u_char *)(s)+ ROUNDUP((s)->sa_len)))
  16. #else
  17. #define NEXTSA(s) \
  18. ((struct sockaddr *)((u_char *)(s)+ ROUNDUP(sizeof(*(s)))))
  19. #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;
  12.     memset(buf,0,sizeof(buf));
  13.     rtm =(struct rt_msghdr *)buf;
  14.     rtm->rtm_version = RTM_VERSION;
  15.     rtm->rtm_type = type;
  16. if(type != RTM_DELETE)
  17.         rtm->rtm_flags = RTF_UP;
  18.     rtm->rtm_addrs = RTA_DST;/* 包含目标地址 */
  19.     rtm->rtm_seq =++r->seq;/* 序列号 */
  20. /* 目标地址 */
  21.     sa =(struct sockaddr *)(rtm +1);
  22. if(addr_net(dst,&net)<0|| addr_ntos(&net, sa)<0)
  23. return(-1);
  24.     sa = NEXTSA(sa);
  25. /* 网关地址 */
  26. if(gw != NULL && type != RTM_GET){
  27.         rtm->rtm_flags |= RTF_GATEWAY;
  28.         rtm->rtm_addrs |= RTA_GATEWAY;
  29. if(addr_ntos(gw, sa)<0)
  30. return(-1);
  31.         sa = NEXTSA(sa);
  32. }
  33. /* 子网掩码 */
  34. if(dst->addr_ip == IP_ADDR_ANY || dst->addr_bits < IP_ADDR_BITS){
  35.         rtm->rtm_addrs |= RTA_NETMASK;
  36. if(addr_btos(dst->addr_bits, sa)<0)
  37. return(-1);
  38.         sa = NEXTSA(sa);
  39. }else
  40.         rtm->rtm_flags |= RTF_HOST;/* 主机路由 */
  41.     rtm->rtm_msglen =(u_char *)sa - buf;
  42. #ifdef DEBUG
  43.     route_msg_print(rtm);
  44. #endif
  45. #ifdef HAVE_STREAMS_ROUTE
  46. if(ioctl(r->fd, RTSTR_SEND, rtm)<0)
  47. return(-1);
  48. #else
  49. /* 发送路由消息 */
  50. if(write(r->fd, buf, rtm->rtm_msglen)<0)
  51. return(-1);
  52.     pid = getpid();
  53. /* 接收响应(仅RTM_GET) */
  54. while(type == RTM_GET &&(len = read(r->fd, buf,sizeof(buf)))>0){
  55. if(len <(int)sizeof(*rtm)){
  56. return(-1);
  57. }
  58. /* 匹配响应 */
  59. if(rtm->rtm_type == type && rtm->rtm_pid == pid &&
  60.             rtm->rtm_seq == r->seq){
  61. if(rtm->rtm_errno){
  62.                 errno = rtm->rtm_errno;
  63. return(-1);
  64. }
  65. break;
  66. }
  67. }
  68. #endif
  69. /* 解析响应 */
  70. if(type == RTM_GET &&(rtm->rtm_addrs &(RTA_DST|RTA_GATEWAY))==
  71. (RTA_DST|RTA_GATEWAY)){
  72.         sa =(struct sockaddr *)(rtm +1);
  73.         sa = NEXTSA(sa);
  74. if(addr_ston(sa, gw)<0|| gw->addr_type != ADDR_TYPE_IP){
  75.             errno = ESRCH;
  76. return(-1);
  77. }
  78. if(intf_name != NULL){
  79. char namebuf[IF_NAMESIZE];
  80. if(if_indextoname(rtm->rtm_index, namebuf)== NULL){
  81.                 errno = ESRCH;
  82. return(-1);
  83. }
  84.             strlcpy(intf_name, namebuf, INTF_NAME_LEN);
  85. }
  86. }
  87. return(0);
  88. }

路由消息类型:

  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;
  6.     memcpy(&rtent, entry,sizeof(rtent));
  7. if(route_msg(r, RTM_ADD, NULL,&rtent.route_dst,&rtent.route_gw)<0)
  8. return(-1);
  9. return(0);
  10. }

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;
  6.     memcpy(&rtent, entry,sizeof(rtent));
  7. /* 先查询获取完整信息 */
  8. if(route_get(r,&rtent)<0)
  9. return(-1);
  10. /* 删除路由 */
  11. if(route_msg(r, RTM_DELETE, NULL,&rtent.route_dst,&rtent.route_gw)<0)
  12. return(-1);
  13. return(0);
  14. }

关键点:

  • 删除前必须先查询获取完整路由信息
  • 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;
  10. return(0);
  11. }

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;
  11. /* sysctl MIB数组 */
  12. int mib[6]={ CTL_NET, PF_ROUTE,0,0/* XXX */, NET_RT_DUMP,0};
  13. size_t len;
  14. /* 第一次调用: 获取所需缓冲区大小 */
  15. if(sysctl(mib,6, NULL,&len, NULL,0)<0)
  16. return(-1);
  17. if(len ==0)
  18. return(0);
  19. /* 分配缓冲区 */
  20. if((buf = malloc(len))== NULL)
  21. return(-1);
  22. /* 第二次调用: 获取实际数据 */
  23. if(sysctl(mib,6, buf,&len, NULL,0)<0){
  24.         free(buf);
  25. return(-1);
  26. }
  27.     lim = buf + len;
  28.     next = buf;
  29. /* 遍历所有路由消息 */
  30. for(ret =0; next < lim; next += rtm->rtm_msglen){
  31. char namebuf[IF_NAMESIZE];
  32.         rtm =(struct rt_msghdr *)next;
  33.         sa =(struct sockaddr *)(rtm +1);
  34. /* 获取接口名称 */
  35. if(if_indextoname(rtm->rtm_index, namebuf)== NULL)
  36. continue;
  37.         strlcpy(entry.intf_name, namebuf,sizeof(entry.intf_name));
  38. /* 解析目标地址 */
  39. if((rtm->rtm_addrs & RTA_DST)==0)
  40. continue;
  41. if(addr_ston(sa,&entry.route_dst)<0)
  42. continue;
  43. /* 解析网关地址 */
  44. if((rtm->rtm_addrs & RTA_GATEWAY)==0)
  45. continue;
  46.         sa = NEXTSA(sa);
  47. if(addr_ston_gateway(&entry.route_dst, sa,&entry.route_gw)<0)
  48. continue;
  49. /* 验证地址类型 */
  50. if(entry.route_dst.addr_type != entry.route_gw.addr_type ||
  51. (entry.route_dst.addr_type != ADDR_TYPE_IP &&
  52.              entry.route_dst.addr_type != ADDR_TYPE_IP6))
  53. continue;
  54. /* 解析子网掩码 */
  55. if(rtm->rtm_addrs & RTA_NETMASK){
  56.             sa = NEXTSA(sa);
  57. if(addr_stob(sa,&entry.route_dst.addr_bits)<0)
  58. continue;
  59. }
  60.         entry.metric =0;
  61. if((ret = callback(&entry, arg))!=0)
  62. break;
  63. }
  64.     free(buf);
  65. return(ret);
  66. }
  67. #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;
  7.     rc = addr_ston(sa, a);
  8. if(rc ==0)
  9. return rc;
  10. #ifdef HAVE_NET_IF_DL_H
  11. # ifdef AF_LINK
  12. /* 如果网关是链路层地址,表示同网段路由 */
  13. if(sa->sa_family == AF_LINK){
  14.         memset(a,0,sizeof(*a));
  15.         a->addr_type = dst->addr_type;/* 使用全0地址 */
  16. return(0);
  17. }
  18. # endif
  19. #endif
  20. return(-1);
  21. }

说明:

  • 同网段路由的网关地址可能是 AF_LINK类型
  • 这种情况下返回全0地址作为网关
  • 表示直接投递,无需网关

方式2: STREAMS MIB2方式(Solaris)

  1. /* 文件: src/route-bsd.c 第392-600行 */
  2. #elif defined(HAVE_STREAMS_MIB2)
  3. int
  4. route_loop(route_t*r, route_handler callback,void*arg)
  5. {
  6. struct route_entry entry;
  7. struct strbuf msg;
  8. struct T_optmgmt_req *tor;
  9. struct T_optmgmt_ack *toa;
  10. struct T_error_ack *tea;
  11. struct opthdr *opt;
  12.     u_char buf[8192];
  13. int flags, rc, rtable, ret;
  14. /* ... 发送MIB2查询请求 ... */
  15. /* ... 接收并解析路由表 ... */
  16. /* IPv4路由 */
  17. for(; rt < rtend; rt++){
  18. /* 过滤特定类型 */
  19. if((rt->ipRouteInfo.re_ire_type &
  20. (IRE_BROADCAST|IRE_ROUTE_REDIRECT|
  21.              IRE_LOCAL|IRE_ROUTE))!=0||
  22.             rt->ipRouteNextHop == IP_ADDR_ANY)
  23. continue;
  24.         entry.intf_name[0]='\0';
  25.         sin.sin_addr.s_addr = rt->ipRouteNextHop;
  26.         addr_ston((struct sockaddr *)&sin,&entry.route_gw);
  27.         sin.sin_addr.s_addr = rt->ipRouteDest;
  28.         addr_ston((struct sockaddr *)&sin,&entry.route_dst);
  29.         sin.sin_addr.s_addr = rt->ipRouteMask;
  30.         addr_stob((struct sockaddr *)&sin,&entry.route_dst.addr_bits);
  31.         entry.metric =0;
  32. if((ret = callback(&entry, arg))!=0)
  33. return(ret);
  34. }
  35. /* IPv6路由(类似处理) */
  36. return(0);
  37. }

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);
  4. if(len ==0)
  5. return(0);
  6. if((buf = malloc(len))== NULL)
  7. return(-1);
  8. if(getkerninfo(KINFO_RT_DUMP,buf,&len,0)<0){
  9.     free(buf);
  10. return(-1);
  11. }
  12. /* ... 解析路由表 ... */

方式4: 内核内存读取(Tru64)

  1. /* 文件: src/route-bsd.c 第601-678行 */
  2. #elif defined(HAVE_NET_RADIX_H)
  3. /* XXX - Tru64, others? */
  4. staticint
  5. _kread(int fd,void*addr,void*buf,int len)
  6. {
  7. if(lseek(fd,(off_t)addr, SEEK_SET)==(off_t)-1L)
  8. return(-1);
  9. return(read(fd, buf, len)== len ?0:-1);
  10. }
  11. staticint
  12. _radix_walk(int fd,struct radix_node *rn, route_handler callback,void*arg)
  13. {
  14. /* 遍历radix树 */
  15. /* ... */
  16. }
  17. int
  18. route_loop(route_t*r, route_handler callback,void*arg)
  19. {
  20. struct radix_node_head *rnh, head;
  21. struct nlist nl[2];
  22. int fd, ret =0;
  23.     memset(nl,0,sizeof(nl));
  24.     nl[0].n_name ="radix_node_head";
  25. /* 读取内核符号 */
  26. if(knlist(nl)<0|| nl[0].n_type ==0||
  27. (fd = open("/dev/kmem", O_RDONLY,0))<0)
  28. return(-1);
  29. /* 遍历radix树 */
  30. for(_kread(fd,(void*)nl[0].n_value,&rnh,sizeof(rnh));
  31.         rnh != NULL; rnh = head.rnh_next){
  32.         _kread(fd, rnh,&head,sizeof(head));
  33. /* XXX - only IPv4 for now... */
  34. if(head.rnh_af == AF_INET){
  35. if((ret = _radix_walk(fd, head.rnh_treetop,
  36.                  callback, arg))!=0)
  37. break;
  38. }
  39. }
  40.     close(fd);
  41. return(ret);
  42. }
  43. #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 route_handle {
  3. int  fd;
  4. };
  5. route_t*
  6. route_open(void)
  7. {
  8. route_t*r;
  9. if((= calloc(1,sizeof(*r)))!= NULL){
  10. if((r->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
  11. return(route_close(r));
  12. }
  13. return(r);
  14. }

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;
  7.     memset(&rt,0,sizeof(rt));
  8.     rt.rt_flags = RTF_UP | RTF_GATEWAY;
  9. if(ADDR_ISHOST(&entry->route_dst)){
  10.         rt.rt_flags |= RTF_HOST;
  11.         memcpy(&dst,&entry->route_dst,sizeof(dst));
  12. }else
  13.         addr_net(&entry->route_dst,&dst);
  14. /* HP-UX使用rt_subnetmask而不是rt_genmask */
  15. if(addr_ntos(&dst,&rt.rt_dst)<0||
  16.         addr_ntos(&entry->route_gw,&rt.rt_gateway)<0||
  17.         addr_btom(entry->route_dst.addr_bits,&rt.rt_subnetmask,
  18.             IP_ADDR_LEN)<0)
  19. return(-1);
  20. return(ioctl(r->fd, SIOCADDRT,&rt));
  21. }

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;
  7.     memset(&rt,0,sizeof(rt));
  8.     rt.rt_flags = RTF_UP;
  9. if(ADDR_ISHOST(&entry->route_dst)){
  10.         rt.rt_flags |= RTF_HOST;
  11.         memcpy(&dst,&entry->route_dst,sizeof(dst));
  12. }else
  13.         addr_net(&entry->route_dst,&dst);
  14. if(addr_ntos(&dst,&rt.rt_dst)<0||
  15.         addr_btom(entry->route_dst.addr_bits,&rt.rt_subnetmask,
  16.             IP_ADDR_LEN)<0)
  17. return(-1);
  18. return(ioctl(r->fd, SIOCDELRT,&rt));
  19. }

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;
  6.     memset(&rtr,0,sizeof(rtr));
  7. /* XXX - gross hack for default route */
  8. if(entry->route_dst.addr_ip == IP_ADDR_ANY){
  9.         rtr.rtr_destaddr = htonl(0x60060606);/* 魔术地址 */
  10.         rtr.rtr_subnetmask =0xffffffff;
  11. }else{
  12.         memcpy(&rtr.rtr_destaddr,&entry->route_dst.addr_ip,
  13.             IP_ADDR_LEN);
  14. if(entry->route_dst.addr_bits < IP_ADDR_BITS)
  15.             addr_btom(entry->route_dst.addr_bits,
  16. &rtr.rtr_subnetmask, IP_ADDR_LEN);
  17. }
  18. if(ioctl(r->fd, SIOCGRTENTRY,&rtr)<0)
  19. return(-1);
  20. if(rtr.rtr_gwayaddr ==0){
  21.         errno = ESRCH;
  22. return(-1);
  23. }
  24.     entry->intf_name[0]='\0';
  25.     entry->route_gw.addr_type = ADDR_TYPE_IP;
  26.     entry->route_gw.addr_bits = IP_ADDR_BITS;
  27.     memcpy(&entry->route_gw.addr_ip, rtr.rtr_gwayaddr, IP_ADDR_LEN);
  28.     entry->metric =0;
  29. return(0);
  30. }

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 */
  3. int
  4. route_loop(route_t*r, route_handler callback,void*arg)
  5. {
  6. struct nmparms nm;
  7. struct route_entry entry;
  8.     mib_ipRouteEnt rtentries[MAX_RTENTRIES];
  9. int fd, i, n, ret;
  10. /* 打开MIB设备 */
  11. if((fd = open_mib("/dev/ip", O_RDWR,0/* XXX */,0))<0)
  12. return(-1);
  13.     nm.objid = ID_ipRouteTable;
  14.     nm.buffer = rtentries;
  15.     n =sizeof(rtentries);
  16.     nm.len =&n;
  17. if(get_mib_info(fd,&nm)<0){
  18.         close_mib(fd);
  19. return(-1);
  20. }
  21.     close_mib(fd);
  22.     n /=sizeof(*rtentries);
  23.     ret =0;
  24. for(=0; i < n; i++){
  25. /* 过滤路由类型 */
  26. if(rtentries[i].Type!= NMDIRECT &&
  27.             rtentries[i].Type!= NMREMOTE)
  28. continue;
  29.         entry.intf_name[0]='\0';
  30.         entry.route_dst.addr_type = entry.route_gw.addr_type = ADDR_TYPE_IP;
  31.         entry.route_dst.addr_bits = entry.route_gw.addr_bits = IP_ADDR_BITS;
  32.         entry.route_dst.addr_ip = rtentries[i].Dest;
  33.         addr_mtob(&rtentries[i].Mask, IP_ADDR_LEN,
  34. &entry.route_dst.addr_bits);
  35.         entry.route_gw.addr_ip = rtentries[i].NextHop;
  36.         entry.metric =0;
  37. if((ret = callback(&entry, arg))!=0)
  38. break;
  39. }
  40. return(ret);
  41. }

MIB路由类型:

  1. #define NMDIRECT   1/* 直连路由 */
  2. #define NMREMOTE   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 route_handle {
  3.     HINSTANCE iphlpapi;/* IP Helper DLL句柄 */
  4.     MIB_IPFORWARDTABLE *ipftable;/* IPv4路由表缓存 */
  5.     MIB_IPFORWARD_TABLE2 *ipftable2;/* IPv4/IPv6路由表缓存(Vista+) */
  6. };
  7. route_t*
  8. route_open(void)
  9. {
  10. route_t*r;
  11.     r = calloc(1,sizeof(route_t));
  12. if(== NULL)
  13. return NULL;
  14.     r->iphlpapi =GetModuleHandle("iphlpapi.dll");/* 获取DLL句柄 */
  15. return r;
  16. }

关键技术点:

  • 不打开任何系统资源
  • 缓存路由表以提高性能
  • 动态加载新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;
  7.     memset(&ipfrow,0,sizeof(ipfrow));
  8. /* 获取到网关的最佳接口索引 */
  9. if(GetBestInterface(entry->route_gw.addr_ip,
  10. &ipfrow.dwForwardIfIndex)!= NO_ERROR)
  11. return(-1);
  12. /* 计算网络地址 */
  13. if(addr_net(&entry->route_dst,&net)<0||
  14.         net.addr_type != ADDR_TYPE_IP)
  15. return(-1);
  16. /* 填充路由条目 */
  17.     ipfrow.dwForwardDest = net.addr_ip;
  18.     addr_btom(entry->route_dst.addr_bits,
  19. &ipfrow.dwForwardMask, IP_ADDR_LEN);
  20.     ipfrow.dwForwardNextHop = entry->route_gw.addr_ip;
  21.     ipfrow.dwForwardType =4;/* XXX - next hop != final dest */
  22.     ipfrow.dwForwardProto =3;/* XXX - MIB_PROTO_NETMGMT */
  23. /* 创建路由条目 */
  24. if(CreateIpForwardEntry(&ipfrow)!= NO_ERROR)
  25. return(-1);
  26. return(0);
  27. }

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 _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;
  7. if(entry->route_dst.addr_type != ADDR_TYPE_IP ||
  8. GetBestRoute(entry->route_dst.addr_ip,
  9.         IP_ADDR_ANY,&ipfrow)!= NO_ERROR)
  10. return(-1);
  11. /* 计算子网掩码 */
  12.     addr_btom(entry->route_dst.addr_bits,&mask, IP_ADDR_LEN);
  13. /* 验证路由匹配 */
  14. if(ipfrow.dwForwardDest != entry->route_dst.addr_ip ||
  15.         ipfrow.dwForwardMask != mask){
  16.         errno = ENXIO;
  17. SetLastError(ERROR_NO_DATA);
  18. return(-1);
  19. }
  20. /* 删除路由 */
  21. if(DeleteIpForwardEntry(&ipfrow)!= NO_ERROR)
  22. return(-1);
  23. return(0);
  24. }

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;
  9. if(entry->route_dst.addr_type != ADDR_TYPE_IP ||
  10. GetBestRoute(entry->route_dst.addr_ip,
  11.         IP_ADDR_ANY,&ipfrow)!= NO_ERROR)
  12. return(-1);
  13. /* 过滤本地路由 */
  14. if(ipfrow.dwForwardProto ==2&&/* XXX - MIB_IPPROTO_LOCAL */
  15. (ipfrow.dwForwardNextHop|IP_CLASSA_NET)!=
  16. (IP_ADDR_LOOPBACK|IP_CLASSA_NET)&&
  17. !IP_LOCAL_GROUP(ipfrow.dwForwardNextHop)){
  18.         errno = ENXIO;
  19. SetLastError(ERROR_NO_DATA);
  20. return(-1);
  21. }
  22.     addr_btom(entry->route_dst.addr_bits,&mask, IP_ADDR_LEN);
  23.     entry->route_gw.addr_type = ADDR_TYPE_IP;
  24.     entry->route_gw.addr_bits = IP_ADDR_BITS;
  25.     entry->route_gw.addr_ip = ipfrow.dwForwardNextHop;
  26.     entry->metric = ipfrow.dwForwardMetric1;
  27. /* 查询接口名称 */
  28.     entry->intf_name[0]='\0';
  29.     intf = intf_open();
  30. if(intf_get_index(intf,&intf_entry,
  31.         AF_INET, ipfrow.dwForwardIfIndex)==0){
  32.         strlcpy(entry->intf_name, intf_entry.intf_name,sizeof(entry->intf_name));
  33. }
  34.     intf_close(intf);
  35. return(0);
  36. }

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;
  9. /* 循环获取路由表,直到缓冲区足够大 */
  10. for(len =sizeof(r->ipftable[0]);;){
  11. if(r->ipftable)
  12.             free(r->ipftable);
  13.         r->ipftable = malloc(len);
  14. if(r->ipftable == NULL)
  15. return(-1);
  16.         ret =GetIpForwardTable(r->ipftable,&len, FALSE);
  17. if(ret == NO_ERROR)
  18. break;
  19. elseif(ret != ERROR_INSUFFICIENT_BUFFER)
  20. return(-1);
  21. }
  22.     intf = intf_open();
  23.     ret =0;
  24. for(=0; i <(int)r->ipftable->dwNumEntries; i++){
  25. struct intf_entry intf_entry;
  26.         entry.route_dst.addr_type = ADDR_TYPE_IP;
  27.         entry.route_dst.addr_bits = IP_ADDR_BITS;
  28.         entry.route_gw.addr_type = ADDR_TYPE_IP;
  29.         entry.route_gw.addr_bits = IP_ADDR_BITS;
  30. /* 解析路由条目 */
  31.         entry.route_dst.addr_ip = r->ipftable->table[i].dwForwardDest;
  32.         addr_mtob(&r->ipftable->table[i].dwForwardMask, IP_ADDR_LEN,
  33. &entry.route_dst.addr_bits);
  34.         entry.route_gw.addr_ip =
  35.             r->ipftable->table[i].dwForwardNextHop;
  36.         entry.metric = r->ipftable->table[i].dwForwardMetric1;
  37. /* 查询接口名称 */
  38.         entry.intf_name[0]='\0';
  39.         intf_entry.intf_len =sizeof(intf_entry);
  40. if(intf_get_index(intf,&intf_entry,
  41.             AF_INET, r->ipftable->table[i].dwForwardIfIndex)==0){
  42.             strlcpy(entry.intf_name, intf_entry.intf_name,sizeof(entry.intf_name));
  43. }
  44. if((ret =(*callback)(&entry, arg))!=0)
  45. break;
  46. }
  47.     intf_close(intf);
  48. return ret;
  49. }

GetIpForwardTable API:

  1. DWORD GetIpForwardTable(
  2.     PMIB_IPFORWARDTABLE pIpForwardTable,/* 接收路由表 */
  3.     PULONG pdwSize,/* 缓冲区大小(输入/输出) */
  4.     BOOL bOrder                          /* 是否排序 */
  5. );
  6. /* MIB_IPFORWARDTABLE结构 */
  7. typedefstruct _MIB_IPFORWARDTABLE {
  8.     DWORD            dwNumEntries;/* 条目数量 */
  9.     MIB_IPFORWARDROW table[1];/* 条目数组(变长) */
  10. } 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;
  10. /* 获取路由表(Vista+) */
  11.     ret =GetIpForwardTable2(AF_UNSPEC,&r->ipftable2);
  12. if(ret != NO_ERROR)
  13. return(-1);
  14.     intf = intf_open();
  15.     ret =0;
  16. for(=0; i < r->ipftable2->NumEntries; i++){
  17. struct intf_entry intf_entry;
  18.         MIB_IPFORWARD_ROW2 *row;
  19.         MIB_IPINTERFACE_ROW ifrow;
  20.         ULONG metric;
  21.         row =&r->ipftable2->Table[i];
  22.         addr_ston((struct sockaddr *)&row->DestinationPrefix.Prefix,&entry.route_dst);
  23.         entry.route_dst.addr_bits = row->DestinationPrefix.PrefixLength;
  24.         addr_ston((struct sockaddr *)&row->NextHop,&entry.route_gw);
  25. /* 查询接口名称 */
  26.         entry.intf_name[0]='\0';
  27.         intf_entry.intf_len =sizeof(intf_entry);
  28. if(intf_get_index(intf,&intf_entry,
  29.             row->DestinationPrefix.Prefix.si_family,
  30.             row->InterfaceIndex)==0){
  31.             strlcpy(entry.intf_name, intf_entry.intf_name,sizeof(entry.intf_name));
  32. }
  33. /* 计算总度量值 */
  34.         ifrow.Family= row->DestinationPrefix.Prefix.si_family;
  35.         ifrow.InterfaceLuid= row->InterfaceLuid;
  36.         ifrow.InterfaceIndex= row->InterfaceIndex;
  37. if(GetIpInterfaceEntry(&ifrow)!= NO_ERROR){
  38. return(-1);
  39. }
  40.         metric = ifrow.Metric+ row->Metric;
  41. if(metric < INT_MAX)
  42.             entry.metric = metric;
  43. else
  44.             entry.metric = INT_MAX;
  45. if((ret =(*callback)(&entry, arg))!=0)
  46. break;
  47. }
  48.     intf_close(intf);
  49. return ret;
  50. }

GetIpForwardTable2 API:

  1. /* 动态加载 */
  2. typedef DWORD (WINAPI *GETIPFORWARDTABLE2)(
  3.     ADDRESS_FAMILY Family,/* 地址族(AF_INET, AF_INET6, AF_UNSPEC) */
  4.     PMIB_IPFORWARD_TABLE2 *Table/* 接收路由表 */
  5. );
  6. /* MIB_IPFORWARD_TABLE2结构 */
  7. typedefstruct _MIB_IPFORWARD_TABLE2 {
  8.     ULONG                   NumEntries;/* 条目数量 */
  9.     MIB_IPFORWARD_ROW2      Table[1];/* 条目数组(变长) */
  10. } MIB_IPFORWARD_TABLE2,*PMIB_IPFORWARD_TABLE2;
  11. /* MIB_IPFORWARD_ROW2结构 */
  12. typedefstruct _MIB_IPFORWARD_ROW2 {
  13.     NET_LUID                InterfaceLuid;/* 接口LUID */
  14.     NET_IFINDEX             InterfaceIndex;/* 接口索引 */
  15.     SOCKADDR_INET           DestinationPrefix;/* 目标前缀 */
  16.     SOCKADDR_INET           NextHop;/* 下一跳 */
  17.     UCHAR                   SitePrefixLength;/* 站点前缀长度 */
  18.     SOCKADDR_INET           SitePrefix;/* 站点前缀 */
  19.     ULONG                   ValidLifetime;/* 有效生存时间 */
  20.     ULONG                   PreferredLifetime;/* 首选生存时间 */
  21.     ULONG                   Metric;/* 路由度量 */
  22.     ULONG                   Protocol;/* 路由协议 */
  23.     BOOLEAN                 Loopback;/* 是否回环 */
  24.     BOOLEAN                 AutoconfigureAddress;/* 是否自动配置 */
  25.     BOOLEAN                 Publish;/* 是否发布 */
  26.     BOOLEAN                 Immortal;/* 是否永久 */
  27.     ULONG                   Age;/* 路由年龄 */
  28.     ULONG                   Origin;/* 路由来源 */
  29. } 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;
  6. /* GetIpForwardTable2仅Vista及以上可用,动态加载 */
  7. GetIpForwardTable2= NULL;
  8. if(r->iphlpapi != NULL)
  9. GetIpForwardTable2=(GETIPFORWARDTABLE2)GetProcAddress(
  10.             r->iphlpapi,"GetIpForwardTable2");
  11. if(GetIpForwardTable2== NULL)
  12. return route_loop_getipforwardtable(r, callback, arg);
  13. else
  14. return route_loop_getipforwardtable2(GetIpForwardTable2, r, callback, arg);
  15. }

新旧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(!= 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文件易于调试
  6. /* 缺点 */
  7. -socket增加复杂度
  8. -Netlink消息格式复杂
  9. -默认路由需要特殊处理

macOS/BSD: 路由消息

  1. /* 优点 */
  2. -统一的消息机制
  3. -支持IPv4/IPv6
  4. - sysctl遍历性能高
  5. -消息格式灵活
  6. /* 缺点 */
  7. - sockaddr对齐复杂
  8. -需要序列号匹配
  9. -消息格式复杂
  10. -删除路由需要先查询

HP-UX: ioctl + MIB

  1. /* 优点 */
  2. - ioctl操作简单
  3. - MIB方式统一
  4. /* 缺点 */
  5. -特有结构(rtreq)
  6. -不支持IPv6
  7. -默认路由需要特殊处理

Windows: IP Helper API

  1. /* 优点 */
  2. -高层API,易用
  3. -不需要句柄
  4. -自动处理接口索引
  5. -API支持IPv6
  6. /* 缺点 */
  7. -依赖Windows DLL
  8. -遍历效率低(全表扫描)
  9. -需要管理员权限
  10. -接口查询复杂

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. -默认路由特殊处理
  6. /* 优点 */
  7. -代码结构清晰
  8. -IPv4/IPv6分离处理
  9. -文档完善

macOS/BSD (route-bsd.c):

  1. /* 维护难点 */
  2. -4种遍历方式
  3. - sockaddr对齐规则变化
  4. -不同BSD版本差异
  5. -路由消息格式变化
  6. -网关地址特殊处理
  7. /* 优点 */
  8. - sysctl方式稳定
  9. -逻辑清晰

HP-UX (route-hpux.c):

  1. /* 维护难点 */
  2. -平台特有结构
  3. - MIB接口变化
  4. -默认路由特殊处理
  5. /* 优点 */
  6. -代码简单
  7. -变化较少

Windows (route-win32.c):

  1. /* 维护难点 */
  2. - API版本更新频繁
  3. -新版API不向后兼容
  4. -Windows版本差异
  5. /* 优点 */
  6. -代码简单
  7. -微软文档完善
  8. -动态加载确保兼容性

7.9 平台特性对比

特性
Linux
macOS/BSD
HP-UX
Windows
IPv4支持
IPv6支持
✅(新API)
静态路由
动态路由
默认路由
✅(特殊处理)
✅(特殊处理)
度量值
接口指定
✅(自动)
✅(自动)
✅(自动)
批量操作
原子操作

8. 实际应用示例

8.1 基本用法

添加路由:

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. int main(void)
  4. {
  5. route_t*route;
  6. struct route_entry entry;
  7. /* 打开路由句柄 */
  8. if((route = route_open())== NULL){
  9.         perror("route_open");
  10. return1;
  11. }
  12. /* 设置路由条目 */
  13.     addr_pton("192.168.2.0/24",&entry.route_dst);/* 目标网络 */
  14.     addr_pton("192.168.1.1",&entry.route_gw);/* 网关 */
  15. /* 添加路由 */
  16. if(route_add(route,&entry)<0){
  17.         perror("route_add");
  18.         route_close(route);
  19. return1;
  20. }
  21.     printf("Route added successfully\n");
  22. /* 关闭句柄 */
  23.     route_close(route);
  24. return0;
  25. }

删除路由:

  1. int main(void)
  2. {
  3. route_t*route;
  4. struct route_entry entry;
  5. if((route = route_open())== NULL){
  6.         perror("route_open");
  7. return1;
  8. }
  9. /* 只需要目标地址 */
  10.     addr_pton("192.168.2.0/24",&entry.route_dst);
  11. /* 删除路由 */
  12. if(route_delete(route,&entry)<0){
  13.         perror("route_delete");
  14.         route_close(route);
  15. return1;
  16. }
  17.     printf("Route deleted\n");
  18.     route_close(route);
  19. return0;
  20. }

8.2 路由表查看器

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<dnet.h>
  4. /* 回调函数: 打印每个路由条目 */
  5. staticint print_route(conststruct route_entry *entry,void*arg)
  6. {
  7. constchar*dst_type;
  8. char dst_str[64], gw_str[64];
  9. /* 判断是主机路由还是网络路由 */
  10. if(entry->route_dst.addr_bits == IP_ADDR_BITS ||
  11.         entry->route_dst.addr_bits == IP6_ADDR_BITS){
  12.         dst_type ="host";
  13. }else{
  14.         dst_type ="net ";
  15. }
  16. /* 格式化地址 */
  17.     snprintf(dst_str,sizeof(dst_str),"%s/%d",
  18.              addr_ntoa(&entry->route_dst),
  19.              entry->route_dst.addr_bits);
  20.     snprintf(gw_str,sizeof(gw_str),"%s",
  21.              addr_ntoa(&entry->route_gw));
  22.     printf("%-20s %-6s %-20s %-10s metric=%d\n",
  23.            dst_str, dst_type, gw_str, entry->intf_name, entry.metric);
  24. return0;
  25. }
  26. int main(void)
  27. {
  28. route_t*route;
  29. /* 打开路由句柄 */
  30. if((route = route_open())== NULL){
  31.         perror("route_open");
  32. return1;
  33. }
  34.     printf("%-20s %-6s %-20s %-10s %s\n",
  35. "Destination","Type","Gateway","Interface","Metric");
  36.     printf("---------------------------------------------------------\n");
  37. /* 遍历并打印所有路由条目 */
  38. if(route_loop(route, print_route, NULL)<0){
  39.         perror("route_loop");
  40.         route_close(route);
  41. return1;
  42. }
  43. /* 关闭句柄 */
  44.     route_close(route);
  45. return0;
  46. }

8.3 路由查询工具

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<dnet.h>
  4. int main(int argc,char*argv[])
  5. {
  6. route_t*route;
  7. struct route_entry entry;
  8. constchar*dst_str;
  9. if(argc !=2){
  10.         fprintf(stderr,"Usage: %s <destination>\n", argv[0]);
  11. return1;
  12. }
  13. /* 解析目标地址 */
  14. if(addr_pton(argv[1],&entry.route_dst)<0){
  15.         fprintf(stderr,"Invalid destination address\n");
  16. return1;
  17. }
  18. /* 打开路由句柄 */
  19. if((route = route_open())== NULL){
  20.         perror("route_open");
  21. return1;
  22. }
  23. /* 查询路由 */
  24. if(route_get(route,&entry)<0){
  25.         perror("route_get");
  26.         route_close(route);
  27. return1;
  28. }
  29. /* 打印结果 */
  30.     dst_str = addr_ntoa(&entry.route_dst);
  31.     printf("Destination: %s\n", dst_str);
  32.     printf("Gateway:     %s\n", addr_ntoa(&entry.route_gw));
  33.     printf("Interface:   %s\n", entry.intf_name);
  34.     printf("Metric:      %d\n", entry.metric);
  35. /* 关闭句柄 */
  36.     route_close(route);
  37. return0;
  38. }

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
  4. # 添加主机路由
  5. $ sudo ./test/dnet/dnet route add 192.168.2.100192.168.1.2
  6. 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>
  5. /* 路由条目缓存 */
  6. #define MAX_ROUTES 1024
  7. staticstruct route_entry cache[MAX_ROUTES];
  8. staticint cache_count =0;
  9. /* 比较路由条目 */
  10. staticint route_cmp(conststruct route_entry *a,
  11. conststruct route_entry *b)
  12. {
  13. int cmp;
  14.     cmp = addr_cmp(&a->route_dst,&b->route_dst);
  15. if(cmp !=0)
  16. return cmp;
  17.     cmp = addr_cmp(&a->route_gw,&b->route_gw);
  18. if(cmp !=0)
  19. return cmp;
  20. return strcmp(a->intf_name, b->intf_name);
  21. }
  22. /* 添加条目到缓存 */
  23. staticvoid add_route(conststruct route_entry *entry)
  24. {
  25. if(cache_count < MAX_ROUTES){
  26.         cache[cache_count]=*entry;
  27.         cache_count++;
  28. }
  29. }
  30. /* 查找条目 */
  31. staticint find_route(conststruct route_entry *entry)
  32. {
  33. for(int i =0; i < cache_count; i++){
  34. if(route_cmp(&cache[i], entry)==0)
  35. return i;
  36. }
  37. return-1;
  38. }
  39. /* 回调函数: 检测变化 */
  40. staticint detect_changes(conststruct route_entry *entry,void*arg)
  41. {
  42. int idx = find_route(entry);
  43. time_t now = time(NULL);
  44. if(idx <0){
  45. /* 新路由 */
  46.         printf("[%s] NEW: %s/%d -> %s via %s\n",
  47.                ctime(&now),
  48.                addr_ntoa(&entry->route_dst),
  49.                entry->route_dst.addr_bits,
  50.                addr_ntoa(&entry->route_gw),
  51.                entry->intf_name);
  52.         add_route(entry);
  53. }
  54. /* 可以添加其他变化检测... */
  55. return0;
  56. }
  57. int main(void)
  58. {
  59. route_t*route;
  60. /* 打开路由句柄 */
  61. if((route = route_open())== NULL){
  62.         perror("route_open");
  63. return1;
  64. }
  65.     printf("Route Monitor (Ctrl+C to exit)\n");
  66. /* 定期检查 */
  67. while(1){
  68.         route_loop(route, detect_changes, NULL);
  69.         sleep(5);/* 5秒间隔 */
  70. }
  71.     route_close(route);
  72. return0;
  73. }

8.6 路由查找器

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<dnet.h>
  4. /* 路由条目 */
  5. struct route_ctx {
  6. struct route_entry entry;
  7. int found;
  8. };
  9. /* 回调函数: 查找最长前缀匹配 */
  10. staticint find_best_match(conststruct route_entry *entry,void*arg)
  11. {
  12. struct route_ctx *ctx =(struct route_ctx *)arg;
  13. int dst_bits = ctx->entry.route_dst.addr_bits;
  14. /* 检查地址类型 */
  15. if(entry->route_dst.addr_type != ctx->entry.route_dst.addr_type)
  16. return0;
  17. /* 检查是否匹配 */
  18. if(addr_blong(&entry->route_dst,&ctx->entry.route_dst)){
  19. /* 更长前缀匹配优先 */
  20. if(!ctx->found ||
  21.             entry->route_dst.addr_bits > ctx->entry.route_dst.addr_bits){
  22.             ctx->entry =*entry;
  23.             ctx->found =1;
  24. }
  25. }
  26. return0;
  27. }
  28. int main(int argc,char*argv[])
  29. {
  30. route_t*route;
  31. struct route_ctx ctx;
  32. if(argc !=2){
  33.         fprintf(stderr,"Usage: %s <destination>\n", argv[0]);
  34. return1;
  35. }
  36. /* 解析目标地址 */
  37. if(addr_pton(argv[1],&ctx.entry.route_dst)<0){
  38.         fprintf(stderr,"Invalid destination address\n");
  39. return1;
  40. }
  41.     ctx.found =0;
  42. /* 打开路由句柄 */
  43. if((route = route_open())== NULL){
  44.         perror("route_open");
  45. return1;
  46. }
  47. /* 遍历路由表查找最长前缀匹配 */
  48. if(route_loop(route, find_best_match,&ctx)<0){
  49.         perror("route_loop");
  50.         route_close(route);
  51. return1;
  52. }
  53. /* 输出结果 */
  54. if(ctx.found){
  55.         printf("Destination: %s\n", argv[1]);
  56.         printf("Gateway:     %s\n", addr_ntoa(&ctx.entry.route_gw));
  57.         printf("Interface:   %s\n", ctx.entry.intf_name);
  58.         printf("Metric:      %d\n", ctx.entry.metric);
  59. }else{
  60.         printf("No route found for %s\n", argv[1]);
  61. }
  62. /* 关闭句柄 */
  63.     route_close(route);
  64. return0;
  65. }

9. 常见问题与解决方案

9.1 权限问题

问题:添加/删除路由失败,errno=EPERM

原因:

  • Linux: 需要root权限或CAPNETADMIN能力
  • macOS/BSD/HP-UX: 需要root权限
  • Windows: 需要管理员权限

解决方案:

  1. # Linux: 使用sudo
  2. sudo ./your_program
  3. # 设置CAP_NET_ADMIN能力
  4. sudo setcap cap_net_admin+ep ./your_program
  5. # Windows: 以管理员身份运行
  6. # 右键 -> 以管理员身份运行

代码检查:

  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");
  3. /* 等待路由建立 */
  4. sleep(1);
  5. /* 再次尝试获取 */
  6. if(route_get(route,&entry)<0&& errno == ESRCH){
  7.     fprintf(stderr,"Route not found\n");
  8. return-1;
  9. }

9.3 默认路由问题

问题:查询默认路由失败

原因:

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

解决方案:

  1. /* Linux/HP-UX默认路由魔术地址 */
  2. #define DEFAULT_ROUTE_MAGIC  0x60060606
  3. /* 使用0.0.0.0/0表示默认路由 */
  4. addr_pton("0.0.0.0/0",&entry.route_dst);
  5. /* 查询 */
  6. if(route_get(route,&entry)<0){
  7.     perror("route_get");
  8. return-1;
  9. }

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. }
  6. /* Windows: 使用新API */
  7. #ifdef _WIN32
  8. /* 动态加载GetIpForwardTable2 */
  9.     GETIPFORWARDTABLE2 pGetIpForwardTable2;
  10.     pGetIpForwardTable2 =(GETIPFORWARDTABLE2)
  11. GetProcAddress(iphlpapi,"GetIpForwardTable2");
  12. if(pGetIpForwardTable2 == NULL){
  13.         fprintf(stderr,"IPv6 requires Windows Vista or later\n");
  14. }
  15. #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
  7. #define ROUNDUP(a) \
  8. ((a)>0?(1+(((a)-1)|(RT_MSGHDR_ALIGNMENT -1))): \
  9.                 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);
  11.         fprintf(stderr,"%s failed: %s (Error %lu)\n",
  12.                 operation, msg, error);
  13. LocalFree(msg);
  14. }
  15. }
  16. /* 使用 */
  17. if(CreateIpForwardEntry(&ipfrow)!= NO_ERROR){
  18.     print_last_error("CreateIpForwardEntry");
  19. return-1;
  20. }

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. };
  8. staticstruct route_cache cache ={NULL,0,0,60};/* 60秒缓存 */
  9. /* 带缓存的遍历 */
  10. int route_loop_cached(route_t*r, route_handler callback,void*arg)
  11. {
  12. time_t now = time(NULL);
  13. /* 检查缓存是否有效 */
  14. if(cache.entries != NULL &&(now - cache.timestamp)< cache.ttl){
  15. /* 使用缓存 */
  16. for(int i =0; i < cache.count; i++){
  17. if(callback(&cache.entries[i], arg)!=0)
  18. break;
  19. }
  20. return0;
  21. }
  22. /* 重建缓存... */
  23. /* 类似ARP缓存的实现 */
  24. }

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

9.10 调试技巧

启用详细日志:

  1. #define ROUTE_DEBUG
  2. #ifdef ROUTE_DEBUG
  3. #define route_log(fmt,...) \
  4.     fprintf(stderr,"[ROUTE] " fmt "\n",##__VA_ARGS__)
  5. #else
  6. #define route_log(fmt,...)do{}while(0)
  7. #endif
  8. /* 使用 */
  9. route_log("Adding route: dst=%s/%d, gw=%s",
  10.           addr_ntoa(&entry.route_dst),
  11.           entry.route_dst.addr_bits,
  12.           addr_ntoa(&entry.route_gw));

查看系统路由表:

  1. # Linux
  2. ip route show
  3. netstat -rn
  4. # macOS/BSD
  5. netstat -rn
  6. route -n get <destination>
  7. # Windows
  8. route print
  9. netstat -rn
  10. # HP-UX
  11. netstat -rn

抓包分析:

  1. # Linux
  2. sudo tcpdump -i eth0 -nn 'ip proto 2'# IGMP
  3. sudo tcpdump -i eth0 -nn icmp
  4. # macOS/BSD
  5. sudo tcpdump -i en0 -nn icmp
  6. # Windows
  7. # 使用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: 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

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 跨平台底层网络库libdnet源码分析系列(五)

猜你喜欢

  • 暂无文章