乐于分享
好东西不私藏

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

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

网:http://securitytech.cc

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

ARP操作源码深入分析

目录

  1. ARP协议概述
  2. libdnet ARP接口设计
  3. Linux平台实现
  4. macOS/BSD平台实现
  5. Windows平台实现
  6. 跨平台对比分析
  7. 实际应用示例
  8. 常见问题与解决方案

1. ARP协议概述

1.1 ARP协议简介

ARP(Address Resolution Protocol)是网络层和数据链路层之间的核心协议,用于将IP地址解析为MAC地址。RFC 826定义了ARP协议标准。

1.2 ARP消息格式

  1. /* 文件: include/dnet/arp.h */
  2. #define ARP_HDR_LEN    8/* 基础ARP头部长度 */
  3. #define ARP_ETHIP_LEN  20/* Ethernet/IP ARP消息长度 */
  4. /* ARP头部结构 */
  5. struct arp_hdr {
  6. uint16_t ar_hrd;/* 硬件地址格式 */
  7. uint16_t ar_pro;/* 协议地址格式 */
  8. uint8_t  ar_hln;/* 硬件地址长度 (ETH_ADDR_LEN) */
  9. uint8_t  ar_pln;/* 协议地址长度 (IP_ADDR_LEN) */
  10. uint16_t ar_op;/* 操作类型 */
  11. } __attribute__((__packed__));
  12. /* Ethernet/IP ARP消息 */
  13. struct arp_ethip {
  14. eth_addr_t ar_sha;/* 发送方硬件地址 */
  15. ip_addr_t  ar_spa;/* 发送方协议地址 */
  16. eth_addr_t ar_tha;/* 目标硬件地址 */
  17. ip_addr_t  ar_tpa;/* 目标协议地址 */
  18. } __attribute__((__packed__));

字段说明:

  • ar_hrd: 硬件类型,Ethernet为0x0001
  • ar_pro: 协议类型,IPv4为0x0800
  • ar_hln: 硬件地址长度,Ethernet为6字节
  • ar_pln: 协议地址长度,IPv4为4字节
  • ar_op: 操作类型
  • ARP_OP_REQUEST (1): ARP请求
  • ARP_OP_REPLY (2): ARP应答
  • ARP_OP_REVREQUEST (3): 反向ARP请求
  • ARP_OP_REVREPLY (4): 反向ARP应答

1.3 ARP缓存条目

  1. /* ARP缓存条目 */
  2. struct arp_entry {
  3. struct addr arp_pa;/* 协议地址(IP地址) */
  4. struct addr arp_ha;/* 硬件地址(MAC地址) */
  5. };

2. libdnet ARP接口设计

2.1 核心API接口

  1. /* 文件: include/dnet/arp.h */
  2. typedefstruct arp_handle arp_t;
  3. typedefint(*arp_handler)(conststruct arp_entry * entry,void*arg);
  4. /* ARP句柄操作 */
  5. arp_t*arp_open(void);// 打开ARP句柄
  6. arp_t*arp_close(arp_t*arp);// 关闭ARP句柄
  7. /* ARP缓存操作 */
  8. int arp_add(arp_t*arp,conststruct arp_entry *entry);// 添加ARP条目
  9. int arp_delete(arp_t*arp,conststruct arp_entry *entry);// 删除ARP条目
  10. int arp_get(arp_t*arp,struct arp_entry *entry);// 获取ARP条目
  11. int arp_loop(arp_t*arp, arp_handler callback,void*arg);// 遍历ARP缓存

2.2 ARP消息打包宏

  1. /* 打包Ethernet/IP ARP消息的便捷宏 */
  2. #define arp_pack_hdr_ethip(hdr, op, sha, spa, tha, tpa)do{ \
  3. struct arp_hdr *pack_arp_p =(struct arp_hdr *)(hdr);    \
  4. struct arp_ethip *pack_ethip_p =(struct arp_ethip *)(pack_arp_p +1); \
  5.     pack_arp_p->ar_hrd = htons(ARP_HRD_ETH);        \
  6.     pack_arp_p->ar_pro = htons(ARP_PRO_IP);         \
  7.     pack_arp_p->ar_hln = ETH_ADDR_LEN;              \
  8.     pack_arp_p->ar_pln = IP_ADDR_LEN;               \
  9.     pack_arp_p->ar_op = htons(op);                  \
  10.     memcpy(&pack_ethip_p->ar_sha,&(sha), ETH_ADDR_LEN); \
  11.     memcpy(&pack_ethip_p->ar_spa,&(spa), IP_ADDR_LEN); \
  12.     memcpy(&pack_ethip_p->ar_tha,&(tha), ETH_ADDR_LEN); \
  13.     memcpy(&pack_ethip_p->ar_tpa,&(tpa), IP_ADDR_LEN); \
  14. }while(0)

2.3 平台适配机制

libdnet通过编译时检测自动选择对应的平台实现:

configure.ac中的选择逻辑:

  1. /* 文件: configure.ac 第219-228行 */
  2. dnl Checkfor arp interface.
  3. if test "$ac_cv_header_iphlpapi_h"= yes ; then
  4.     AC_LIBOBJ([arp-win32])/* Windows平台 */
  5. elif test "$ac_cv_dnet_ioctl_arp"= yes ; then
  6.     AC_LIBOBJ([arp-ioctl])/* Linux/Unix ioctl方式 */
  7. elif test "$ac_cv_dnet_route_h_has_rt_msghdr"= yes ; then
  8.     AC_LIBOBJ([arp-bsd])/* BSD/macOS route socket方式 */
  9. else
  10.     AC_LIBOBJ([arp-none])/* 不支持的平台 */
  11. fi

3. Linux平台实现

3.1 实现架构

Linux平台使用 ioctl系统调用操作ARP缓存,核心文件为 src/arp-ioctl.c

平台检测:

  1. /* 文件: m4/acinclude.m4 第201-221行 */
  2. AC_DEFUN([AC_DNET_IOCTL_ARP],
  3. [AC_MSG_CHECKING(for arp(7) ioctls)
  4.     AC_CACHE_VAL(ac_cv_dnet_ioctl_arp,
  5.     AC_EGREP_CPP(werd,[
  6. #include<sys/types.h>
  7. #define BSD_COMP
  8. #include<sys/ioctl.h>
  9. #ifdef SIOCGARP    /* 检查ARP相关ioctl定义 */
  10.         werd
  11. #endif],
  12.         ac_cv_dnet_ioctl_arp=yes,
  13.         ac_cv_dnet_ioctl_arp=no))
  14. case"$host_os" in
  15.     irix*)
  16.         ac_cv_dnet_ioctl_arp=no ;;/* IRIX不支持 */
  17.     esac])

3.2 句柄管理

ARP句柄结构:

  1. /* 文件: src/arp-ioctl.c 第47-52行 */
  2. struct arp_handle {
  3. int   fd;/* socket文件描述符 */
  4. #ifdef HAVE_ARPREQ_ARP_DEV
  5. intf_t*intf;/* 网络接口句柄(需要指定设备时) */
  6. #endif
  7. };

