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

ARP操作源码深入分析
目录
- ARP协议概述
- libdnet ARP接口设计
- Linux平台实现
- macOS/BSD平台实现
- Windows平台实现
- 跨平台对比分析
- 实际应用示例
- 常见问题与解决方案
1. ARP协议概述
1.1 ARP协议简介
ARP(Address Resolution Protocol)是网络层和数据链路层之间的核心协议,用于将IP地址解析为MAC地址。RFC 826定义了ARP协议标准。
1.2 ARP消息格式
/* 文件: include/dnet/arp.h */#define ARP_HDR_LEN 8/* 基础ARP头部长度 */#define ARP_ETHIP_LEN 20/* Ethernet/IP ARP消息长度 *//* ARP头部结构 */struct arp_hdr {uint16_t ar_hrd;/* 硬件地址格式 */uint16_t ar_pro;/* 协议地址格式 */uint8_t ar_hln;/* 硬件地址长度 (ETH_ADDR_LEN) */uint8_t ar_pln;/* 协议地址长度 (IP_ADDR_LEN) */uint16_t ar_op;/* 操作类型 */} __attribute__((__packed__));/* Ethernet/IP ARP消息 */struct arp_ethip {eth_addr_t ar_sha;/* 发送方硬件地址 */ip_addr_t ar_spa;/* 发送方协议地址 */eth_addr_t ar_tha;/* 目标硬件地址 */ip_addr_t ar_tpa;/* 目标协议地址 */} __attribute__((__packed__));
字段说明:
ar_hrd: 硬件类型,Ethernet为0x0001ar_pro: 协议类型,IPv4为0x0800ar_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缓存条目
/* ARP缓存条目 */struct arp_entry {struct addr arp_pa;/* 协议地址(IP地址) */struct addr arp_ha;/* 硬件地址(MAC地址) */};
2. libdnet ARP接口设计
2.1 核心API接口
/* 文件: include/dnet/arp.h */typedefstruct arp_handle arp_t;typedefint(*arp_handler)(conststruct arp_entry * entry,void*arg);/* ARP句柄操作 */arp_t*arp_open(void);// 打开ARP句柄arp_t*arp_close(arp_t*arp);// 关闭ARP句柄/* ARP缓存操作 */int arp_add(arp_t*arp,conststruct arp_entry *entry);// 添加ARP条目int arp_delete(arp_t*arp,conststruct arp_entry *entry);// 删除ARP条目int arp_get(arp_t*arp,struct arp_entry *entry);// 获取ARP条目int arp_loop(arp_t*arp, arp_handler callback,void*arg);// 遍历ARP缓存
2.2 ARP消息打包宏
/* 打包Ethernet/IP ARP消息的便捷宏 */#define arp_pack_hdr_ethip(hdr, op, sha, spa, tha, tpa)do{ \struct arp_hdr *pack_arp_p =(struct arp_hdr *)(hdr); \struct arp_ethip *pack_ethip_p =(struct arp_ethip *)(pack_arp_p +1); \pack_arp_p->ar_hrd = htons(ARP_HRD_ETH); \pack_arp_p->ar_pro = htons(ARP_PRO_IP); \pack_arp_p->ar_hln = ETH_ADDR_LEN; \pack_arp_p->ar_pln = IP_ADDR_LEN; \pack_arp_p->ar_op = htons(op); \memcpy(&pack_ethip_p->ar_sha,&(sha), ETH_ADDR_LEN); \memcpy(&pack_ethip_p->ar_spa,&(spa), IP_ADDR_LEN); \memcpy(&pack_ethip_p->ar_tha,&(tha), ETH_ADDR_LEN); \memcpy(&pack_ethip_p->ar_tpa,&(tpa), IP_ADDR_LEN); \}while(0)
2.3 平台适配机制
libdnet通过编译时检测自动选择对应的平台实现:
configure.ac中的选择逻辑:
/* 文件: configure.ac 第219-228行 */dnl Checkfor arp interface.if test "$ac_cv_header_iphlpapi_h"= yes ; thenAC_LIBOBJ([arp-win32])/* Windows平台 */elif test "$ac_cv_dnet_ioctl_arp"= yes ; thenAC_LIBOBJ([arp-ioctl])/* Linux/Unix ioctl方式 */elif test "$ac_cv_dnet_route_h_has_rt_msghdr"= yes ; thenAC_LIBOBJ([arp-bsd])/* BSD/macOS route socket方式 */elseAC_LIBOBJ([arp-none])/* 不支持的平台 */fi
3. Linux平台实现
3.1 实现架构
Linux平台使用 ioctl系统调用操作ARP缓存,核心文件为 src/arp-ioctl.c。
平台检测:
/* 文件: m4/acinclude.m4 第201-221行 */AC_DEFUN([AC_DNET_IOCTL_ARP],[AC_MSG_CHECKING(for arp(7) ioctls)AC_CACHE_VAL(ac_cv_dnet_ioctl_arp,AC_EGREP_CPP(werd,[#include<sys/types.h>#define BSD_COMP#include<sys/ioctl.h>#ifdef SIOCGARP /* 检查ARP相关ioctl定义 */werd#endif],ac_cv_dnet_ioctl_arp=yes,ac_cv_dnet_ioctl_arp=no))case"$host_os" inirix*)ac_cv_dnet_ioctl_arp=no ;;/* IRIX不支持 */esac])
3.2 句柄管理
ARP句柄结构:
打开ARP句柄:
/* 文件: src/arp-ioctl.c 第54-74行 */arp_t*arp_open(void){arp_t*a;if((a = calloc(1,sizeof(*a)))!= NULL){#ifdef HAVE_STREAMS_MIB2/* Solaris STREAMS方式 */if((a->fd = open(IP_DEV_NAME, O_RDWR))<0)#elif defined(HAVE_STREAMS_ROUTE)/* Solaris/IRIX STREAMS路由设备 */if((a->fd = open("/dev/route", O_WRONLY,0))<0)#else/* 标准Linux: 创建AF_INET socket用于ioctl */if((a->fd = socket(AF_INET, SOCK_DGRAM,0))<0)#endifreturn(arp_close(a));#ifdef HAVE_ARPREQ_ARP_DEV/* 需要指定网络设备的平台 */if((a->intf = intf_open())== NULL)return(arp_close(a));#endif}return(a);}
关键技术点:
- 使用
AF_INET+SOCK_DGRAM创建socket,而不是原始socket - 该socket仅用于
ioctl操作,不进行实际网络通信 - 某些Linux发行版需要指定网络接口名称
3.3 添加ARP条目
实现源码:
/* 文件: src/arp-ioctl.c 第100-163行 */intarp_add(arp_t*a,conststruct arp_entry *entry){struct arpreq ar;memset(&ar,0,sizeof(ar));/* 设置协议地址(IP) */if(addr_ntos(&entry->arp_pa,&ar.arp_pa)<0)return(-1);/* 设置硬件地址(MAC) - 平台差异处理 */#ifdef __linux__/* Linux特定: 设置sa_family为ARP_HRD_ETH */if(addr_ntos(&entry->arp_ha,&ar.arp_ha)<0)return(-1);ar.arp_ha.sa_family = ARP_HRD_ETH;#else/* Solaris, HP-UX, IRIX等其他Unix系统 */ar.arp_ha.sa_family = AF_UNSPEC;memcpy(ar.arp_ha.sa_data,&entry->arp_ha.addr_eth, ETH_ADDR_LEN);#endif/* 指定网络接口 */#ifdef HAVE_ARPREQ_ARP_DEVif(intf_loop(a->intf, _arp_set_dev,&ar)!=1){errno = ESRCH;return(-1);}#endif/* 设置ARP标志: 永久且完成 */ar.arp_flags = ATF_PERM | ATF_COM;#ifdef hpux/* HP-UX特殊处理: 扩展arpreq结构 */{struct sockaddr_in *sin;ar.arp_hw_addr_len = ETH_ADDR_LEN;sin =(struct sockaddr_in *)&ar.arp_pa_mask;sin->sin_family = AF_INET;sin->sin_addr.s_addr = IP_ADDR_BROADCAST;}#endif/* 执行ioctl添加ARP条目 */if(ioctl(a->fd, SIOCSARP,&ar)<0)return(-1);#ifdef HAVE_STREAMS_MIB2/* Solaris MIB2特殊处理: 强制进入ipNetToMediaTable */{struct sockaddr_in sin;int fd;addr_ntos(&entry->arp_pa,(struct sockaddr *)&sin);sin.sin_port = htons(666);/* 魔术端口触发ARP查询 */if((fd = socket(AF_INET, SOCK_DGRAM,0))<0)return(-1);if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0){close(fd);return(-1);}write(fd, NULL,0);/* 触发ARP解析 */close(fd);}#endifreturn(0);}
网络设备查找辅助函数:
/* 文件: src/arp-ioctl.c 第76-98行 */staticint_arp_set_dev(conststruct intf_entry *entry,void*arg){struct arpreq *ar =(struct arpreq *)arg;struct addr dst;uint32_t mask;/* 只处理Ethernet接口 */if(entry->intf_type == INTF_TYPE_ETH &&entry->intf_addr.addr_type == ADDR_TYPE_IP){/* 计算子网掩码 */addr_btom(entry->intf_addr.addr_bits,&mask, IP_ADDR_LEN);addr_ston((struct sockaddr *)&ar->arp_pa,&dst);/* 检查目标IP是否在该接口的子网内 */if((entry->intf_addr.addr_ip & mask)==(dst.addr_ip & mask)){/* 设置设备名称 */strlcpy(ar->arp_dev, entry->intf_name,sizeof(ar->arp_dev));return(1);/* 找到匹配的接口 */}}return(0);}
ioctl命令详解:
SIOCSARP(Set ARP): 添加或修改ARP条目SIOCDARP(Delete ARP): 删除ARP条目SIOCGARP(Get ARP): 获取ARP条目
ARP标志位:
3.4 删除ARP条目
实现源码:
/* 文件: src/arp-ioctl.c 第165-179行 */intarp_delete(arp_t*a,conststruct arp_entry *entry){struct arpreq ar;memset(&ar,0,sizeof(ar));/* 只需要指定IP地址 */if(addr_ntos(&entry->arp_pa,&ar.arp_pa)<0)return(-1);/* 执行ioctl删除 */if(ioctl(a->fd, SIOCDARP,&ar)<0)return(-1);return(0);}
3.5 获取ARP条目
实现源码:
/* 文件: src/arp-ioctl.c 第181-205行 */intarp_get(arp_t*a,struct arp_entry *entry){struct arpreq ar;memset(&ar,0,sizeof(ar));/* 设置要查询的IP地址 */if(addr_ntos(&entry->arp_pa,&ar.arp_pa)<0)return(-1);/* 指定网络接口 */#ifdef HAVE_ARPREQ_ARP_DEVif(intf_loop(a->intf, _arp_set_dev,&ar)!=1){errno = ESRCH;return(-1);}#endif/* 执行ioctl查询 */if(ioctl(a->fd, SIOCGARP,&ar)<0)return(-1);/* 检查条目是否完成 */if((ar.arp_flags & ATF_COM)==0){errno = ESRCH;return(-1);}/* 提取硬件地址 */return(addr_ston(&ar.arp_ha,&entry->arp_ha));}
3.6 遍历ARP缓存
Linux提供三种遍历ARP缓存的方式:
方式1: /proc/net/arp 文件解析(推荐)
/* 文件: src/arp-ioctl.c 第207-240行 */#ifdef HAVE_LINUX_PROCFSintarp_loop(arp_t*a, arp_handler callback,void*arg){FILE*fp;struct arp_entry entry;char buf[BUFSIZ], ipbuf[100], macbuf[100], maskbuf[100], devbuf[100];int i, type, flags, ret;/* 打开/proc/net/arp文件 */if((fp = fopen(PROC_ARP_FILE,"r"))== NULL)return(-1);ret =0;while(fgets(buf,sizeof(buf), fp)!= NULL){/* 解析ARP条目行* 格式: IP地址 HW类型 标志 MAC地址 掩码 设备* 示例: 192.168.1.1 0x1 0x2 00:11:22:33:44:55 * eth0*/i = sscanf(buf,"%s 0x%x 0x%x %99s %99s %99s\n",ipbuf,&type,&flags, macbuf, maskbuf, devbuf);if(i <4||(flags & ATF_COM)==0)continue;/* 跳过未完成的条目 *//* 转换地址并调用回调 */if(addr_aton(ipbuf,&entry.arp_pa)==0&&addr_aton(macbuf,&entry.arp_ha)==0){if((ret = callback(&entry, arg))!=0)break;}}if(ferror(fp)){fclose(fp);return(-1);}fclose(fp);return(ret);}#endif
/proc/net/arp文件格式示例:
IP address HW type Flags HW address MaskDevice192.168.1.10x10x200:11:22:33:44:55* eth0192.168.1.2540x10x6 aa:bb:cc:dd:ee:ff * eth0
Flags字段说明:
0x0: 未完成0x2: 完成0x4: 永久0x6: 永久且完成0x8: 代理ARP
方式2: STREAMS MIB2(Solaris/Irix)
/* 文件: src/arp-ioctl.c 第241-337行 */#elif defined (HAVE_STREAMS_MIB2)intarp_loop(arp_t*r, arp_handler callback,void*arg){struct arp_entry entry;struct strbuf msg;struct T_optmgmt_req *tor;struct T_optmgmt_ack *toa;struct T_error_ack *tea;struct opthdr *opt;mib2_ipNetToMediaEntry_t*arp,*arpend;u_char buf[8192];int flags, rc, atable, ret;tor =(struct T_optmgmt_req *)buf;toa =(struct T_optmgmt_ack *)buf;tea =(struct T_error_ack *)buf;/* 构建MIB2查询请求 */tor->PRIM_type = T_OPTMGMT_REQ;tor->OPT_offset =sizeof(*tor);tor->OPT_length =sizeof(*opt);tor->MGMT_flags = T_CURRENT;opt =(struct opthdr *)(tor +1);opt->level = MIB2_IP;opt->name = opt->len =0;msg.maxlen =sizeof(buf);msg.len =sizeof(*tor)+sizeof(*opt);msg.buf = buf;/* 发送查询请求 */if(putmsg(r->fd,&msg, NULL,0)<0)return(-1);opt =(struct opthdr *)(toa +1);msg.maxlen =sizeof(buf);/* 循环读取响应 */for(;;){flags =0;if((rc = getmsg(r->fd,&msg, NULL,&flags))<0)return(-1);/* 检查是否完成 */if(rc ==0&&msg.len >=sizeof(*toa)&&toa->PRIM_type == T_OPTMGMT_ACK &&toa->MGMT_flags == T_SUCCESS && opt->len ==0)break;if(msg.len >=sizeof(*tea)&& tea->PRIM_type == T_ERROR_ACK)return(-1);if(rc != MOREDATA || msg.len <(int)sizeof(*toa)||toa->PRIM_type != T_OPTMGMT_ACK ||toa->MGMT_flags != T_SUCCESS)return(-1);/* 检查是否是ARP表 */atable =(opt->level == MIB2_IP && opt->name == MIB2_IP_22);msg.maxlen =sizeof(buf)-(sizeof(buf)%sizeof(*arp));msg.len =0;flags =0;do{rc = getmsg(r->fd, NULL,&msg,&flags);if(rc !=0&& rc != MOREDATA)return(-1);if(!atable)continue;/* 解析ARP条目 */arp =(mib2_ipNetToMediaEntry_t*)msg.buf;arpend =(mib2_ipNetToMediaEntry_t*)(msg.buf + msg.len);entry.arp_pa.addr_type = ADDR_TYPE_IP;entry.arp_pa.addr_bits = IP_ADDR_BITS;entry.arp_ha.addr_type = ADDR_TYPE_ETH;entry.arp_ha.addr_bits = ETH_ADDR_BITS;for(; arp < arpend; arp++){entry.arp_pa.addr_ip =arp->ipNetToMediaNetAddress;memcpy(&entry.arp_ha.addr_eth,arp->ipNetToMediaPhysAddress.o_bytes,ETH_ADDR_LEN);if((ret = callback(&entry, arg))!=0)return(ret);}}while(rc == MOREDATA);}return(0);}#endif
方式3: 内核内存读取(HP-UX/Tru64)
/* 文件: src/arp-ioctl.c 第338-385行 */#elif defined(HAVE_SYS_MIB_H)#define MAX_ARPENTRIES 512/* 最大条目数 */intarp_loop(arp_t*r, arp_handler callback,void*arg){struct nmparms nm;struct arp_entry entry;mib_ipNetToMediaEnt arpentries[MAX_ARPENTRIES];int fd, i, n, ret;/* 打开MIB设备 */if((fd = open_mib("/dev/ip", O_RDWR,0,0))<0)return(-1);/* 查询ARP表 */nm.objid = ID_ipNetToMediaTable;nm.buffer = arpentries;n =sizeof(arpentries);nm.len =&n;if(get_mib_info(fd,&nm)<0){close_mib(fd);return(-1);}close_mib(fd);entry.arp_pa.addr_type = ADDR_TYPE_IP;entry.arp_pa.addr_bits = IP_ADDR_BITS;entry.arp_ha.addr_type = ADDR_TYPE_ETH;entry.arp_ha.addr_bits = ETH_ADDR_BITS;n /=sizeof(*arpentries);ret =0;for(i =0; i < n; i++){/* 跳过无效条目 */if(arpentries[i].Type== INTM_INVALID ||arpentries[i].PhysAddr.o_length != ETH_ADDR_LEN)continue;entry.arp_pa.addr_ip = arpentries[i].NetAddr;memcpy(&entry.arp_ha.addr_eth,arpentries[i].PhysAddr.o_bytes, ETH_ADDR_LEN);if((ret = callback(&entry, arg))!=0)break;}return(ret);}#endif
3.7 Linux平台特定问题
权限要求:
- 添加/删除ARP条目需要
CAP_NET_ADMIN能力(root或sudo) - 读取ARP缓存普通用户即可
内核版本差异:
IPv6支持:
- libdnet的ARP模块仅支持IPv4
- IPv6使用NDP(Neighbor Discovery Protocol),不在ARP模块处理
4. macOS/BSD平台实现
4.1 实现架构
macOS和BSD系列系统使用路由套接字(Route Socket)管理ARP缓存,核心文件为 src/arp-bsd.c。
平台选择逻辑:
/* configure.ac 第224-225行 */elif test "$ac_cv_dnet_route_h_has_rt_msghdr"= yes ; thenAC_LIBOBJ([arp-bsd])
4.2 路由套接字机制
BSD系统通过 PF_ROUTE套接字接收和发送路由消息,包括ARP表项变更。
句柄结构:
/* 文件: src/arp-bsd.c 第38-46行 */struct arp_handle {int fd;/* 路由套接字文件描述符 */int seq;/* 序列号,用于匹配请求和响应 */};/* ARP消息结构 */struct arpmsg {struct rt_msghdr rtm;/* 路由消息头 */u_char addrs[256];/* 地址数据 */};
打开句柄:
/* 文件: src/arp-bsd.c 第48-62行 */arp_t*arp_open(void){arp_t*arp;if((arp = calloc(1,sizeof(*arp)))!= NULL){#ifdef HAVE_STREAMS_ROUTE/* STREAMS路由设备(Solaris/Irix) */if((arp->fd = open("/dev/route", O_RDWR,0))<0)#else/* 标准BSD/macOS: 创建路由套接字 */if((arp->fd = socket(PF_ROUTE, SOCK_RAW,0))<0)#endifreturn(arp_close(arp));}return(arp);}
4.3 路由消息通信
核心消息处理函数:
/* 文件: src/arp-bsd.c 第64-107行 */staticintarp_msg(arp_t*arp,struct arpmsg *msg){struct arpmsg smsg;int len, i =0;pid_t pid;/* 设置消息版本和序列号 */msg->rtm.rtm_version = RTM_VERSION;msg->rtm.rtm_seq =++arp->seq;memcpy(&smsg, msg,sizeof(smsg));#ifdef HAVE_STREAMS_ROUTE/* STREAMS方式: 使用ioctl */return(ioctl(arp->fd, RTSTR_SEND,&msg->rtm));#else/* 标准BSD/macOS: 使用write/read *//* 发送路由消息 */if(write(arp->fd,&smsg, smsg.rtm.rtm_msglen)<0){/* 处理特殊情况: 删除不存在的条目 */if(errno != ESRCH || msg->rtm.rtm_type != RTM_DELETE)return(-1);}pid = getpid();/* 读取响应消息 */while((len = read(arp->fd, msg,sizeof(*msg)))>0){if(len <(int)sizeof(msg->rtm))return(-1);/* 检查是否是我们的消息 */if(msg->rtm.rtm_pid == pid){if(msg->rtm.rtm_seq == arp->seq)break;/* 找到匹配的响应 */continue;}elseif((i++%2)==0)continue;/* 重复请求 */if(write(arp->fd,&smsg, smsg.rtm.rtm_msglen)<0){if(errno != ESRCH || msg->rtm.rtm_type != RTM_DELETE)return(-1);}}if(len <0)return(-1);return(0);#endif}
路由消息类型:
地址标志位:
路由标志位:
4.4 添加ARP条目
实现源码:
/* 文件: src/arp-bsd.c 第109-173行 */intarp_add(arp_t*arp,conststruct arp_entry *entry){struct arpmsg msg;struct sockaddr_in *sin;struct sockaddr *sa;int index, type;/* 验证地址类型 */if(entry->arp_pa.addr_type != ADDR_TYPE_IP ||entry->arp_ha.addr_type != ADDR_TYPE_ETH){errno = EAFNOSUPPORT;return(-1);}sin =(struct sockaddr_in *)msg.addrs;sa =(struct sockaddr *)(sin +1);/* 首先查询现有路由 */if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0)return(-1);memset(&msg.rtm,0,sizeof(msg.rtm));msg.rtm.rtm_type = RTM_GET;msg.rtm.rtm_addrs = RTA_DST;msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin);if(arp_msg(arp,&msg)<0)return(-1);/* 验证响应 */if(msg.rtm.rtm_msglen <(int)sizeof(msg.rtm)+sizeof(*sin)+sizeof(*sa)){errno = EADDRNOTAVAIL;return(-1);}/* 检查是否是ARP条目而非网关 */if(sin->sin_addr.s_addr == entry->arp_pa.addr_ip){if((msg.rtm.rtm_flags & RTF_LLINFO)==0||(msg.rtm.rtm_flags & RTF_GATEWAY)!=0){errno = EADDRINUSE;return(-1);}}/* 保存接口信息 */if(sa->sa_family != AF_LINK){errno = EADDRNOTAVAIL;return(-1);}else{index =((struct sockaddr_dl *)sa)->sdl_index;/* 接口索引 */type =((struct sockaddr_dl *)sa)->sdl_type;/* 接口类型 */}/* 构建添加消息 */if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0||addr_ntos(&entry->arp_ha, sa)<0)return(-1);/* 恢复接口信息 */((struct sockaddr_dl *)sa)->sdl_index = index;((struct sockaddr_dl *)sa)->sdl_type = type;/* 设置消息参数 */memset(&msg.rtm,0,sizeof(msg.rtm));msg.rtm.rtm_type = RTM_ADD;msg.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY;msg.rtm.rtm_inits = RTV_EXPIRE;/* 设置过期时间 */msg.rtm.rtm_flags = RTF_HOST | RTF_STATIC;/* 主机路由、静态 */#ifdef HAVE_SOCKADDR_SA_LENmsg.rtm.rtm_msglen =sizeof(msg.rtm)+ sin->sin_len + sa->sa_len;#elsemsg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin)+sizeof(*sa);#endifreturn(arp_msg(arp,&msg));}
关键点解析:
- 两步操作:
- 先用
RTM_GET查询接口信息 - 再用
RTM_ADD添加条目
- 地址结构:
RTA_DST: 目标IP地址RTA_GATEWAY: 实际存储MAC地址(通过sockaddr_dl)
- 接口索引:
- BSD系统使用接口索引而非名称
- 通过
sdl_index字段指定接口
- 过期时间:
RTV_EXPIRE标志表示设置过期时间RTF_STATIC标志创建静态条目
4.5 删除ARP条目
实现源码:
/* 文件: src/arp-bsd.c 第175-219行 */intarp_delete(arp_t*arp,conststruct arp_entry *entry){struct arpmsg msg;struct sockaddr_in *sin;struct sockaddr *sa;/* 验证地址类型 */if(entry->arp_pa.addr_type != ADDR_TYPE_IP){errno = EAFNOSUPPORT;return(-1);}sin =(struct sockaddr_in *)msg.addrs;sa =(struct sockaddr *)(sin +1);/* 查询现有路由 */if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0)return(-1);memset(&msg.rtm,0,sizeof(msg.rtm));msg.rtm.rtm_type = RTM_GET;msg.rtm.rtm_addrs = RTA_DST;msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin);if(arp_msg(arp,&msg)<0)return(-1);/* 验证响应 */if(msg.rtm.rtm_msglen <(int)sizeof(msg.rtm)+sizeof(*sin)+sizeof(*sa)){errno = ESRCH;return(-1);}/* 检查是否是ARP条目 */if(sin->sin_addr.s_addr == entry->arp_pa.addr_ip){if((msg.rtm.rtm_flags & RTF_LLINFO)==0||(msg.rtm.rtm_flags & RTF_GATEWAY)!=0){errno = EADDRINUSE;return(-1);}}/* 验证地址族 */if(sa->sa_family != AF_LINK){errno = ESRCH;return(-1);}/* 发送删除请求 */msg.rtm.rtm_type = RTM_DELETE;return(arp_msg(arp,&msg));}
4.6 获取ARP条目
实现源码:
/* 文件: src/arp-bsd.c 第221-258行 */intarp_get(arp_t*arp,struct arp_entry *entry){struct arpmsg msg;struct sockaddr_in *sin;struct sockaddr *sa;/* 验证地址类型 */if(entry->arp_pa.addr_type != ADDR_TYPE_IP){errno = EAFNOSUPPORT;return(-1);}sin =(struct sockaddr_in *)msg.addrs;sa =(struct sockaddr *)(sin +1);/* 设置查询参数 */if(addr_ntos(&entry->arp_pa,(struct sockaddr *)sin)<0)return(-1);memset(&msg.rtm,0,sizeof(msg.rtm));msg.rtm.rtm_type = RTM_GET;msg.rtm.rtm_addrs = RTA_DST;msg.rtm.rtm_flags = RTF_LLINFO;/* 只查询ARP条目 */msg.rtm.rtm_msglen =sizeof(msg.rtm)+sizeof(*sin);/* 发送查询 */if(arp_msg(arp,&msg)<0)return(-1);/* 验证响应 */if(msg.rtm.rtm_msglen <(int)sizeof(msg.rtm)+sizeof(*sin)+sizeof(*sa)||sin->sin_addr.s_addr != entry->arp_pa.addr_ip ||sa->sa_family != AF_LINK){errno = ESRCH;return(-1);}/* 提取MAC地址 */if(addr_ston(sa,&entry->arp_ha)<0)return(-1);return(0);}
4.7 遍历ARP缓存
sysctl方式遍历(macOS/BSD推荐):
/* 文件: src/arp-bsd.c 第260-304行 */#ifdef HAVE_SYS_SYSCTL_Hintarp_loop(arp_t*arp, arp_handler callback,void*arg){struct arp_entry entry;struct rt_msghdr *rtm;struct sockaddr_in *sin;struct sockaddr *sa;char*buf,*lim,*next;size_t len;int ret, mib[6]={ CTL_NET, PF_ROUTE,0, AF_INET,NET_RT_FLAGS, RTF_LLINFO };/* 第一次调用: 获取所需缓冲区大小 */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;ret =0;/* 遍历所有路由消息 */for(next = buf; next < lim; next += rtm->rtm_msglen){rtm =(struct rt_msghdr *)next;sin =(struct sockaddr_in *)(rtm +1);sa =(struct sockaddr *)(sin +1);/* 解析IP和MAC地址 */if(addr_ston((struct sockaddr *)sin,&entry.arp_pa)<0||addr_ston(sa,&entry.arp_ha)<0)continue;/* 调用回调函数 */if((ret = callback(&entry, arg))!=0)break;}free(buf);return(ret);}#endif
sysctl MIB数组解析:
int mib[6]={CTL_NET,// 网络子系统PF_ROUTE,// 路由子系统0,// 协议族(0表示所有)AF_INET,// 地址族(IPv4)NET_RT_FLAGS,// 获取指定标志的路由RTF_LLINFO // 链路层信息(ARP)};
sysctl的优势:
- 不需要打开socket
- 原子操作,一次获取所有数据
- 性能高,无多次I/O开销
- 不需要root权限
4.8 macOS/BSD平台特定问题
sockaddr_dl结构:
/* 链路层地址结构 */struct sockaddr_dl {uint8_t sdl_len;/* 地址长度 */uint8_t sdl_family;/* AF_LINK */uint16_t sdl_index;/* 接口索引 */uint8_t sdl_type;/* 接口类型 */uint8_t sdl_nlen;/* 接口名称长度 */uint8_t sdl_alen;/* 链路层地址长度 */uint8_t sdl_slen;/* 选择器长度 */char sdl_data[12];/* 接口名称和地址 */};
MAC地址提取:
/* 从sockaddr_dl中提取MAC地址 */if(sa->sa_family == AF_LINK){struct sockaddr_dl *sdl =(struct sockaddr_dl *)sa;uint8_t*mac = LLADDR(sdl);/* 指向MAC地址的宏 */}
权限要求:
- 添加/删除ARP条目需要root权限
- 遍历ARP缓存普通用户即可
FreeBSD vs macOS差异:
- FreeBSD支持sockaddr_len字段
- macOS需要特殊处理某些标志位
- 不同版本的sysctl实现略有差异
5. Windows平台实现
5.1 实现架构
Windows平台使用IP Helper API(iphlpapi.dll)管理ARP缓存,核心文件为 src/arp-win32.c。
句柄结构:
/* 文件: src/arp-win32.c 第20-22行 */struct arp_handle {MIB_IPNETTABLE *iptable;/* ARP表缓存指针 */};
5.2 打开句柄
实现源码:
/* 文件: src/arp-win32.c 第24-28行 */arp_t*arp_open(void){/* 简单分配内存,不打开任何系统资源 */return(calloc(1,sizeof(arp_t)));}
特点:
- Windows实现不需要持久句柄
- 所有操作都直接调用IP Helper API
- 延迟加载ARP表
5.3 添加ARP条目
实现源码:
/* 文件: src/arp-win32.c 第30-50行 */intarp_add(arp_t*arp,conststruct arp_entry *entry){MIB_IPFORWARDROW ipfrow;/* 路由条目 */MIB_IPNETROW iprow;/* ARP条目 *//* 查找最佳路由以确定接口索引 */if(GetBestRoute(entry->arp_pa.addr_ip,IP_ADDR_ANY,&ipfrow)!= NO_ERROR)return(-1);/* 填充ARP条目 */iprow.dwIndex = ipfrow.dwForwardIfIndex;/* 接口索引 */iprow.dwPhysAddrLen = ETH_ADDR_LEN;/* MAC地址长度 */memcpy(iprow.bPhysAddr,&entry->arp_ha.addr_eth, ETH_ADDR_LEN);iprow.dwAddr = entry->arp_pa.addr_ip;/* IP地址 */iprow.dwType =4;/* XXX - static 静态条目 *//* 创建ARP条目 */if(CreateIpNetEntry(&iprow)!= NO_ERROR)return(-1);return(0);}
IP Helper API详解:
- GetBestRoute:
- 查找到目标IP的最佳路由
- 返回路由信息,包括接口索引
- 原型:
DWORDGetBestRoute(DWORD dwDestAddr,DWORD dwSourceAddr,PMIB_IPFORWARDROW pBestRoute);
- CreateIpNetEntry:
- 创建ARP表条目
- 原型:
DWORDCreateIpNetEntry(PMIB_IPNETROW pArpEntry);
- MIB_IPNETROW结构:
typedefstruct _MIB_IPNETROW {DWORD dwIndex;/* 接口索引 */DWORD dwPhysAddrLen;/* 物理地址长度 */BYTE bPhysAddr[MAXLEN_PHYSADDR];/* 物理地址 */DWORD dwAddr;/* IP地址 */DWORD dwType;/* 类型: 1=其他, 2=无效, 3=动态, 4=静态 */} MIB_IPNETROW,*PMIB_IPNETROW;
ARP条目类型:
1: 其他2: 无效3: 动态(ARP协议学习)4: 静态(手动添加)
5.4 删除ARP条目
实现源码:
/* 文件: src/arp-win32.c 第52-71行 */intarp_delete(arp_t*arp,conststruct arp_entry *entry){MIB_IPFORWARDROW ipfrow;MIB_IPNETROW iprow;/* 查找最佳路由以确定接口索引 */if(GetBestRoute(entry->arp_pa.addr_ip,IP_ADDR_ANY,&ipfrow)!= NO_ERROR)return(-1);/* 填充删除参数 */memset(&iprow,0,sizeof(iprow));iprow.dwIndex = ipfrow.dwForwardIfIndex;iprow.dwAddr = entry->arp_pa.addr_ip;/* 删除ARP条目 */if(DeleteIpNetEntry(&iprow)!= NO_ERROR){errno = ENXIO;return(-1);}return(0);}
DeleteIpNetEntry API:
- 原型:
DWORDDeleteIpNetEntry(PMIB_IPNETROW pArpEntry); - 只需要指定接口索引和IP地址
- 不需要MAC地址
5.5 获取ARP条目
实现源码:
/* 文件: src/arp-win32.c 第73-94行 */staticint_arp_get_entry(conststruct arp_entry *entry,void*arg){struct arp_entry *e =(struct arp_entry *)arg;/* 比较IP地址 */if(addr_cmp(&entry->arp_pa,&e->arp_pa)==0){/* 找到匹配项,复制MAC地址 */memcpy(&e->arp_ha,&entry->arp_ha,sizeof(e->arp_ha));return(1);/* 停止遍历 */}return(0);}intarp_get(arp_t*arp,struct arp_entry *entry){/* 遍历ARP表查找匹配项 */if(arp_loop(arp, _arp_get_entry, entry)!=1){errno = ENXIO;SetLastError(ERROR_NO_DATA);return(-1);}return(0);}
实现策略:
- 使用
arp_loop遍历整个ARP表 - 使用回调函数查找匹配的IP地址
- 返回第一条匹配的MAC地址
5.6 遍历ARP缓存
实现源码:
/* 文件: src/arp-win32.c 第96-132行 */intarp_loop(arp_t*arp, arp_handler callback,void*arg){struct arp_entry entry;ULONG len;int i, ret;/* 循环获取ARP表,直到缓冲区足够大 */for(len =sizeof(arp->iptable[0]);;){if(arp->iptable)free(arp->iptable);arp->iptable = malloc(len);if(arp->iptable == NULL)return(-1);/* 获取ARP表 */ret =GetIpNetTable(arp->iptable,&len, FALSE);if(ret == NO_ERROR)break;/* 成功 */elseif(ret != ERROR_INSUFFICIENT_BUFFER)return(-1);/* 其他错误 *//* 缓冲区不足,继续循环 */}entry.arp_pa.addr_type = ADDR_TYPE_IP;entry.arp_pa.addr_bits = IP_ADDR_BITS;entry.arp_ha.addr_type = ADDR_TYPE_ETH;entry.arp_ha.addr_bits = ETH_ADDR_BITS;/* 遍历所有ARP条目 */for(i =0; i <(int)arp->iptable->dwNumEntries; i++){/* 跳过MAC地址长度不正确的条目 */if(arp->iptable->table[i].dwPhysAddrLen != ETH_ADDR_LEN)continue;entry.arp_pa.addr_ip = arp->iptable->table[i].dwAddr;memcpy(&entry.arp_ha.addr_eth,arp->iptable->table[i].bPhysAddr, ETH_ADDR_LEN);/* 调用回调函数 */if((ret =(*callback)(&entry, arg))!=0)return(ret);}return(0);}
GetIpNetTable API详解:
/* 原型 */DWORD GetIpNetTable(PMIB_IPNETTABLE pIpNetTable,// 接收ARP表PULONG pdwSize,// 缓冲区大小(输入/输出)BOOL bOrder // 是否排序);/* MIB_IPNETTABLE结构 */typedefstruct _MIB_IPNETTABLE {DWORD dwNumEntries;// 条目数量MIB_IPNETROW table[1];// 条目数组(变长)} MIB_IPNETTABLE,*PMIB_IPNETTABLE;
缓冲区处理策略:
- 从小缓冲区开始
- 调用
GetIpNetTable - 如果返回
ERROR_INSUFFICIENT_BUFFER,增大缓冲区 - 重复直到成功或错误
ARP条目过滤:
- 只处理
dwPhysAddrLen==6的条目 - 跳过无效或未完成的条目
5.7 关闭句柄
实现源码:
/* 文件: src/arp-win32.c 第134-143行 */arp_t*arp_close(arp_t*arp){if(arp != NULL){/* 释放缓存的ARP表 */if(arp->iptable != NULL)free(arp->iptable);free(arp);}return(NULL);}
5.8 Windows平台特定问题
DLL依赖:
- 需要链接
iphlpapi.lib - 运行时需要
iphlpapi.dll - Windows 2000及以上版本支持
权限要求:
- 添加/删除ARP条目需要管理员权限
- 读取ARP缓存普通用户即可
API版本:
- 传统API:
GetIpNetTable,CreateIpNetEntry,DeleteIpNetEntry - 新版API (Vista+):
GetIpNetTable2,CreateIpNetEntry2 - libdnet使用传统API以保证兼容性
IPv6支持:
- 传统IP Helper API仅支持IPv4
- IPv6 ARP(NDP)需要使用
GetIpNetTable2等新API - libdnet ARP模块不支持IPv6
错误处理:
/* Windows API返回值处理 */if(GetIpNetTable(...)!= NO_ERROR){/* 设置errno和last error */errno = ENXIO;SetLastError(GetLastError());return(-1);}
6. 跨平台对比分析
6.1 API设计对比
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6.2 数据结构对比
Linux: arpreq
struct arpreq {struct sockaddr arp_pa;/* 协议地址(IP) */struct sockaddr arp_ha;/* 硬件地址(MAC) */int arp_flags;/* 标志 */struct sockaddr arp_netmask;/* 网络掩码 */char arp_dev[16];/* 设备名称 */};
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[256];/* 地址数据 */};
Windows: MIB_IPNETROW
typedefstruct _MIB_IPNETROW {DWORD dwIndex;/* 接口索引 */DWORD dwPhysAddrLen;/* MAC地址长度 */BYTE bPhysAddr[MAXLEN_PHYSADDR];DWORD dwAddr;/* IP地址 */DWORD dwType;/* 类型 */} MIB_IPNETROW;
6.3 操作方式对比
Linux: ioctl方式
/* 优点 */-直接、简单-一次性操作-内核直接处理/* 缺点 */-需要socket句柄-设备名称处理复杂-不同发行版差异大
macOS/BSD: 路由消息方式
/* 优点 */-统一的路由管理接口-支持链路层地址-消息机制灵活- sysctl遍历性能高/* 缺点 */-实现复杂-需要处理序列号-两步操作(先GET再ADD)-消息格式复杂
Windows: IP Helper API
/* 优点 */-高层API,易用-不需要句柄-自动处理接口索引-缓冲区自动扩展/* 缺点 */-依赖Windows DLL-仅支持IPv4-遍历效率低(全表扫描)-需要管理员权限
6.4 性能对比
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
详细分析:
Linux遍历性能:
/* /proc/net/arp解析 */-文件I/O:快-文本解析:中等-系统调用:少(fopen/fgets/fclose)-适用场景:小到中等ARP表
macOS/BSD遍历性能:
/* sysctl方式 */-系统调用:1次-内存拷贝:1次-数据格式:二进制,无需解析-适用场景:所有规模,最佳性能
Windows遍历性能:
/* GetIpNetTable API */-系统调用:1-2次(缓冲区调整)-数据格式:二进制-缓冲区管理:复杂-适用场景:中小ARP表
6.5 权限要求对比
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6.6 错误处理对比
Linux: errno
/* 常见错误 */ESRCH -条目不存在EEXIST -条目已存在EPERM -权限不足EINVAL -参数错误ENODEV -设备不存在
macOS/BSD: errno
/* 常见错误 */ESRCH -条目不存在EADDRNOTAVAIL -地址不可用EADDRINUSE -地址已使用EAFNOSUPPORT -地址族不支持EPERM -权限不足
Windows: GetLastError + errno
/* 常见错误 */ERROR_NO_DATA -数据不存在ERROR_ACCESS_DENIED -访问被拒绝ERROR_INVALID_PARAMETER -参数无效ERROR_NOT_SUPPORTED -不支持的操作/* libdnet映射 */errno = ENXIO -> ERROR_NO_DATAerrno = EPERM -> ERROR_ACCESS_DENIED
6.7 平台特性对比
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6.8 代码复杂度对比
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6.9 维护性分析
Linux (arp-ioctl.c):
/* 维护难点 */-3种遍历方式(proc/streams/mib)-多种Unix变体(Solaris/HP-UX/IRIX)-设备名称处理复杂-需要持续跟进内核变化/* 优点 */- ioctl机制稳定-/proc接口文档完善
macOS/BSD (arp-bsd.c):
/* 维护难点 */-路由消息格式变化-不同BSD版本差异- sockaddr_dl结构变化- sysctl MIB参数变化/* 优点 */-代码统一-逻辑清晰
Windows (arp-win32.c):
/* 维护难点 */- API版本更新频繁-新版API不向后兼容-Windows版本差异/* 优点 */-代码简单-微软文档完善
7. 实际应用示例
7.1 基本用法
添加静态ARP条目:
#include<stdio.h>#include<dnet.h>int main(void){arp_t*arp;struct arp_entry entry;/* 打开ARP句柄 */if((arp = arp_open())== NULL){perror("arp_open");return1;}/* 设置ARP条目 */addr_pton("192.168.1.100",&entry.arp_pa);/* IP地址 */addr_pton("00:11:22:33:44:55",&entry.arp_ha);/* MAC地址 *//* 添加到ARP表 */if(arp_add(arp,&entry)<0){perror("arp_add");arp_close(arp);return1;}printf("ARP entry added successfully\n");/* 关闭句柄 */arp_close(arp);return0;}
删除ARP条目:
int main(void){arp_t*arp;struct arp_entry entry;if((arp = arp_open())== NULL){perror("arp_open");return1;}/* 只需要IP地址 */addr_pton("192.168.1.100",&entry.arp_pa);/* 删除ARP条目 */if(arp_delete(arp,&entry)<0){perror("arp_delete");arp_close(arp);return1;}printf("ARP entry deleted\n");arp_close(arp);return0;}
7.2 ARP扫描器
局域网主机发现:
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<dnet.h>/* 回调函数: 打印每个ARP条目 */staticint print_arp_entry(conststruct arp_entry *entry,void*arg){printf("IP: %-15s MAC: %s\n",addr_ntoa(&entry->arp_pa),addr_ntoa(&entry->arp_ha));return0;}int main(void){arp_t*arp;/* 打开ARP句柄 */if((arp = arp_open())== NULL){perror("arp_open");return1;}printf("ARP Cache:\n");printf("=========================================\n");/* 遍历并打印所有ARP条目 */if(arp_loop(arp, print_arp_entry, NULL)<0){perror("arp_loop");arp_close(arp);return1;}printf("=========================================\n");/* 关闭句柄 */arp_close(arp);return0;}
7.3 ARP请求发送
发送原始ARP请求:
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<dnet.h>int main(int argc,char*argv[]){eth_t*eth;arp_t*arp;intf_t*intf;struct intf_entry if_entry;struct addr my_ip, my_mac, target_ip;u_char pkt[ETH_MTU];struct arp_hdr *arp_hdr;struct arp_ethip *arp_ethip;if(argc !=2){fprintf(stderr,"Usage: %s <target_ip>\n", argv[0]);return1;}/* 解析目标IP */if(addr_pton(argv[1],&target_ip)<0){fprintf(stderr,"Invalid IP address\n");return1;}/* 获取默认网络接口信息 */if((intf = intf_open())== NULL){perror("intf_open");return1;}if_entry.intf_len =sizeof(if_entry);if(intf_get(intf,&if_entry)<0){perror("intf_get");intf_close(intf);return1;}intf_close(intf);my_ip = if_entry.intf_addr;/* 本机IP */my_mac = if_entry.intf_link_addr;/* 本机MAC *//* 打开Ethernet设备 */if((eth = eth_open(if_entry.intf_name))== NULL){perror("eth_open");return1;}/* 打开ARP句柄(用于封装) */if((arp = arp_open())== NULL){perror("arp_open");eth_close(eth);return1;}/* 构造ARP请求包 */memset(pkt,0,sizeof(pkt));arp_pack_hdr_ethip(pkt,ARP_OP_REQUEST,/* 请求 */my_mac.addr_eth,/* 发送方MAC */my_ip.addr_ip,/* 发送方IP */ETH_ADDR_BROADCAST,/* 目标MAC(广播) */target_ip.addr_ip);/* 目标IP *//* 发送ARP请求 */if(eth_send(eth, pkt, ARP_HDR_LEN + ARP_ETHIP_LEN)<0){perror("eth_send");}else{printf("ARP request sent to %s\n", argv[1]);}/* 清理资源 */arp_close(arp);eth_close(eth);return0;}
7.4 ARP监控工具
实时监控ARP变化:
#include<stdio.h>#include<stdlib.h>#include<time.h>#include<dnet.h>/* ARP条目缓存 */#define MAX_ENTRIES 1024staticstruct arp_entry cache[MAX_ENTRIES];staticint cache_count =0;/* 查找条目 */staticint find_entry(conststruct addr *pa){for(int i =0; i < cache_count; i++){if(addr_cmp(pa,&cache[i].arp_pa)==0)return i;}return-1;}/* 添加条目到缓存 */staticvoid add_entry(conststruct arp_entry *entry){if(cache_count < MAX_ENTRIES){cache[cache_count]=*entry;cache_count++;}}/* 回调函数: 检测变化 */staticint detect_changes(conststruct arp_entry *entry,void*arg){int idx = find_entry(&entry->arp_pa);time_t now = time(NULL);if(idx <0){/* 新条目 */printf("[%s] NEW: %s -> %s\n",ctime(&now),addr_ntoa(&entry->arp_pa),addr_ntoa(&entry->arp_ha));add_entry(entry);}elseif(addr_cmp(&entry->arp_ha,&cache[idx].arp_ha)!=0){/* MAC地址变化 */printf("[%s] CHANGE: %s: %s -> %s\n",ctime(&now),addr_ntoa(&entry->arp_pa),addr_ntoa(&cache[idx].arp_ha),addr_ntoa(&entry->arp_ha));cache[idx]=*entry;}return0;}int main(void){arp_t*arp;/* 打开ARP句柄 */if((arp = arp_open())== NULL){perror("arp_open");return1;}printf("ARP Monitor (Ctrl+C to exit)\n");/* 定期检查 */while(1){arp_loop(arp, detect_changes, NULL);sleep(2);/* 2秒间隔 */}arp_close(arp);return0;}
7.5 使用dnet命令行工具
查看ARP表:
# 显示所有ARP条目$ ./test/dnet/dnet arp show192.168.1.1 at 00:11:22:33:44:55192.168.1.254 at aa:bb:cc:dd:ee:ff
添加ARP条目:
# 添加静态ARP条目$ sudo ./test/dnet/dnet arp add 192.168.1.10000:11:22:33:44:55192.168.1.100 added
删除ARP条目:
# 删除ARP条目$ sudo ./test/dnet/dnet arp delete 192.168.1.100192.168.1.100 deleted
查询特定IP的MAC:
# 查询ARP条目$ ./test/dnet/dnet arp get 192.168.1.1192.168.1.1 at 00:11:22:33:44:55
7.6 ARP欺骗检测
简单的ARP欺骗检测:
#include<stdio.h>#include<stdlib.h>#include<dnet.h>/* 已知网关的MAC地址 */staticstruct addr gateway_mac;/* 初始化 */staticint init(constchar*gateway_ip,constchar*gateway_mac_str){if(addr_pton(gateway_ip,&gateway_mac)<0){fprintf(stderr,"Invalid gateway IP\n");return-1;}if(addr_pton(gateway_mac_str,&gateway_mac)<0){fprintf(stderr,"Invalid gateway MAC\n");return-1;}return0;}/* 检测回调 */staticint detect_arp_spoof(conststruct arp_entry *entry,void*arg){if(addr_cmp(&entry->arp_pa,&gateway_mac)==0){/* 找到网关IP */if(addr_cmp(&entry->arp_ha,&gateway_mac)!=0){/* MAC地址不匹配,可能是ARP欺骗 */printf("[ALERT] ARP Spoofing detected!\n");printf(" Gateway IP: %s\n", addr_ntoa(&entry->arp_pa));printf(" Expected MAC: %s\n", addr_ntoa(&gateway_mac));printf(" Actual MAC: %s\n", addr_ntoa(&entry->arp_ha));return1;/* 停止扫描 */}}return0;}int main(int argc,char*argv[]){arp_t*arp;if(argc !=3){fprintf(stderr,"Usage: %s <gateway_ip> <gateway_mac>\n", argv[0]);return1;}/* 初始化网关信息 */if(init(argv[1], argv[2])<0)return1;/* 打开ARP句柄 */if((arp = arp_open())== NULL){perror("arp_open");return1;}printf("Checking for ARP spoofing...\n");/* 检测ARP欺骗 */if(arp_loop(arp, detect_arp_spoof, NULL)<0){perror("arp_loop");}else{printf("No ARP spoofing detected\n");}arp_close(arp);return0;}
8. 常见问题与解决方案
8.1 权限问题
问题:添加/删除ARP条目失败,errno=EPERM
原因:
- Linux: 需要root权限或CAPNETADMIN能力
- macOS/BSD: 需要root权限
- Windows: 需要管理员权限
解决方案:
# Linux: 使用sudosudo ./your_program# 设置CAP_NET_ADMIN能力sudo setcap cap_net_admin+ep ./your_program# Windows: 以管理员身份运行# 右键 -> 以管理员身份运行
代码检查:
if(arp_add(arp,&entry)<0){if(errno == EPERM){fprintf(stderr,"Error: Need root/administrator privileges\n");fprintf(stderr,"Please run with sudo or as administrator\n");}perror("arp_add");return-1;}
8.2 设备未找到
问题:Linux平台添加ARP失败,errno=ENODEV
原因:
- 无法确定使用哪个网络接口
- IP地址不在任何接口的子网内
解决方案:
/* 手动指定接口名称(需要修改arpreq) */struct arpreq ar;memset(&ar,0,sizeof(ar));addr_ntos(&entry->arp_pa,&ar.arp_pa);addr_ntos(&entry->arp_ha,&ar.arp_ha);ar.arp_flags = ATF_PERM | ATF_COM;strncpy(ar.arp_dev,"eth0",sizeof(ar.arp_dev));/* 指定接口 */if(ioctl(fd, SIOCSARP,&ar)<0){perror("ioctl(SIOCSARP)");return-1;}
8.3 条目不存在
问题:arp_get失败,errno=ESRCH
原因:
- ARP条目不存在
- 条目未完成(ATF_COM标志未设置)
解决方案:
/* 先尝试ping触发ARP解析 */system("ping -c 1 192.168.1.100 > /dev/null 2>&1");/* 等待ARP解析完成 */sleep(1);/* 再次尝试获取 */if(arp_get(arp,&entry)<0&& errno == ESRCH){fprintf(stderr,"ARP entry not found or incomplete\n");return-1;}
8.4 macOS接口索引问题
问题:macOS平台添加ARP失败,errno=EADDRNOTAVAIL
原因:
- 无法确定接口索引
- IP地址不在路由表中
解决方案:
/* 手动指定接口 */intf_t*intf;struct intf_entry if_entry;if((intf = intf_open())== NULL){perror("intf_open");return-1;}/* 获取指定接口信息 */strncpy(if_entry.intf_name,"en0",sizeof(if_entry.intf_name));if_entry.intf_len =sizeof(if_entry);if(intf_get(intf,&if_entry)<0){perror("intf_get");intf_close(intf);return-1;}intf_close(intf);/* 使用接口索引... */
8.5 Windows API错误
问题:Windows平台操作失败,GetLastError返回错误码
常见错误码:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
调试代码:
#include<stdio.h>#include<windows.h>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(CreateIpNetEntry(&iprow)!= NO_ERROR){print_last_error("CreateIpNetEntry");return-1;}
8.6 ARP条目不持久
问题:重启后添加的ARP条目丢失
原因:
- 操作系统默认不保存静态ARP条目
解决方案:
Linux (/etc/ethers):
# 编辑/etc/ethers文件192.168.1.10000:11:22:33:44:55# 配置启动脚本sudo bash -c 'echo "arp -f /etc/ethers" >> /etc/rc.local'
Windows (arp -s):
REM 添加持久ARP条目arp -s 192.168.1.10000-11-22-33-44-55REM 或使用netshnetsh interface ip add neighbors "Ethernet""192.168.1.100""00-11-22-33-44-55"
8.7 性能问题
问题:arp_loop遍历慢
原因:
- Windows: 每次都全表扫描
- Linux: /proc文件解析效率低
解决方案:
使用缓存:
/* 缓存ARP表 */struct arp_cache {struct arp_entry *entries;int count;time_t timestamp;time_t ttl;/* 缓存生存时间 */};staticstruct arp_cache cache ={NULL,0,0,60};/* 60秒缓存 *//* 带缓存的遍历 */int arp_loop_cached(arp_t*arp, arp_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;}/* 重建缓存 */if(cache.entries != NULL)free(cache.entries);/* 第一次调用获取数量 */struct arp_entry tmp[1];int count =0;arp_loop(arp, count_callback,&count);/* 分配缓存 */cache.entries = malloc(sizeof(struct arp_entry)* count);cache.count =0;/* 填充缓存 */struct loop_data data ={cache.entries,&cache.count};arp_loop(arp, fill_cache_callback,&data);cache.timestamp = now;/* 使用新缓存 */for(int i =0; i < cache.count; i++){if(callback(&cache.entries[i], arg)!=0)break;}return0;}
8.8 线程安全问题
问题:多线程同时操作ARP表导致竞争条件
解决方案:
#include<pthread.h>staticpthread_mutex_t arp_mutex = PTHREAD_MUTEX_INITIALIZER;/* 线程安全的arp_add */int arp_add_threadsafe(arp_t*arp,conststruct arp_entry *entry){int ret;pthread_mutex_lock(&arp_mutex);ret = arp_add(arp, entry);pthread_mutex_unlock(&arp_mutex);return ret;}/* 线程安全的arp_get */int arp_get_threadsafe(arp_t*arp,struct arp_entry *entry){int ret;pthread_mutex_lock(&arp_mutex);ret = arp_get(arp, entry);pthread_mutex_unlock(&arp_mutex);return ret;}
8.9 IPv6支持
问题:libdnet ARP模块不支持IPv6
原因:
- IPv6使用NDP(Neighbor Discovery Protocol)
- 不使用ARP协议
解决方案:
Windows:
/* 使用新版IP Helper API */#include<iphlpapi.h>DWORD GetIpNetTable2(ADDRESS_FAMILY Family,/* AF_INET6 for IPv6 */PMIB_IPNET_TABLE2 *Table/* 返回IPv6邻居表 */);
Linux:
/* 读取/proc/net/ndp */FILE*fp = fopen("/proc/net/ndp","r");// 解析IPv6邻居发现表
macOS/BSD:
/* 使用sysctl获取IPv6邻居表 */int mib[6]={CTL_NET, PF_ROUTE,0, AF_INET6, NET_RT_FLAGS, RTF_LLINFO};sysctl(mib,6,...);
8.10 调试技巧
启用详细日志:
查看系统ARP表:
# Linux/macOSarp -an# Linux: 查看详细信息ip neigh show# Windowsarp -a# 查看/proc/net/arp(Linux)cat /proc/net/arp
抓包分析:
# 使用tcpdump抓ARP包sudo tcpdump -i eth0 -nn arp# 使用Wireshark# 过滤器: arp
附录A: 相关系统调用和API参考
Linux ioctl命令
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
BSD路由消息类型
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Windows IP Helper API
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
附录B: 参考资料
RFC文档
- RFC 826: An Ethernet Address Resolution Protocol
- RFC 903: A Reverse Address Resolution Protocol
系统文档
- Linux:
man7arp - FreeBSD:
man4arp - macOS:
man4route
相关工具
- Linux:
arp,ip neigh,tcpdump - macOS:
arp,ndp,tcpdump - Windows:
arp,netsh,Wireshark
文档版本: 1.0最后更新: 2026作者: libdnet源码分析适用版本: libdnet 1.13
-
公众号:安全狗的自我修养
-
vx:2207344074
-
http://gitee.com/haidragon
-
http://github.com/haidragon
-
bilibili:haidragonx
-


夜雨聆风