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

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


夜雨聆风