打开ARP句柄:

  1. /* 文件: src/arp-ioctl.c 第54-74行 */
  2. arp_t*
  3. arp_open(void)
  4. {
  5. arp_t*a;
  6. if((= calloc(1,sizeof(*a)))!= NULL){
  7. #ifdef HAVE_STREAMS_MIB2
  8. /* Solaris STREAMS方式 */
  9. if((a->fd = open(IP_DEV_NAME, O_RDWR))<0)
  10. #elif defined(HAVE_STREAMS_ROUTE)
  11. /* Solaris/IRIX STREAMS路由设备 */
  12. if((a->fd = open("/dev/route", O_WRONLY,0))<0)
  13. #else
  14. /* 标准Linux: 创建AF_INET socket用于ioctl */
  15. if((a->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
  16. #endif
  17. return(arp_close(a));
  18. #ifdef HAVE_ARPREQ_ARP_DEV
  19. /* 需要指定网络设备的平台 */
  20. if((a->intf = intf_open())== NULL)
  21. return(arp_close(a));
  22. #endif
  23. }
  24. return(a);
  25. }

关键技术点:

  1. 使用 AF_INET + SOCK_DGRAM创建socket,而不是原始socket
  2. 该socket仅用于 ioctl操作,不进行实际网络通信
  3. 某些Linux发行版需要指定网络接口名称

3.3 添加ARP条目

实现源码:

  1. /* 文件: src/arp-ioctl.c 第100-163行 */
  2. int
  3. arp_add(arp_t*a,conststruct arp_entry *entry)
  4. {
  5. struct arpreq ar;
  6.     memset(&ar,0,sizeof(ar));
  7. /* 设置协议地址(IP) */
  8. if(addr_ntos(&entry->arp_pa,&ar.arp_pa)<0)
  9. return(-1);
  10. /* 设置硬件地址(MAC) - 平台差异处理 */
  11. #ifdef __linux__
  12. /* Linux特定: 设置sa_family为ARP_HRD_ETH */
  13. if(addr_ntos(&entry->arp_ha,&ar.arp_ha)<0)
  14. return(-1);
  15.     ar.arp_ha.sa_family = ARP_HRD_ETH;
  16. #else
  17. /* Solaris, HP-UX, IRIX等其他Unix系统 */
  18.     ar.arp_ha.sa_family = AF_UNSPEC;
  19.     memcpy(ar.arp_ha.sa_data,&entry->arp_ha.addr_eth, ETH_ADDR_LEN);
  20. #endif
  21. /* 指定网络接口 */
  22. #ifdef HAVE_ARPREQ_ARP_DEV
  23. if(intf_loop(a->intf, _arp_set_dev,&ar)!=1){
  24.         errno = ESRCH;
  25. return(-1);
  26. }
  27. #endif
  28. /* 设置ARP标志: 永久且完成 */
  29.     ar.arp_flags = ATF_PERM | ATF_COM;
  30. #ifdef hpux
  31. /* HP-UX特殊处理: 扩展arpreq结构 */
  32. {
  33. struct sockaddr_in *sin;
  34.         ar.arp_hw_addr_len = ETH_ADDR_LEN;
  35.         sin =(struct sockaddr_in *)&ar.arp_pa_mask;
  36.         sin->sin_family = AF_INET;
  37.         sin->sin_addr.s_addr = IP_ADDR_BROADCAST;
  38. }
  39. #endif
  40. /* 执行ioctl添加ARP条目 */
  41. if(ioctl(a->fd, SIOCSARP,&ar)<0)
  42. return(-1);
  43. #ifdef HAVE_STREAMS_MIB2
  44. /* Solaris MIB2特殊处理: 强制进入ipNetToMediaTable */
  45. {
  46. struct sockaddr_in sin;
  47. int fd;
  48.         addr_ntos(&entry->arp_pa,(struct sockaddr *)&sin);
  49.         sin.sin_port = htons(666);/* 魔术端口触发ARP查询 */
  50. if((fd = socket(AF_INET, SOCK_DGRAM,0))<0)
  51. return(-1);
  52. if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
  53.             close(fd);
  54. return(-1);
  55. }
  56.         write(fd, NULL,0);/* 触发ARP解析 */
  57.         close(fd);
  58. }
  59. #endif
  60. return(0);
  61. }

网络设备查找辅助函数:

  1. /* 文件: src/arp-ioctl.c 第76-98行 */
  2. staticint
  3. _arp_set_dev(conststruct intf_entry *entry,void*arg)
  4. {
  5. struct arpreq *ar =(struct arpreq *)arg;
  6. struct addr dst;
  7. uint32_t mask;
  8. /* 只处理Ethernet接口 */
  9. if(entry->intf_type == INTF_TYPE_ETH &&
  10.         entry->intf_addr.addr_type == ADDR_TYPE_IP){
  11. /* 计算子网掩码 */
  12.         addr_btom(entry->intf_addr.addr_bits,&mask, IP_ADDR_LEN);
  13.         addr_ston((struct sockaddr *)&ar->arp_pa,&dst);
  14. /* 检查目标IP是否在该接口的子网内 */
  15. if((entry->intf_addr.addr_ip & mask)==
  16. (dst.addr_ip & mask)){
  17. /* 设置设备名称 */
  18.             strlcpy(ar->arp_dev, entry->intf_name,
  19. sizeof(ar->arp_dev));
  20. return(1);/* 找到匹配的接口 */
  21. }
  22. }
  23. return(0);
  24. }

ioctl命令详解:

  • SIOCSARP (Set ARP): 添加或修改ARP条目
  • SIOCDARP (Delete ARP): 删除ARP条目
  • SIOCGARP (Get ARP): 获取ARP条目

ARP标志位:

  1. /* Linux kernel定义的ARP标志 */
  2. #define ATF_COM     0x02/* 地址完成(已解析) */
  3. #define ATF_PERM    0x04/* 永久条目 */
  4. #define ATF_PUBL    0x08/* 发布条目(代理ARP) */
  5. #define ATF_USETRAILERS 0x10/* 使用trailers */

3.4 删除ARP条目

实现源码:

  1. /* 文件: src/arp-ioctl.c 第165-179行 */
  2. int
  3. arp_delete(arp_t*a,conststruct arp_entry *entry)
  4. {
  5. struct arpreq ar;
  6.     memset(&ar,0,sizeof(ar));
  7. /* 只需要指定IP地址 */
  8. if(addr_ntos(&entry->arp_pa,&ar.arp_pa)<0)
  9. return(-1);
  10. /* 执行ioctl删除 */
  11. if(ioctl(a->fd, SIOCDARP,&ar)<0)
  12. return(-1);
  13. return(0);
  14. }

3.5 获取ARP条目

实现源码:

  1. /* 文件: src/arp-ioctl.c 第181-205行 */
  2. int
  3. arp_get(arp_t*a,struct arp_entry *entry)
  4. {
  5. struct arpreq ar;
  6.     memset(&ar,0,sizeof(ar));
  7. /* 设置要查询的IP地址 */
  8. if(addr_ntos(&entry->arp_pa,&ar.arp_pa)<0)
  9. return(-1);
  10. /* 指定网络接口 */
  11. #ifdef HAVE_ARPREQ_ARP_DEV
  12. if(intf_loop(a->intf, _arp_set_dev,&ar)!=1){
  13.         errno = ESRCH;
  14. return(-1);
  15. }
  16. #endif
  17. /* 执行ioctl查询 */
  18. if(ioctl(a->fd, SIOCGARP,&ar)<0)
  19. return(-1);
  20. /* 检查条目是否完成 */
  21. if((ar.arp_flags & ATF_COM)==0){
  22.         errno = ESRCH;
  23. return(-1);
  24. }
  25. /* 提取硬件地址 */
  26. return(addr_ston(&ar.arp_ha,&entry->arp_ha));
  27. }

3.6 遍历ARP缓存

Linux提供三种遍历ARP缓存的方式:

方式1: /proc/net/arp 文件解析(推荐)

  1. /* 文件: src/arp-ioctl.c 第207-240行 */
  2. #ifdef HAVE_LINUX_PROCFS
  3. int
  4. arp_loop(arp_t*a, arp_handler callback,void*arg)
  5. {
  6. FILE*fp;
  7. struct arp_entry entry;
  8. char buf[BUFSIZ], ipbuf[100], macbuf[100], maskbuf[100], devbuf[100];
  9. int i, type, flags, ret;
  10. /* 打开/proc/net/arp文件 */
  11. if((fp = fopen(PROC_ARP_FILE,"r"))== NULL)
  12. return(-1);
  13.     ret =0;
  14. while(fgets(buf,sizeof(buf), fp)!= NULL){
  15. /* 解析ARP条目行
  16.          * 格式: IP地址  HW类型  标志  MAC地址  掩码  设备
  17.          * 示例: 192.168.1.1  0x1  0x2  00:11:22:33:44:55  *  eth0
  18.          */
  19.         i = sscanf(buf,"%s 0x%x 0x%x %99s %99s %99s\n",
  20.             ipbuf,&type,&flags, macbuf, maskbuf, devbuf);
  21. if(<4||(flags & ATF_COM)==0)
  22. continue;/* 跳过未完成的条目 */
  23. /* 转换地址并调用回调 */
  24. if(addr_aton(ipbuf,&entry.arp_pa)==0&&
  25.             addr_aton(macbuf,&entry.arp_ha)==0){
  26. if((ret = callback(&entry, arg))!=0)
  27. break;
  28. }
  29. }
  30. if(ferror(fp)){
  31.         fclose(fp);
  32. return(-1);
  33. }
  34.     fclose(fp);
  35. return(ret);
  36. }
  37. #endif

/proc/net/arp文件格式示例:

  1. IP address       HW type  Flags  HW address            MaskDevice
  2. 192.168.1.10x10x200:11:22:33:44:55*        eth0
  3. 192.168.1.2540x10x6    aa:bb:cc:dd:ee:ff     *        eth0

Flags字段说明:

  • 0x0: 未完成
  • 0x2: 完成
  • 0x4: 永久
  • 0x6: 永久且完成
  • 0x8: 代理ARP

方式2: STREAMS MIB2(Solaris/Irix)

  1. /* 文件: src/arp-ioctl.c 第241-337行 */
  2. #elif defined (HAVE_STREAMS_MIB2)
  3. int
  4. arp_loop(arp_t*r, arp_handler callback,void*arg)
  5. {
  6. struct arp_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. mib2_ipNetToMediaEntry_t*arp,*arpend;
  13.     u_char buf[8192];
  14. int flags, rc, atable, ret;
  15.     tor =(struct T_optmgmt_req *)buf;
  16.     toa =(struct T_optmgmt_ack *)buf;
  17.     tea =(struct T_error_ack *)buf;
  18. /* 构建MIB2查询请求 */
  19.     tor->PRIM_type = T_OPTMGMT_REQ;
  20.     tor->OPT_offset =sizeof(*tor);
  21.     tor->OPT_length =sizeof(*opt);
  22.     tor->MGMT_flags = T_CURRENT;
  23.     opt =(struct opthdr *)(tor +1);
  24.     opt->level = MIB2_IP;
  25.     opt->name = opt->len =0;
  26.     msg.maxlen =sizeof(buf);
  27.     msg.len =sizeof(*tor)+sizeof(*opt);
  28.     msg.buf = buf;
  29. /* 发送查询请求 */
  30. if(putmsg(r->fd,&msg, NULL,0)<0)
  31. return(-1);
  32.     opt =(struct opthdr *)(toa +1);
  33.     msg.maxlen =sizeof(buf);
  34. /* 循环读取响应 */
  35. for(;;){
  36.         flags =0;
  37. if((rc = getmsg(r->fd,&msg, NULL,&flags))<0)
  38. return(-1);
  39. /* 检查是否完成 */
  40. if(rc ==0&&
  41.             msg.len >=sizeof(*toa)&&
  42.             toa->PRIM_type == T_OPTMGMT_ACK &&
  43.             toa->MGMT_flags == T_SUCCESS && opt->len ==0)
  44. break;
  45. if(msg.len >=sizeof(*tea)&& tea->PRIM_type == T_ERROR_ACK)
  46. return(-1);
  47. if(rc != MOREDATA || msg.len <(int)sizeof(*toa)||
  48.             toa->PRIM_type != T_OPTMGMT_ACK ||
  49.             toa->MGMT_flags != T_SUCCESS)
  50. return(-1);
  51. /* 检查是否是ARP表 */
  52.         atable =(opt->level == MIB2_IP && opt->name == MIB2_IP_22);
  53.         msg.maxlen =sizeof(buf)-(sizeof(buf)%sizeof(*arp));
  54.         msg.len =0;
  55.         flags =0;
  56. do{
  57.             rc = getmsg(r->fd, NULL,&msg,&flags);
  58. if(rc !=0&& rc != MOREDATA)
  59. return(-1);
  60. if(!atable)
  61. continue;
  62. /* 解析ARP条目 */
  63.             arp =(mib2_ipNetToMediaEntry_t*)msg.buf;
  64.             arpend =(mib2_ipNetToMediaEntry_t*)
  65. (msg.buf + msg.len);
  66.             entry.arp_pa.addr_type = ADDR_TYPE_IP;
  67.             entry.arp_pa.addr_bits = IP_ADDR_BITS;
  68.             entry.arp_ha.addr_type = ADDR_TYPE_ETH;
  69.             entry.arp_ha.addr_bits = ETH_ADDR_BITS;
  70. for(; arp < arpend; arp++){
  71.                 entry.arp_pa.addr_ip =
  72.                     arp->ipNetToMediaNetAddress;
  73.                 memcpy(&entry.arp_ha.addr_eth,
  74.                     arp->ipNetToMediaPhysAddress.o_bytes,
  75.                     ETH_ADDR_LEN);
  76. if((ret = callback(&entry, arg))!=0)
  77. return(ret);
  78. }
  79. }while(rc == MOREDATA);
  80. }
  81. return(0);
  82. }
  83. #endif

方式3: 内核内存读取(HP-UX/Tru64)

  1. /* 文件: src/arp-ioctl.c 第338-385行 */
  2. #elif defined(HAVE_SYS_MIB_H)
  3. #define MAX_ARPENTRIES  512/* 最大条目数 */
  4. int
  5. arp_loop(arp_t*r, arp_handler callback,void*arg)
  6. {
  7. struct nmparms nm;
  8. struct arp_entry entry;
  9.     mib_ipNetToMediaEnt arpentries[MAX_ARPENTRIES];
  10. int fd, i, n, ret;
  11. /* 打开MIB设备 */
  12. if((fd = open_mib("/dev/ip", O_RDWR,0,0))<0)
  13. return(-1);
  14. /* 查询ARP表 */
  15.     nm.objid = ID_ipNetToMediaTable;
  16.     nm.buffer = arpentries;
  17.     n =sizeof(arpentries);
  18.     nm.len =&n;
  19. if(get_mib_info(fd,&nm)<0){
  20.         close_mib(fd);
  21. return(-1);
  22. }
  23.     close_mib(fd);
  24.     entry.arp_pa.addr_type = ADDR_TYPE_IP;
  25.     entry.arp_pa.addr_bits = IP_ADDR_BITS;
  26.     entry.arp_ha.addr_type = ADDR_TYPE_ETH;
  27.     entry.arp_ha.addr_bits = ETH_ADDR_BITS;
  28.     n /=sizeof(*arpentries);
  29.     ret =0;
  30. for(=0; i < n; i++){
  31. /* 跳过无效条目 */
  32. if(arpentries[i].Type== INTM_INVALID ||
  33.             arpentries[i].PhysAddr.o_length != ETH_ADDR_LEN)
  34. continue;
  35.         entry.arp_pa.addr_ip = arpentries[i].NetAddr;
  36.         memcpy(&entry.arp_ha.addr_eth,
  37.             arpentries[i].PhysAddr.o_bytes, ETH_ADDR_LEN);
  38. if((ret = callback(&entry, arg))!=0)
  39. break;
  40. }
  41. return(ret);
  42. }
  43. #endif

3.7 Linux平台特定问题

权限要求:

  • 添加/删除ARP条目需要 CAP_NET_ADMIN能力(root或sudo)
  • 读取ARP缓存普通用户即可

内核版本差异:

  1. /* 某些旧内核不支持arpreq中的arp_dev字段 */
  2. #ifndef HAVE_ARPREQ_ARP_DEV
  3. /* 需要手动设置网络设备 */
  4. #endif

IPv6支持:

  • libdnet的ARP模块仅支持IPv4
  • IPv6使用NDP(Neighbor Discovery Protocol),不在ARP模块处理

4. macOS/BSD平台实现

4.1 实现架构

macOS和BSD系列系统使用路由套接字(Route Socket)管理ARP缓存,核心文件为 src/arp-bsd.c

平台选择逻辑:

  1. /* configure.ac 第224-225行 */
  2. elif test "$ac_cv_dnet_route_h_has_rt_msghdr"= yes ; then
  3.     AC_LIBOBJ([arp-bsd])

4.2 路由套接字机制

BSD系统通过 PF_ROUTE套接字接收和发送路由消息,包括ARP表项变更。

句柄结构:

  1. /* 文件: src/arp-bsd.c 第38-46行 */
  2. struct arp_handle {
  3. int   fd;/* 路由套接字文件描述符 */
  4. int   seq;/* 序列号,用于匹配请求和响应 */
  5. };
  6. /* ARP消息结构 */
  7. struct arpmsg {
  8. struct rt_msghdr rtm;/* 路由消息头 */
  9.     u_char          addrs[256];/* 地址数据 */
  10. };

打开句柄:

  1. /* 文件: src/arp-bsd.c 第48-62行 */
  2. arp_t*
  3. arp_open(void)
  4. {
  5. arp_t*arp;
  6. if((arp = calloc(1,sizeof(*arp)))!= NULL){
  7. #ifdef HAVE_STREAMS_ROUTE
  8. /* STREAMS路由设备(Solaris/Irix) */
  9. if((arp->fd = open("/dev/route", O_RDWR,0))<0)
  10. #else
  11. /* 标准BSD/macOS: 创建路由套接字 */
  12. if((arp->fd = socket(PF_ROUTE, SOCK_RAW,0))<0)
  13. #endif
  14. return(arp_close(arp));
  15. }
  16. return(arp);
  17. }

4.3 路由消息通信

核心消息处理函数:

  1. /* 文件: src/arp-bsd.c 第64-107行 */
  2. staticint
  3. arp_msg(arp_t*arp,struct arpmsg *msg)
  4. {
  5. struct arpmsg smsg;
  6. int len, i =0;
  7. pid_t pid;
  8. /* 设置消息版本和序列号 */
  9.     msg->rtm.rtm_version = RTM_VERSION;
  10.     msg->rtm.rtm_seq =++arp->seq;
  11.     memcpy(&smsg, msg,sizeof(smsg));
  12. #ifdef HAVE_STREAMS_ROUTE
  13. /* STREAMS方式: 使用ioctl */
  14. return(ioctl(arp->fd, RTSTR_SEND,&msg->rtm));
  15. #else
  16. /* 标准BSD/macOS: 使用write/read */
  17. /* 发送路由消息 */
  18. if(write(arp->fd,&smsg, smsg.rtm.rtm_msglen)<0){
  19. /* 处理特殊情况: 删除不存在的条目 */
  20. if(errno != ESRCH || msg->rtm.rtm_type != RTM_DELETE)
  21. return(-1);
  22. }
  23.     pid = getpid();
  24. /* 读取响应消息 */
  25. while((len = read(arp->fd, msg,sizeof(*msg)))>0){
  26. if(len <(int)sizeof(msg->rtm))
  27. return(-1);
  28. /* 检查是否是我们的消息 */
  29. if(msg->rtm.rtm_pid == pid){
  30. if(msg->rtm.rtm_seq == arp->seq)
  31. break;/* 找到匹配的响应 */
  32. continue;
  33. }elseif((i++%2)==0)
  34. continue;
  35. /* 重复请求 */
  36. if(write(arp->fd,&smsg, smsg.rtm.rtm_msglen)<0){
  37. if(errno != ESRCH || msg->rtm.rtm_type != RTM_DELETE)
  38. return(-1);
  39. }
  40. }
  41. if(len <0)
  42. return(-1);
  43. return(0);
  44. #endif
  45. }

路由消息类型:

  1. /* FreeBSD/macOS内核定义 */
  2. #define RTM_ADD        0x1/* 添加路由 */
  3. #define RTM_DELETE     0x2/* 删除路由 */
  4. #define RTM_CHANGE     0x3/* 修改路由 */
  5. #define RTM_GET        0x4/* 获取路由 */

地址标志位:

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

路由标志位:

  1. #define RTF_LLINFO     0x400/* 链路层信息(ARP) */
  2. #define RTF_STATIC     0x800/* 手动添加 */
  3. #define RTF_HOST       0x4/* 主机路由 */
  4. #define RTF_GATEWAY    0x2/* 网关路由 */

4.4 添加ARP条目

实现源码:

  1. /* 文件: src/arp-bsd.c 第109-173行 */
  2. int
  3. arp_add(arp_t*arp,conststruct arp_entry *entry)
  4. {
  5. struct arpmsg msg;
  6. struct sockaddr_in *sin;
  7. struct sockaddr *sa;
  8. int index, type;
  9. /* 验证地址类型 */
  10. if(entry->arp_pa.addr_type != ADDR_TYPE_IP ||
  11.         entry->arp_ha.addr_type != ADDR_TYPE_ETH){
  12.         errno = EAFNOSUPPORT;
  13. return(-1);
  14. }
  15.     sin =(struct sockaddr_in *)msg.addrs;
  16.     sa =(struct sockaddr *)(sin +1);
  17. /* 首先查询现有路由 */
  18. if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0)
  19. return(-1);
  20.     memset(&msg.rtm,0,sizeof(msg.rtm));
  21.     msg.rtm.rtm_type = RTM_GET;
  22.     msg.rtm.rtm_addrs = RTA_DST;
  23.     msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin);
  24. if(arp_msg(arp,&msg)<0)
  25. return(-1);
  26. /* 验证响应 */
  27. if(msg.rtm.rtm_msglen <(int)sizeof(msg.rtm)+
  28. sizeof(*sin)+sizeof(*sa)){
  29.         errno = EADDRNOTAVAIL;
  30. return(-1);
  31. }
  32. /* 检查是否是ARP条目而非网关 */
  33. if(sin->sin_addr.s_addr == entry->arp_pa.addr_ip){
  34. if((msg.rtm.rtm_flags & RTF_LLINFO)==0||
  35. (msg.rtm.rtm_flags & RTF_GATEWAY)!=0){
  36.             errno = EADDRINUSE;
  37. return(-1);
  38. }
  39. }
  40. /* 保存接口信息 */
  41. if(sa->sa_family != AF_LINK){
  42.         errno = EADDRNOTAVAIL;
  43. return(-1);
  44. }else{
  45.         index =((struct sockaddr_dl *)sa)->sdl_index;/* 接口索引 */
  46.         type =((struct sockaddr_dl *)sa)->sdl_type;/* 接口类型 */
  47. }
  48. /* 构建添加消息 */
  49. if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0||
  50.         addr_ntos(&entry->arp_ha, sa)<0)
  51. return(-1);
  52. /* 恢复接口信息 */
  53. ((struct sockaddr_dl *)sa)->sdl_index = index;
  54. ((struct sockaddr_dl *)sa)->sdl_type = type;
  55. /* 设置消息参数 */
  56.     memset(&msg.rtm,0,sizeof(msg.rtm));
  57.     msg.rtm.rtm_type = RTM_ADD;
  58.     msg.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY;
  59.     msg.rtm.rtm_inits = RTV_EXPIRE;/* 设置过期时间 */
  60.     msg.rtm.rtm_flags = RTF_HOST | RTF_STATIC;/* 主机路由、静态 */
  61. #ifdef HAVE_SOCKADDR_SA_LEN
  62.     msg.rtm.rtm_msglen =sizeof(msg.rtm)+ sin->sin_len + sa->sa_len;
  63. #else
  64.     msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin)+sizeof(*sa);
  65. #endif
  66. return(arp_msg(arp,&msg));
  67. }

关键点解析:

  1. 两步操作:
  • 先用 RTM_GET查询接口信息
  • 再用 RTM_ADD添加条目
  1. 地址结构:
  • RTA_DST: 目标IP地址
  • RTA_GATEWAY: 实际存储MAC地址(通过sockaddr_dl)
  1. 接口索引:
  • BSD系统使用接口索引而非名称
  • 通过 sdl_index字段指定接口
  1. 过期时间:
  • RTV_EXPIRE标志表示设置过期时间
  • RTF_STATIC标志创建静态条目

4.5 删除ARP条目

实现源码:

  1. /* 文件: src/arp-bsd.c 第175-219行 */
  2. int
  3. arp_delete(arp_t*arp,conststruct arp_entry *entry)
  4. {
  5. struct arpmsg msg;
  6. struct sockaddr_in *sin;
  7. struct sockaddr *sa;
  8. /* 验证地址类型 */
  9. if(entry->arp_pa.addr_type != ADDR_TYPE_IP){
  10.         errno = EAFNOSUPPORT;
  11. return(-1);
  12. }
  13.     sin =(struct sockaddr_in *)msg.addrs;
  14.     sa =(struct sockaddr *)(sin +1);
  15. /* 查询现有路由 */
  16. if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0)
  17. return(-1);
  18.     memset(&msg.rtm,0,sizeof(msg.rtm));
  19.     msg.rtm.rtm_type = RTM_GET;
  20.     msg.rtm.rtm_addrs = RTA_DST;
  21.     msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin);
  22. if(arp_msg(arp,&msg)<0)
  23. return(-1);
  24. /* 验证响应 */
  25. if(msg.rtm.rtm_msglen <(int)sizeof(msg.rtm)+
  26. sizeof(*sin)+sizeof(*sa)){
  27.         errno = ESRCH;
  28. return(-1);
  29. }
  30. /* 检查是否是ARP条目 */
  31. if(sin->sin_addr.s_addr == entry->arp_pa.addr_ip){
  32. if((msg.rtm.rtm_flags & RTF_LLINFO)==0||
  33. (msg.rtm.rtm_flags & RTF_GATEWAY)!=0){
  34.             errno = EADDRINUSE;
  35. return(-1);
  36. }
  37. }
  38. /* 验证地址族 */
  39. if(sa->sa_family != AF_LINK){
  40.         errno = ESRCH;
  41. return(-1);
  42. }
  43. /* 发送删除请求 */
  44.     msg.rtm.rtm_type = RTM_DELETE;
  45. return(arp_msg(arp,&msg));
  46. }

4.6 获取ARP条目

实现源码:

  1. /* 文件: src/arp-bsd.c 第221-258行 */
  2. int
  3. arp_get(arp_t*arp,struct arp_entry *entry)
  4. {
  5. struct arpmsg msg;
  6. struct sockaddr_in *sin;
  7. struct sockaddr *sa;
  8. /* 验证地址类型 */
  9. if(entry->arp_pa.addr_type != ADDR_TYPE_IP){
  10.         errno = EAFNOSUPPORT;
  11. return(-1);
  12. }
  13.     sin =(struct sockaddr_in *)msg.addrs;
  14.     sa =(struct sockaddr *)(sin +1);
  15. /* 设置查询参数 */
  16. if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0)
  17. return(-1);
  18.     memset(&msg.rtm,0,sizeof(msg.rtm));
  19.     msg.rtm.rtm_type = RTM_GET;
  20.     msg.rtm.rtm_addrs = RTA_DST;
  21.     msg.rtm.rtm_flags = RTF_LLINFO;/* 只查询ARP条目 */
  22.     msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin);
  23. /* 发送查询 */
  24. if(arp_msg(arp,&msg)<0)
  25. return(-1);
  26. /* 验证响应 */
  27. if(msg.rtm.rtm_msglen <(int)sizeof(msg.rtm)+
  28. sizeof(*sin)+sizeof(*sa)||
  29.         sin->sin_addr.s_addr != entry->arp_pa.addr_ip ||
  30.         sa->sa_family != AF_LINK){
  31.         errno = ESRCH;
  32. return(-1);
  33. }
  34. /* 提取MAC地址 */
  35. if(addr_ston(sa,&entry->arp_ha)<0)
  36. return(-1);
  37. return(0);
  38. }

4.7 遍历ARP缓存

sysctl方式遍历(macOS/BSD推荐):

  1. /* 文件: src/arp-bsd.c 第260-304行 */
  2. #ifdef HAVE_SYS_SYSCTL_H
  3. int
  4. arp_loop(arp_t*arp, arp_handler callback,void*arg)
  5. {
  6. struct arp_entry entry;
  7. struct rt_msghdr *rtm;
  8. struct sockaddr_in *sin;
  9. struct sockaddr *sa;
  10. char*buf,*lim,*next;
  11. size_t len;
  12. int ret, mib[6]={ CTL_NET, PF_ROUTE,0, AF_INET,
  13.                 NET_RT_FLAGS, RTF_LLINFO };
  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.     ret =0;
  29. /* 遍历所有路由消息 */
  30. for(next = buf; next < lim; next += rtm->rtm_msglen){
  31.         rtm =(struct rt_msghdr *)next;
  32.         sin =(struct sockaddr_in *)(rtm +1);
  33.         sa =(struct sockaddr *)(sin +1);
  34. /* 解析IP和MAC地址 */
  35. if(addr_ston((struct sockaddr *)sin,&entry.arp_pa)<0||
  36.             addr_ston(sa,&entry.arp_ha)<0)
  37. continue;
  38. /* 调用回调函数 */
  39. if((ret = callback(&entry, arg))!=0)
  40. break;
  41. }
  42.     free(buf);
  43. return(ret);
  44. }
  45. #endif

sysctl MIB数组解析:

  1. int mib[6]={
  2.     CTL_NET,// 网络子系统
  3.     PF_ROUTE,// 路由子系统
  4. 0,// 协议族(0表示所有)
  5.     AF_INET,// 地址族(IPv4)
  6.     NET_RT_FLAGS,// 获取指定标志的路由
  7.     RTF_LLINFO      // 链路层信息(ARP)
  8. };

sysctl的优势:

  1. 不需要打开socket
  2. 原子操作,一次获取所有数据
  3. 性能高,无多次I/O开销
  4. 不需要root权限

4.8 macOS/BSD平台特定问题

sockaddr_dl结构:

  1. /* 链路层地址结构 */
  2. struct sockaddr_dl {
  3. uint8_t  sdl_len;/* 地址长度 */
  4. uint8_t  sdl_family;/* AF_LINK */
  5. uint16_t sdl_index;/* 接口索引 */
  6. uint8_t  sdl_type;/* 接口类型 */
  7. uint8_t  sdl_nlen;/* 接口名称长度 */
  8. uint8_t  sdl_alen;/* 链路层地址长度 */
  9. uint8_t  sdl_slen;/* 选择器长度 */
  10. char     sdl_data[12];/* 接口名称和地址 */
  11. };

MAC地址提取:

  1. /* 从sockaddr_dl中提取MAC地址 */
  2. if(sa->sa_family == AF_LINK){
  3. struct sockaddr_dl *sdl =(struct sockaddr_dl *)sa;
  4. uint8_t*mac = LLADDR(sdl);/* 指向MAC地址的宏 */
  5. }

权限要求:

  • 添加/删除ARP条目需要root权限
  • 遍历ARP缓存普通用户即可

FreeBSD vs macOS差异:

  1. FreeBSD支持sockaddr_len字段
  2. macOS需要特殊处理某些标志位
  3. 不同版本的sysctl实现略有差异

5. Windows平台实现

5.1 实现架构

Windows平台使用IP Helper API(iphlpapi.dll)管理ARP缓存,核心文件为 src/arp-win32.c

句柄结构:

  1. /* 文件: src/arp-win32.c 第20-22行 */
  2. struct arp_handle {
  3.     MIB_IPNETTABLE *iptable;/* ARP表缓存指针 */
  4. };

5.2 打开句柄

实现源码:

  1. /* 文件: src/arp-win32.c 第24-28行 */
  2. arp_t*
  3. arp_open(void)
  4. {
  5. /* 简单分配内存,不打开任何系统资源 */
  6. return(calloc(1,sizeof(arp_t)));
  7. }

特点:

  • Windows实现不需要持久句柄
  • 所有操作都直接调用IP Helper API
  • 延迟加载ARP表

5.3 添加ARP条目

实现源码:

  1. /* 文件: src/arp-win32.c 第30-50行 */
  2. int
  3. arp_add(arp_t*arp,conststruct arp_entry *entry)
  4. {
  5.     MIB_IPFORWARDROW ipfrow;/* 路由条目 */
  6.     MIB_IPNETROW iprow;/* ARP条目 */
  7. /* 查找最佳路由以确定接口索引 */
  8. if(GetBestRoute(entry->arp_pa.addr_ip,
  9.         IP_ADDR_ANY,&ipfrow)!= NO_ERROR)
  10. return(-1);
  11. /* 填充ARP条目 */
  12.     iprow.dwIndex = ipfrow.dwForwardIfIndex;/* 接口索引 */
  13.     iprow.dwPhysAddrLen = ETH_ADDR_LEN;/* MAC地址长度 */
  14.     memcpy(iprow.bPhysAddr,&entry->arp_ha.addr_eth, ETH_ADDR_LEN);
  15.     iprow.dwAddr = entry->arp_pa.addr_ip;/* IP地址 */
  16.     iprow.dwType =4;/* XXX - static 静态条目 */
  17. /* 创建ARP条目 */
  18. if(CreateIpNetEntry(&iprow)!= NO_ERROR)
  19. return(-1);
  20. return(0);
  21. }

IP Helper API详解:

  1. GetBestRoute:
  • 查找到目标IP的最佳路由
  • 返回路由信息,包括接口索引
  • 原型: DWORDGetBestRoute(DWORD dwDestAddr,DWORD dwSourceAddr,PMIB_IPFORWARDROW pBestRoute);
  1. CreateIpNetEntry:
  • 创建ARP表条目
  • 原型: DWORDCreateIpNetEntry(PMIB_IPNETROW pArpEntry);
  1. MIB_IPNETROW结构:
  1. typedefstruct _MIB_IPNETROW {
  2.     DWORD    dwIndex;/* 接口索引 */
  3.     DWORD    dwPhysAddrLen;/* 物理地址长度 */
  4.     BYTE     bPhysAddr[MAXLEN_PHYSADDR];/* 物理地址 */
  5.     DWORD    dwAddr;/* IP地址 */
  6.     DWORD    dwType;/* 类型: 1=其他, 2=无效, 3=动态, 4=静态 */
  7. } MIB_IPNETROW,*PMIB_IPNETROW;

ARP条目类型:

  • 1: 其他
  • 2: 无效
  • 3: 动态(ARP协议学习)
  • 4: 静态(手动添加)

5.4 删除ARP条目

实现源码:

  1. /* 文件: src/arp-win32.c 第52-71行 */
  2. int
  3. arp_delete(arp_t*arp,conststruct arp_entry *entry)
  4. {
  5.     MIB_IPFORWARDROW ipfrow;
  6.     MIB_IPNETROW iprow;
  7. /* 查找最佳路由以确定接口索引 */
  8. if(GetBestRoute(entry->arp_pa.addr_ip,
  9.         IP_ADDR_ANY,&ipfrow)!= NO_ERROR)
  10. return(-1);
  11. /* 填充删除参数 */
  12.     memset(&iprow,0,sizeof(iprow));
  13.     iprow.dwIndex = ipfrow.dwForwardIfIndex;
  14.     iprow.dwAddr = entry->arp_pa.addr_ip;
  15. /* 删除ARP条目 */
  16. if(DeleteIpNetEntry(&iprow)!= NO_ERROR){
  17.         errno = ENXIO;
  18. return(-1);
  19. }
  20. return(0);
  21. }

DeleteIpNetEntry API:

  • 原型: DWORDDeleteIpNetEntry(PMIB_IPNETROW pArpEntry);
  • 只需要指定接口索引和IP地址
  • 不需要MAC地址

5.5 获取ARP条目

实现源码:

  1. /* 文件: src/arp-win32.c 第73-94行 */
  2. staticint
  3. _arp_get_entry(conststruct arp_entry *entry,void*arg)
  4. {
  5. struct arp_entry *=(struct arp_entry *)arg;
  6. /* 比较IP地址 */
  7. if(addr_cmp(&entry->arp_pa,&e->arp_pa)==0){
  8. /* 找到匹配项,复制MAC地址 */
  9.         memcpy(&e->arp_ha,&entry->arp_ha,sizeof(e->arp_ha));
  10. return(1);/* 停止遍历 */
  11. }
  12. return(0);
  13. }
  14. int
  15. arp_get(arp_t*arp,struct arp_entry *entry)
  16. {
  17. /* 遍历ARP表查找匹配项 */
  18. if(arp_loop(arp, _arp_get_entry, entry)!=1){
  19.         errno = ENXIO;
  20. SetLastError(ERROR_NO_DATA);
  21. return(-1);
  22. }
  23. return(0);
  24. }

实现策略:

  1. 使用 arp_loop遍历整个ARP表
  2. 使用回调函数查找匹配的IP地址
  3. 返回第一条匹配的MAC地址

5.6 遍历ARP缓存

实现源码:

  1. /* 文件: src/arp-win32.c 第96-132行 */
  2. int
  3. arp_loop(arp_t*arp, arp_handler callback,void*arg)
  4. {
  5. struct arp_entry entry;
  6.     ULONG len;
  7. int i, ret;
  8. /* 循环获取ARP表,直到缓冲区足够大 */
  9. for(len =sizeof(arp->iptable[0]);;){
  10. if(arp->iptable)
  11.             free(arp->iptable);
  12.         arp->iptable = malloc(len);
  13. if(arp->iptable == NULL)
  14. return(-1);
  15. /* 获取ARP表 */
  16.         ret =GetIpNetTable(arp->iptable,&len, FALSE);
  17. if(ret == NO_ERROR)
  18. break;/* 成功 */
  19. elseif(ret != ERROR_INSUFFICIENT_BUFFER)
  20. return(-1);/* 其他错误 */
  21. /* 缓冲区不足,继续循环 */
  22. }
  23.     entry.arp_pa.addr_type = ADDR_TYPE_IP;
  24.     entry.arp_pa.addr_bits = IP_ADDR_BITS;
  25.     entry.arp_ha.addr_type = ADDR_TYPE_ETH;
  26.     entry.arp_ha.addr_bits = ETH_ADDR_BITS;
  27. /* 遍历所有ARP条目 */
  28. for(=0; i <(int)arp->iptable->dwNumEntries; i++){
  29. /* 跳过MAC地址长度不正确的条目 */
  30. if(arp->iptable->table[i].dwPhysAddrLen != ETH_ADDR_LEN)
  31. continue;
  32.         entry.arp_pa.addr_ip = arp->iptable->table[i].dwAddr;
  33.         memcpy(&entry.arp_ha.addr_eth,
  34.             arp->iptable->table[i].bPhysAddr, ETH_ADDR_LEN);
  35. /* 调用回调函数 */
  36. if((ret =(*callback)(&entry, arg))!=0)
  37. return(ret);
  38. }
  39. return(0);
  40. }

GetIpNetTable API详解:

  1. /* 原型 */
  2. DWORD GetIpNetTable(
  3.     PMIB_IPNETTABLE pIpNetTable,// 接收ARP表
  4.     PULONG pdwSize,// 缓冲区大小(输入/输出)
  5.     BOOL bOrder                   // 是否排序
  6. );
  7. /* MIB_IPNETTABLE结构 */
  8. typedefstruct _MIB_IPNETTABLE {
  9.     DWORD       dwNumEntries;// 条目数量
  10.     MIB_IPNETROW table[1];// 条目数组(变长)
  11. } MIB_IPNETTABLE,*PMIB_IPNETTABLE;

缓冲区处理策略:

  1. 从小缓冲区开始
  2. 调用 GetIpNetTable
  3. 如果返回 ERROR_INSUFFICIENT_BUFFER,增大缓冲区
  4. 重复直到成功或错误

ARP条目过滤:

  • 只处理 dwPhysAddrLen==6的条目
  • 跳过无效或未完成的条目

5.7 关闭句柄

实现源码:

  1. /* 文件: src/arp-win32.c 第134-143行 */
  2. arp_t*
  3. arp_close(arp_t*arp)
  4. {
  5. if(arp != NULL){
  6. /* 释放缓存的ARP表 */
  7. if(arp->iptable != NULL)
  8.             free(arp->iptable);
  9.         free(arp);
  10. }
  11. return(NULL);
  12. }

5.8 Windows平台特定问题

DLL依赖:

  • 需要链接 iphlpapi.lib
  • 运行时需要 iphlpapi.dll
  • Windows 2000及以上版本支持

权限要求:

  • 添加/删除ARP条目需要管理员权限
  • 读取ARP缓存普通用户即可

API版本:

  • 传统API: GetIpNetTableCreateIpNetEntryDeleteIpNetEntry
  • 新版API (Vista+): GetIpNetTable2CreateIpNetEntry2
  • libdnet使用传统API以保证兼容性

IPv6支持:

  • 传统IP Helper API仅支持IPv4
  • IPv6 ARP(NDP)需要使用 GetIpNetTable2等新API
  • libdnet ARP模块不支持IPv6

错误处理:

  1. /* Windows API返回值处理 */
  2. if(GetIpNetTable(...)!= NO_ERROR){
  3. /* 设置errno和last error */
  4.     errno = ENXIO;
  5. SetLastError(GetLastError());
  6. return(-1);
  7. }

6. 跨平台对比分析

6.1 API设计对比

功能
Linux
macOS/BSD
Windows
无实现
打开句柄
socket(AFINET, SOCKDGRAM)
socket(PFROUTE, SOCKRAW)
calloc
返回NULL
添加ARP
ioctl(SIOCSARP)
RTM_ADD
CreateIpNetEntry
ENOSYS
删除ARP
ioctl(SIOCDARP)
RTM_DELETE
DeleteIpNetEntry
ENOSYS
获取ARP
ioctl(SIOCGARP)
RTM_GET
arp_loop遍历
ENOSYS
遍历ARP
/proc/net/arp
sysctl
GetIpNetTable
ENOSYS
关闭句柄
close(fd)
close(fd)
free
free

6.2 数据结构对比

Linux: arpreq

  1. struct arpreq {
  2. struct sockaddr arp_pa;/* 协议地址(IP) */
  3. struct sockaddr arp_ha;/* 硬件地址(MAC) */
  4. int arp_flags;/* 标志 */
  5. struct sockaddr arp_netmask;/* 网络掩码 */
  6. char arp_dev[16];/* 设备名称 */
  7. };

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[256];/* 地址数据 */
  10. };

Windows: MIB_IPNETROW

  1. typedefstruct _MIB_IPNETROW {
  2.     DWORD dwIndex;/* 接口索引 */
  3.     DWORD dwPhysAddrLen;/* MAC地址长度 */
  4.     BYTE  bPhysAddr[MAXLEN_PHYSADDR];
  5.     DWORD dwAddr;/* IP地址 */
  6.     DWORD dwType;/* 类型 */
  7. } MIB_IPNETROW;

6.3 操作方式对比

Linux: ioctl方式

  1. /* 优点 */
  2. -直接、简单
  3. -一次性操作
  4. -内核直接处理
  5. /* 缺点 */
  6. -需要socket句柄
  7. -设备名称处理复杂
  8. -不同发行版差异大

macOS/BSD: 路由消息方式

  1. /* 优点 */
  2. -统一的路由管理接口
  3. -支持链路层地址
  4. -消息机制灵活
  5. - sysctl遍历性能高
  6. /* 缺点 */
  7. -实现复杂
  8. -需要处理序列号
  9. -两步操作(先GETADD)
  10. -消息格式复杂

Windows: IP Helper API

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

6.4 性能对比

操作
Linux
macOS/BSD
Windows
单条获取
快(ioctl)
中(消息交换)
慢(全表扫描)
单条添加
快(ioctl)
中(消息交换)
快(API)
全表遍历
快(proc文件)
最快(sysctl)
中(API)
批量操作
需多次ioctl
需多次消息
需多次API

详细分析:

Linux遍历性能:

  1. /* /proc/net/arp解析 */
  2. -文件I/O:
  3. -文本解析:中等
  4. -系统调用:少(fopen/fgets/fclose)
  5. -适用场景:小到中等ARP

macOS/BSD遍历性能:

  1. /* sysctl方式 */
  2. -系统调用:1
  3. -内存拷贝:1
  4. -数据格式:二进制,无需解析
  5. -适用场景:所有规模,最佳性能

Windows遍历性能:

  1. /* GetIpNetTable API */
  2. -系统调用:1-2次(缓冲区调整)
  3. -数据格式:二进制
  4. -缓冲区管理:复杂
  5. -适用场景:中小ARP

6.5 权限要求对比

操作
Linux
macOS/BSD
Windows
打开句柄
普通用户
普通用户
普通用户
读取ARP
普通用户
普通用户
普通用户
添加ARP
root/CAPNETADMIN
root
管理员
删除ARP
root/CAPNETADMIN
root
管理员
遍历ARP
普通用户
普通用户
普通用户

6.6 错误处理对比

Linux: errno

  1. /* 常见错误 */
  2. ESRCH    -条目不存在
  3. EEXIST   -条目已存在
  4. EPERM    -权限不足
  5. EINVAL   -参数错误
  6. ENODEV   -设备不存在

macOS/BSD: errno

  1. /* 常见错误 */
  2. ESRCH          -条目不存在
  3. EADDRNOTAVAIL  -地址不可用
  4. EADDRINUSE     -地址已使用
  5. EAFNOSUPPORT   -地址族不支持
  6. EPERM          -权限不足

Windows: GetLastError + errno

  1. /* 常见错误 */
  2. ERROR_NO_DATA      -数据不存在
  3. ERROR_ACCESS_DENIED -访问被拒绝
  4. ERROR_INVALID_PARAMETER -参数无效
  5. ERROR_NOT_SUPPORTED   -不支持的操作
  6. /* libdnet映射 */
  7. errno = ENXIO  -> ERROR_NO_DATA
  8. errno = EPERM  -> ERROR_ACCESS_DENIED

6.7 平台特性对比

特性
Linux
macOS/BSD
Windows
IPv4支持
IPv6支持
❌(需新API)
静态ARP
动态ARP
代理ARP
ARP超时
接口指定
✅(设备名)
✅(索引)
✅(索引)
批量操作
原子操作

6.8 代码复杂度对比

文件
行数
复杂度
主要难点
arp-ioctl.c
490
多平台变体、proc文件解析
arp-bsd.c
324
路由消息、序列号匹配
arp-win32.c
144
API简单、缓冲区管理

6.9 维护性分析

Linux (arp-ioctl.c):

  1. /* 维护难点 */
  2. -3种遍历方式(proc/streams/mib)
  3. -多种Unix变体(Solaris/HP-UX/IRIX)
  4. -设备名称处理复杂
  5. -需要持续跟进内核变化
  6. /* 优点 */
  7. - ioctl机制稳定
  8. -/proc接口文档完善

macOS/BSD (arp-bsd.c):

  1. /* 维护难点 */
  2. -路由消息格式变化
  3. -不同BSD版本差异
  4. - sockaddr_dl结构变化
  5. - sysctl MIB参数变化
  6. /* 优点 */
  7. -代码统一
  8. -逻辑清晰

Windows (arp-win32.c):

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

7. 实际应用示例

7.1 基本用法

添加静态ARP条目:

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. int main(void)
  4. {
  5. arp_t*arp;
  6. struct arp_entry entry;
  7. /* 打开ARP句柄 */
  8. if((arp = arp_open())== NULL){
  9.         perror("arp_open");
  10. return1;
  11. }
  12. /* 设置ARP条目 */
  13.     addr_pton("192.168.1.100",&entry.arp_pa);/* IP地址 */
  14.     addr_pton("00:11:22:33:44:55",&entry.arp_ha);/* MAC地址 */
  15. /* 添加到ARP表 */
  16. if(arp_add(arp,&entry)<0){
  17.         perror("arp_add");
  18.         arp_close(arp);
  19. return1;
  20. }
  21.     printf("ARP entry added successfully\n");
  22. /* 关闭句柄 */
  23.     arp_close(arp);
  24. return0;
  25. }

删除ARP条目:

  1. int main(void)
  2. {
  3. arp_t*arp;
  4. struct arp_entry entry;
  5. if((arp = arp_open())== NULL){
  6.         perror("arp_open");
  7. return1;
  8. }
  9. /* 只需要IP地址 */
  10.     addr_pton("192.168.1.100",&entry.arp_pa);
  11. /* 删除ARP条目 */
  12. if(arp_delete(arp,&entry)<0){
  13.         perror("arp_delete");
  14.         arp_close(arp);
  15. return1;
  16. }
  17.     printf("ARP entry deleted\n");
  18.     arp_close(arp);
  19. return0;
  20. }

7.2 ARP扫描器

局域网主机发现:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<unistd.h>
  4. #include<dnet.h>
  5. /* 回调函数: 打印每个ARP条目 */
  6. staticint print_arp_entry(conststruct arp_entry *entry,void*arg)
  7. {
  8.     printf("IP: %-15s  MAC: %s\n",
  9.            addr_ntoa(&entry->arp_pa),
  10.            addr_ntoa(&entry->arp_ha));
  11. return0;
  12. }
  13. int main(void)
  14. {
  15. arp_t*arp;
  16. /* 打开ARP句柄 */
  17. if((arp = arp_open())== NULL){
  18.         perror("arp_open");
  19. return1;
  20. }
  21.     printf("ARP Cache:\n");
  22.     printf("=========================================\n");
  23. /* 遍历并打印所有ARP条目 */
  24. if(arp_loop(arp, print_arp_entry, NULL)<0){
  25.         perror("arp_loop");
  26.         arp_close(arp);
  27. return1;
  28. }
  29.     printf("=========================================\n");
  30. /* 关闭句柄 */
  31.     arp_close(arp);
  32. return0;
  33. }

7.3 ARP请求发送

发送原始ARP请求:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. #include<dnet.h>
  5. int main(int argc,char*argv[])
  6. {
  7. eth_t*eth;
  8. arp_t*arp;
  9. intf_t*intf;
  10. struct intf_entry if_entry;
  11. struct addr my_ip, my_mac, target_ip;
  12.     u_char pkt[ETH_MTU];
  13. struct arp_hdr *arp_hdr;
  14. struct arp_ethip *arp_ethip;
  15. if(argc !=2){
  16.         fprintf(stderr,"Usage: %s <target_ip>\n", argv[0]);
  17. return1;
  18. }
  19. /* 解析目标IP */
  20. if(addr_pton(argv[1],&target_ip)<0){
  21.         fprintf(stderr,"Invalid IP address\n");
  22. return1;
  23. }
  24. /* 获取默认网络接口信息 */
  25. if((intf = intf_open())== NULL){
  26.         perror("intf_open");
  27. return1;
  28. }
  29.     if_entry.intf_len =sizeof(if_entry);
  30. if(intf_get(intf,&if_entry)<0){
  31.         perror("intf_get");
  32.         intf_close(intf);
  33. return1;
  34. }
  35.     intf_close(intf);
  36.     my_ip = if_entry.intf_addr;/* 本机IP */
  37.     my_mac = if_entry.intf_link_addr;/* 本机MAC */
  38. /* 打开Ethernet设备 */
  39. if((eth = eth_open(if_entry.intf_name))== NULL){
  40.         perror("eth_open");
  41. return1;
  42. }
  43. /* 打开ARP句柄(用于封装) */
  44. if((arp = arp_open())== NULL){
  45.         perror("arp_open");
  46.         eth_close(eth);
  47. return1;
  48. }
  49. /* 构造ARP请求包 */
  50.     memset(pkt,0,sizeof(pkt));
  51.     arp_pack_hdr_ethip(pkt,
  52.         ARP_OP_REQUEST,/* 请求 */
  53.         my_mac.addr_eth,/* 发送方MAC */
  54.         my_ip.addr_ip,/* 发送方IP */
  55.         ETH_ADDR_BROADCAST,/* 目标MAC(广播) */
  56.         target_ip.addr_ip);/* 目标IP */
  57. /* 发送ARP请求 */
  58. if(eth_send(eth, pkt, ARP_HDR_LEN + ARP_ETHIP_LEN)<0){
  59.         perror("eth_send");
  60. }else{
  61.         printf("ARP request sent to %s\n", argv[1]);
  62. }
  63. /* 清理资源 */
  64.     arp_close(arp);
  65.     eth_close(eth);
  66. return0;
  67. }

7.4 ARP监控工具

实时监控ARP变化:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<time.h>
  4. #include<dnet.h>
  5. /* ARP条目缓存 */
  6. #define MAX_ENTRIES 1024
  7. staticstruct arp_entry cache[MAX_ENTRIES];
  8. staticint cache_count =0;
  9. /* 查找条目 */
  10. staticint find_entry(conststruct addr *pa)
  11. {
  12. for(int i =0; i < cache_count; i++){
  13. if(addr_cmp(pa,&cache[i].arp_pa)==0)
  14. return i;
  15. }
  16. return-1;
  17. }
  18. /* 添加条目到缓存 */
  19. staticvoid add_entry(conststruct arp_entry *entry)
  20. {
  21. if(cache_count < MAX_ENTRIES){
  22.         cache[cache_count]=*entry;
  23.         cache_count++;
  24. }
  25. }
  26. /* 回调函数: 检测变化 */
  27. staticint detect_changes(conststruct arp_entry *entry,void*arg)
  28. {
  29. int idx = find_entry(&entry->arp_pa);
  30. time_t now = time(NULL);
  31. if(idx <0){
  32. /* 新条目 */
  33.         printf("[%s] NEW: %s -> %s\n",
  34.                ctime(&now),
  35.                addr_ntoa(&entry->arp_pa),
  36.                addr_ntoa(&entry->arp_ha));
  37.         add_entry(entry);
  38. }elseif(addr_cmp(&entry->arp_ha,&cache[idx].arp_ha)!=0){
  39. /* MAC地址变化 */
  40.         printf("[%s] CHANGE: %s: %s -> %s\n",
  41.                ctime(&now),
  42.                addr_ntoa(&entry->arp_pa),
  43.                addr_ntoa(&cache[idx].arp_ha),
  44.                addr_ntoa(&entry->arp_ha));
  45.         cache[idx]=*entry;
  46. }
  47. return0;
  48. }
  49. int main(void)
  50. {
  51. arp_t*arp;
  52. /* 打开ARP句柄 */
  53. if((arp = arp_open())== NULL){
  54.         perror("arp_open");
  55. return1;
  56. }
  57.     printf("ARP Monitor (Ctrl+C to exit)\n");
  58. /* 定期检查 */
  59. while(1){
  60.         arp_loop(arp, detect_changes, NULL);
  61.         sleep(2);/* 2秒间隔 */
  62. }
  63.     arp_close(arp);
  64. return0;
  65. }

7.5 使用dnet命令行工具

查看ARP表:

  1. # 显示所有ARP条目
  2. ./test/dnet/dnet arp show
  3. 192.168.1.1 at 00:11:22:33:44:55
  4. 192.168.1.254 at aa:bb:cc:dd:ee:ff

添加ARP条目:

  1. # 添加静态ARP条目
  2. $ sudo ./test/dnet/dnet arp add 192.168.1.10000:11:22:33:44:55
  3. 192.168.1.100 added

删除ARP条目:

  1. # 删除ARP条目
  2. $ sudo ./test/dnet/dnet arp delete 192.168.1.100
  3. 192.168.1.100 deleted

查询特定IP的MAC:

  1. # 查询ARP条目
  2. ./test/dnet/dnet arp get 192.168.1.1
  3. 192.168.1.1 at 00:11:22:33:44:55

7.6 ARP欺骗检测

简单的ARP欺骗检测:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<dnet.h>
  4. /* 已知网关的MAC地址 */
  5. staticstruct addr gateway_mac;
  6. /* 初始化 */
  7. staticint init(constchar*gateway_ip,constchar*gateway_mac_str)
  8. {
  9. if(addr_pton(gateway_ip,&gateway_mac)<0){
  10.         fprintf(stderr,"Invalid gateway IP\n");
  11. return-1;
  12. }
  13. if(addr_pton(gateway_mac_str,&gateway_mac)<0){
  14.         fprintf(stderr,"Invalid gateway MAC\n");
  15. return-1;
  16. }
  17. return0;
  18. }
  19. /* 检测回调 */
  20. staticint detect_arp_spoof(conststruct arp_entry *entry,void*arg)
  21. {
  22. if(addr_cmp(&entry->arp_pa,&gateway_mac)==0){
  23. /* 找到网关IP */
  24. if(addr_cmp(&entry->arp_ha,&gateway_mac)!=0){
  25. /* MAC地址不匹配,可能是ARP欺骗 */
  26.             printf("[ALERT] ARP Spoofing detected!\n");
  27.             printf("  Gateway IP: %s\n", addr_ntoa(&entry->arp_pa));
  28.             printf("  Expected MAC: %s\n", addr_ntoa(&gateway_mac));
  29.             printf("  Actual MAC: %s\n", addr_ntoa(&entry->arp_ha));
  30. return1;/* 停止扫描 */
  31. }
  32. }
  33. return0;
  34. }
  35. int main(int argc,char*argv[])
  36. {
  37. arp_t*arp;
  38. if(argc !=3){
  39.         fprintf(stderr,"Usage: %s <gateway_ip> <gateway_mac>\n", argv[0]);
  40. return1;
  41. }
  42. /* 初始化网关信息 */
  43. if(init(argv[1], argv[2])<0)
  44. return1;
  45. /* 打开ARP句柄 */
  46. if((arp = arp_open())== NULL){
  47.         perror("arp_open");
  48. return1;
  49. }
  50.     printf("Checking for ARP spoofing...\n");
  51. /* 检测ARP欺骗 */
  52. if(arp_loop(arp, detect_arp_spoof, NULL)<0){
  53.         perror("arp_loop");
  54. }else{
  55.         printf("No ARP spoofing detected\n");
  56. }
  57.     arp_close(arp);
  58. return0;
  59. }

8. 常见问题与解决方案

8.1 权限问题

问题:添加/删除ARP条目失败,errno=EPERM

原因:

  • Linux: 需要root权限或CAPNETADMIN能力
  • macOS/BSD: 需要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(arp_add(arp,&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("arp_add");
  7. return-1;
  8. }

8.2 设备未找到

问题:Linux平台添加ARP失败,errno=ENODEV

原因:

  • 无法确定使用哪个网络接口
  • IP地址不在任何接口的子网内

解决方案:

  1. /* 手动指定接口名称(需要修改arpreq) */
  2. struct arpreq ar;
  3. memset(&ar,0,sizeof(ar));
  4. addr_ntos(&entry->arp_pa,&ar.arp_pa);
  5. addr_ntos(&entry->arp_ha,&ar.arp_ha);
  6. ar.arp_flags = ATF_PERM | ATF_COM;
  7. strncpy(ar.arp_dev,"eth0",sizeof(ar.arp_dev));/* 指定接口 */
  8. if(ioctl(fd, SIOCSARP,&ar)<0){
  9.     perror("ioctl(SIOCSARP)");
  10. return-1;
  11. }

8.3 条目不存在

问题:arp_get失败,errno=ESRCH

原因:

  • ARP条目不存在
  • 条目未完成(ATF_COM标志未设置)

解决方案:

  1. /* 先尝试ping触发ARP解析 */
  2. system("ping -c 1 192.168.1.100 > /dev/null 2>&1");
  3. /* 等待ARP解析完成 */
  4. sleep(1);
  5. /* 再次尝试获取 */
  6. if(arp_get(arp,&entry)<0&& errno == ESRCH){
  7.     fprintf(stderr,"ARP entry not found or incomplete\n");
  8. return-1;
  9. }

8.4 macOS接口索引问题

问题:macOS平台添加ARP失败,errno=EADDRNOTAVAIL

原因:

  • 无法确定接口索引
  • IP地址不在路由表中

解决方案:

  1. /* 手动指定接口 */
  2. intf_t*intf;
  3. struct intf_entry if_entry;
  4. if((intf = intf_open())== NULL){
  5.     perror("intf_open");
  6. return-1;
  7. }
  8. /* 获取指定接口信息 */
  9. strncpy(if_entry.intf_name,"en0",sizeof(if_entry.intf_name));
  10. if_entry.intf_len =sizeof(if_entry);
  11. if(intf_get(intf,&if_entry)<0){
  12.     perror("intf_get");
  13.     intf_close(intf);
  14. return-1;
  15. }
  16. intf_close(intf);
  17. /* 使用接口索引... */

8.5 Windows API错误

问题:Windows平台操作失败,GetLastError返回错误码

常见错误码:

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

调试代码:

  1. #include<stdio.h>
  2. #include<windows.h>
  3. void print_last_error(constchar*operation)
  4. {
  5.     DWORD error =GetLastError();
  6. if(error != NO_ERROR){
  7.         LPSTR msg = NULL;
  8. FormatMessageA(
  9.             FORMAT_MESSAGE_ALLOCATE_BUFFER |
  10.             FORMAT_MESSAGE_FROM_SYSTEM |
  11.             FORMAT_MESSAGE_IGNORE_INSERTS,
  12.             NULL, error,0,(LPSTR)&msg,0, NULL);
  13.         fprintf(stderr,"%s failed: %s (Error %lu)\n",
  14.                 operation, msg, error);
  15. LocalFree(msg);
  16. }
  17. }
  18. /* 使用 */
  19. if(CreateIpNetEntry(&iprow)!= NO_ERROR){
  20.     print_last_error("CreateIpNetEntry");
  21. return-1;
  22. }

8.6 ARP条目不持久

问题:重启后添加的ARP条目丢失

原因:

  • 操作系统默认不保存静态ARP条目

解决方案:

Linux (/etc/ethers):

  1. # 编辑/etc/ethers文件
  2. 192.168.1.10000:11:22:33:44:55
  3. # 配置启动脚本
  4. sudo bash -'echo "arp -f /etc/ethers" >> /etc/rc.local'

Windows (arp -s):

  1. REM 添加持久ARP条目
  2. arp -192.168.1.10000-11-22-33-44-55
  3. REM 或使用netsh
  4. netsh interface ip add neighbors "Ethernet""192.168.1.100""00-11-22-33-44-55"

8.7 性能问题

问题:arp_loop遍历慢

原因:

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

解决方案:

使用缓存:

  1. /* 缓存ARP表 */
  2. struct arp_cache {
  3. struct arp_entry *entries;
  4. int count;
  5. time_t timestamp;
  6. time_t ttl;/* 缓存生存时间 */
  7. };
  8. staticstruct arp_cache cache ={NULL,0,0,60};/* 60秒缓存 */
  9. /* 带缓存的遍历 */
  10. int arp_loop_cached(arp_t*arp, arp_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. if(cache.entries != NULL)
  24.         free(cache.entries);
  25. /* 第一次调用获取数量 */
  26. struct arp_entry tmp[1];
  27. int count =0;
  28.     arp_loop(arp, count_callback,&count);
  29. /* 分配缓存 */
  30.     cache.entries = malloc(sizeof(struct arp_entry)* count);
  31.     cache.count =0;
  32. /* 填充缓存 */
  33. struct loop_data data ={cache.entries,&cache.count};
  34.     arp_loop(arp, fill_cache_callback,&data);
  35.     cache.timestamp = now;
  36. /* 使用新缓存 */
  37. for(int i =0; i < cache.count; i++){
  38. if(callback(&cache.entries[i], arg)!=0)
  39. break;
  40. }
  41. return0;
  42. }

8.8 线程安全问题

问题:多线程同时操作ARP表导致竞争条件

解决方案:

  1. #include<pthread.h>
  2. staticpthread_mutex_t arp_mutex = PTHREAD_MUTEX_INITIALIZER;
  3. /* 线程安全的arp_add */
  4. int arp_add_threadsafe(arp_t*arp,conststruct arp_entry *entry)
  5. {
  6. int ret;
  7.     pthread_mutex_lock(&arp_mutex);
  8.     ret = arp_add(arp, entry);
  9.     pthread_mutex_unlock(&arp_mutex);
  10. return ret;
  11. }
  12. /* 线程安全的arp_get */
  13. int arp_get_threadsafe(arp_t*arp,struct arp_entry *entry)
  14. {
  15. int ret;
  16.     pthread_mutex_lock(&arp_mutex);
  17.     ret = arp_get(arp, entry);
  18.     pthread_mutex_unlock(&arp_mutex);
  19. return ret;
  20. }

8.9 IPv6支持

问题:libdnet ARP模块不支持IPv6

原因:

  • IPv6使用NDP(Neighbor Discovery Protocol)
  • 不使用ARP协议

解决方案:

Windows:

  1. /* 使用新版IP Helper API */
  2. #include<iphlpapi.h>
  3. DWORD GetIpNetTable2(
  4.     ADDRESS_FAMILY Family,/* AF_INET6 for IPv6 */
  5.     PMIB_IPNET_TABLE2 *Table/* 返回IPv6邻居表 */
  6. );

Linux:

  1. /* 读取/proc/net/ndp */
  2. FILE*fp = fopen("/proc/net/ndp","r");
  3. // 解析IPv6邻居发现表

macOS/BSD:

  1. /* 使用sysctl获取IPv6邻居表 */
  2. int mib[6]={CTL_NET, PF_ROUTE,0, AF_INET6, NET_RT_FLAGS, RTF_LLINFO};
  3. sysctl(mib,6,...);

8.10 调试技巧

启用详细日志:

  1. #define ARP_DEBUG
  2. #ifdef ARP_DEBUG
  3. #define arp_log(fmt,...) \
  4.     fprintf(stderr,"[ARP] " fmt "\n",##__VA_ARGS__)
  5. #else
  6. #define arp_log(fmt,...)do{}while(0)
  7. #endif
  8. /* 使用 */
  9. arp_log("Adding entry: IP=%s, MAC=%s",
  10.         addr_ntoa(&entry->arp_pa),
  11.         addr_ntoa(&entry->arp_ha));

查看系统ARP表:

  1. # Linux/macOS
  2. arp -an
  3. # Linux: 查看详细信息
  4. ip neigh show
  5. # Windows
  6. arp -a
  7. # 查看/proc/net/arp(Linux)
  8. cat /proc/net/arp

抓包分析:

  1. # 使用tcpdump抓ARP包
  2. sudo tcpdump -i eth0 -nn arp
  3. # 使用Wireshark
  4. # 过滤器: arp

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

Linux ioctl命令

命令
描述
参数
SIOCGARP
获取ARP条目
struct arpreq *
SIOCSARP
设置ARP条目
struct arpreq *
SIOCDARP
删除ARP条目
struct arpreq *

BSD路由消息类型

类型
描述
RTM_ADD
0x1
添加路由
RTM_DELETE
0x2
删除路由
RTM_CHANGE
0x3
修改路由
RTM_GET
0x4
获取路由

Windows IP Helper API

API
描述
GetIpNetTable
获取ARP表
CreateIpNetEntry
创建ARP条目
DeleteIpNetEntry
删除ARP条目
GetBestRoute
获取最佳路由

附录B: 参考资料

RFC文档

  • RFC 826: An Ethernet Address Resolution Protocol
  • RFC 903: A Reverse Address Resolution Protocol

系统文档

  • Linux: man7arp
  • FreeBSD: man4arp
  • macOS: man4route

相关工具

  • Linux: arpip neightcpdump
  • macOS: arpndptcpdump
  • Windows: arpnetshWireshark

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

  • 公众号:安全狗的自我修养

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx

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

猜你喜欢

  • 暂无文章