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

网络接口控制实现深入分析
目录
- 网络接口基础概念
- libdnet接口设计
- Unix/Linux平台实现
- macOS/BSD平台实现
- Windows平台实现
- 跨平台对比分析
- 实际应用示例
- 常见问题与解决方案
1. 网络接口基础概念
1.1 网络接口简介
网络接口(Network Interface)是操作系统与网络硬件之间的抽象层,负责:
- 数据链路层通信:处理MAC地址、以太网帧等
- IP地址管理:配置IPv4/IPv6地址
- 接口状态控制:启用/禁用接口、设置MTU
- 别名支持:一个接口配置多个IP地址
- 多播和广播支持:组播和广播功能
1.2 接口类型(MIB-II标准)
libdnet支持以下接口类型(基于IANA ifType MIB):
|
|
|
|
|---|---|---|
INTF_TYPE_OTHER |
|
|
INTF_TYPE_ETH |
|
|
INTF_TYPE_TOKENRING |
|
|
INTF_TYPE_FDDI |
|
|
INTF_TYPE_PPP |
|
|
INTF_TYPE_LOOPBACK |
|
|
INTF_TYPE_SLIP |
|
|
INTF_TYPE_TUN |
|
|
1.3 接口标志
接口标志用于描述接口的当前状态和能力:
|
|
|
|
|
|---|---|---|---|
INTF_FLAG_UP |
|
|
|
INTF_FLAG_LOOPBACK |
|
|
|
INTF_FLAG_POINTOPOINT |
|
|
|
INTF_FLAG_NOARP |
|
|
|
INTF_FLAG_BROADCAST |
|
|
|
INTF_FLAG_MULTICAST |
|
|
|
1.4 地址结构体
libdnet使用统一的 structaddr表示不同类型的地址:
// include/dnet/addr.h:18-30struct addr {uint16_t addr_type;// 地址类型:ADDR_TYPE_ETH/IP/IP6/NONEuint16_t addr_bits;// 地址前缀长度(网络掩码位数)union{eth_addr_t __eth;// 6字节MAC地址ip_addr_t __ip;// 4字节IPv4地址ip6_addr_t __ip6;// 16字节IPv6地址uint8_t __data8[16];uint16_t __data16[8];uint32_t __data32[4];} __addr_u;};
2. libdnet接口设计
2.1 接口条目结构
// include/dnet/intf.h:25-50struct intf_entry {uint32_t intf_len;// 条目总长度(包括别名)char intf_name[INTF_NAME_LEN];// 接口名称(eth0, lo等)char os_intf_name[...];// 操作系统原始接口名char pcap_intf_name[...];// libpcap使用的接口名char driver_name[...];// 驱动程序名称char driver_vers[...];// 驱动版本char firmware_vers[...];// 固件版本uint32_t intf_index;// 接口索引(只读)uint16_t intf_type;// 接口类型uint16_t intf_flags;// 接口标志uint32_t intf_mtu;// 最大传输单元struct addr intf_addr;// 主IP地址struct addr intf_dst_addr;// 点对点目标地址struct addr intf_link_addr;// 链路层地址(MAC)uint32_t intf_alias_num;// 别名地址数量struct addr intf_alias_addrs[];// 别名地址数组(柔性数组)};
2.2 核心API接口
// include/dnet/intf.h:79-87intf_t*intf_open(void);// 打开接口句柄int intf_get(intf_t*i,struct intf_entry *entry);// 获取接口信息int intf_get_index(intf_t*intf,struct intf_entry *entry,int af,unsignedint index);// 通过索引获取接口int intf_get_src(intf_t*i,struct intf_entry *entry,struct addr *src);// 根据源地址查找接口int intf_get_dst(intf_t*i,struct intf_entry *entry,struct addr *dst);// 根据目标地址查找接口int intf_set(intf_t*i,conststruct intf_entry *entry);// 设置接口配置int intf_loop(intf_t*i, intf_handler callback,void*arg);// 遍历所有接口intf_t*intf_close(intf_t*i);// 关闭接口句柄
2.3 回调函数类型
// include/dnet/intf.h:76typedefint(*intf_handler)(conststruct intf_entry *entry,void*arg);
2.4 平台适配机制
libdnet通过编译时宏选择不同实现:
- Unix/Linux:
src/intf.c - Windows:
src/intf-win32.c
配置系统在 configure.ac中检测平台特性:
# 检测IPv6支持AC_CHECK_DECL([SIOCGLIFCONF],...)# 检测proc文件系统AC_CHECK_FILE([/proc/net/dev],...)# 检测getkerninfo(AIX)AC_CHECK_FUNCS([getkerninfo],...)
3. Unix/Linux平台实现
3.1 实现文件
主文件: src/intf.c(适用于Linux、macOS/BSD、HP-UX、AIX、Solaris等)
3.2 接口句柄结构
3.3 打开接口句柄
// src/intf.c:148-173intf_t*intf_open(void){intf_t*intf;int one =1;if((intf = calloc(1,sizeof(*intf)))!= NULL){intf->fd = intf->fd6 =-1;// 创建IPv4 socket用于ioctl操作if((intf->fd = socket(AF_INET, SOCK_DGRAM,0))<0)return(intf_close(intf));// 允许广播setsockopt(intf->fd, SOL_SOCKET, SO_BROADCAST,(constchar*)&one,sizeof(one));// 创建IPv6 socket(如果支持)#if defined(SIOCGLIFCONF) || defined(SIOCGIFNETMASK_IN6) || defined(SIOCGIFNETMASK6)if((intf->fd6 = socket(AF_INET6, SOCK_DGRAM,0))<0){#ifdef EPROTONOSUPPORTif(errno != EPROTONOSUPPORT)#endifreturn(intf_close(intf));}#endif}return(intf);}
关键点:
- 使用
AF_INETsocket作为ioctl操作的句柄 - 设置
SO_BROADCAST选项以支持广播操作 - 可选创建IPv6 socket处理IPv6相关ioctl
3.4 ioctl接口操作
3.4.1 获取接口信息(无别名)
Linux平台使用标准ioctl系列:
// src/intf.c:489-598staticint _intf_get_noalias(intf_t*intf,struct intf_entry *entry){struct ifreq ifr;#ifdef HAVE_GETKERNINFOint size;struct kinfo_ndd *nddp;void*end;#endif// 1. 获取接口索引entry->intf_index = if_nametoindex(entry->intf_name);if(entry->intf_index ==0)return(-1);strlcpy(ifr.ifr_name, entry->intf_name,sizeof(ifr.ifr_name));// 2. 获取接口标志if(ioctl(intf->fd, SIOCGIFFLAGS,&ifr)<0)return(-1);entry->intf_flags = intf_iff_to_flags(ifr.ifr_flags);_intf_set_type(entry);// 3. 获取接口MTU#ifdef SIOCGIFMTUif(ioctl(intf->fd, SIOCGIFMTU,&ifr)<0)#endifreturn(-1);entry->intf_mtu = ifr.ifr_mtu;entry->intf_addr.addr_type = entry->intf_dst_addr.addr_type =entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;// 4. 获取主IP地址if(ioctl(intf->fd, SIOCGIFADDR,&ifr)==0){addr_ston(&ifr.ifr_addr,&entry->intf_addr);if(ioctl(intf->fd, SIOCGIFNETMASK,&ifr)<0)return(-1);addr_stob(&ifr.ifr_addr,&entry->intf_addr.addr_bits);}// 5. 获取其他地址if(entry->intf_type == INTF_TYPE_TUN){// 点对点目标地址if(ioctl(intf->fd, SIOCGIFDSTADDR,&ifr)==0){if(addr_ston(&ifr.ifr_addr,&entry->intf_dst_addr)<0)return(-1);}}elseif(entry->intf_type == INTF_TYPE_ETH){#if defined(HAVE_GETKERNINFO)// AIX:使用getkerninfo获取MAC地址size = getkerninfo(KINFO_NDD,0,0,0);if(size <=0)return-1;nddp =(struct kinfo_ndd *)malloc(size);if(!nddp)return-1;if(getkerninfo(KINFO_NDD, nddp,&size,0)<0){free(nddp);return-1;}end =(void*)nddp + size;while((void*)nddp < end){if(!strcmp(nddp->ndd_alias, entry->intf_name)||!strcmp(nddp->ndd_name, entry->intf_name)){addr_pack(&entry->intf_link_addr, ADDR_TYPE_ETH, ETH_ADDR_BITS,nddp->ndd_addr, ETH_ADDR_LEN);break;}elsenddp++;}free(nddp);#elif defined(SIOCGIFHWADDR)// Linux:标准方式if(ioctl(intf->fd, SIOCGIFHWADDR,&ifr)<0)return(-1);if(addr_ston(&ifr.ifr_addr,&entry->intf_link_addr)<0)return(-1);#elif defined(SIOCRPHYSADDR)// Tru64:特殊ioctlstruct ifdevea *ifd =(struct ifdevea *)𝔦if(ioctl(intf->fd, SIOCRPHYSADDR, ifd)<0)return(-1);addr_pack(&entry->intf_link_addr, ADDR_TYPE_ETH, ETH_ADDR_BITS,ifd->current_pa, ETH_ADDR_LEN);#else// 回退:使用eth模块eth_t*eth;if((eth = eth_open(entry->intf_name))!= NULL){if(!eth_get(eth,&entry->intf_link_addr.addr_eth)){entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;}eth_close(eth);}#endif}return(0);}
ioctl命令说明:
|
|
|
|
|---|---|---|
SIOCGIFFLAGS |
|
|
SIOCGIFMTU |
|
|
SIOCGIFADDR |
|
|
SIOCGIFNETMASK |
|
|
SIOCGIFDSTADDR |
|
|
SIOCGIFHWADDR |
|
|
SIOCRPHYSADDR |
|
|
3.4.2 获取别名地址
// src/intf.c:694-802staticint _intf_get_aliases(intf_t*intf,struct intf_entry *entry){struct ifreq *ifr,*lifr;struct ifreq tmpifr;struct addr *ap,*lap;char*p;if(intf->ifc.ifc_len <(int)sizeof(*ifr)){errno = EINVAL;return(-1);}entry->intf_alias_num =0;ap = entry->intf_alias_addrs;lifr =(struct ifreq *)intf->ifc.ifc_buf +(intf->ifc.ifc_len /sizeof(*lifr));lap =(struct addr *)((u_char *)entry + entry->intf_len);// 遍历所有ifreq条目for(ifr = intf->ifc.ifc_req; ifr < lifr &&(ap +1)< lap;ifr = NEXTIFR(ifr)){// Linux/Solaris:别名接口名格式为eth0:1if((p = strchr(ifr->ifr_name,':'))!= NULL)*p ='\0';if(strcmp(ifr->ifr_name, entry->intf_name)!=0){if(p)*p =':';continue;}// 恢复别名名称if(p)*p =':';if(addr_ston(&ifr.ifr_addr, ap)<0)continue;// 特殊处理if(ap->addr_type == ADDR_TYPE_ETH){memcpy(&entry->intf_link_addr, ap,sizeof(*ap));continue;}elseif(ap->addr_type == ADDR_TYPE_IP){// 跳过主地址和点对点地址if(ap->addr_ip == entry->intf_addr.addr_ip ||ap->addr_ip == entry->intf_dst_addr.addr_ip)continue;strlcpy(tmpifr.ifr_name, ifr->ifr_name,sizeof(tmpifr.ifr_name));if(ioctl(intf->fd, SIOCGIFNETMASK,&tmpifr)==0)addr_stob(&tmpifr.ifr_addr,&ap->addr_bits);}#ifdef SIOCGIFNETMASK_IN6elseif(ap->addr_type == ADDR_TYPE_IP6 && intf->fd6 !=-1){struct in6_ifreq ifr6;memcpy(&ifr6, ifr,sizeof(ifr6));if(ioctl(intf->fd6, SIOCGIFNETMASK_IN6,&ifr6)==0){addr_stob((struct sockaddr *)&ifr6.ifr_addr,&ap->addr_bits);}}#endifap++, entry->intf_alias_num++;}#ifdef HAVE_LINUX_PROCFS// Linux:从/proc/net/if_inet6读取IPv6地址#define PROC_INET6_FILE "/proc/net/if_inet6"{FILE*f;char buf[256], s[8][5], name[INTF_NAME_LEN];u_int idx, bits, scope, flags;if((f = fopen(PROC_INET6_FILE,"r"))!= NULL){while(ap < lap &&fgets(buf,sizeof(buf), f)!= NULL){sscanf(buf,"%04s%04s%04s%04s%04s%04s%04s%04s %x %02x %02x %02x %32s\n",s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],&idx,&bits,&scope,&flags, name);if(strcmp(name, entry->intf_name)==0){snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], bits);addr_aton(buf, ap);ap++, entry->intf_alias_num++;}}fclose(f);}}#endifentry->intf_len =(u_char *)ap -(u_char *)entry;return(0);}
别名处理:
- Linux:别名接口名为
eth0:1、eth0:2等形式 - IPv6:从
/proc/net/if_inet6读取 - NEXTIFR宏:处理sockaddr的变长对齐(BSD)
3.5 Linux特定实现:proc文件系统
Linux使用 /proc/net/dev遍历接口列表:
// src/intf.c:943-999#ifdef HAVE_LINUX_PROCFS#define PROC_DEV_FILE "/proc/net/dev"int intf_loop(intf_t*intf, intf_handler callback,void*arg){FILE*fp;struct intf_entry *entry;char*p, buf[BUFSIZ], ebuf[BUFSIZ];int ret;entry =(struct intf_entry *)ebuf;// 打开/proc/net/devif((fp = fopen(PROC_DEV_FILE,"r"))== NULL)return(-1);// 获取所有接口配置intf->ifc.ifc_buf =(caddr_t)intf->ifcbuf;intf->ifc.ifc_len =sizeof(intf->ifcbuf);if(ioctl(intf->fd, SIOCGIFCONF,&intf->ifc)<0){fclose(fp);return(-1);}ret =0;while(fgets(buf,sizeof(buf), fp)!= NULL){if((p = strchr(buf,':'))== NULL)continue;*p ='\0';for(p = buf;*p ==' '; p++);// 跳过前导空格memset(ebuf,0,sizeof(ebuf));strlcpy(entry->intf_name, p,sizeof(entry->intf_name));strlcpy(entry->os_intf_name, p,sizeof(entry->os_intf_name));strlcpy(entry->pcap_intf_name, p,sizeof(entry->pcap_intf_name));// 获取驱动信息(ethtool)intf_get_drv_info(intf, entry);entry->intf_len =sizeof(ebuf);// 获取接口详细信息if(_intf_get_noalias(intf, entry)<0){ret =-1;break;}// 获取别名地址if(_intf_get_aliases(intf, entry)<0){ret =-1;break;}// 调用回调函数if((ret =(*callback)(entry, arg))!=0)break;}if(ferror(fp))ret =-1;fclose(fp);return(ret);}#endif
/proc/net/dev格式示例:
Inter-|Receive|Transmitface |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressedeth0:123456787650000009876545432000000lo:45678923450000004567892345000000
3.6 Linux驱动信息获取(ethtool)
Linux使用ethtool接口获取驱动和固件信息:
// src/intf.c:903-941#if defined(__linux__)#include<linux/types.h>typedef __u64 u64;typedef __u32 u32;typedef __u16 u16;typedef __u8 u8;#include<linux/sockios.h>#include<linux/ethtool.h>staticint intf_get_drv_info(intf_t*intf,struct intf_entry *entry){struct ifreq ifr;struct ethtool_drvinfo drvinfo;memset(&ifr,0,sizeof(ifr));memset(&drvinfo,0,sizeof(drvinfo));drvinfo.cmd = ETHTOOL_GDRVINFO;ifr.ifr_data =(caddr_t)&drvinfo;if(strlen(entry->intf_name)> IFNAMSIZ +1){return-1;}strlcpy(ifr.ifr_name, entry->intf_name, IFNAMSIZ);if(ioctl(intf->fd, SIOCETHTOOL,&ifr)){return-1;}strncpy(entry->driver_name, drvinfo.driver, INTF_VERS_LEN);strncpy(entry->driver_vers, drvinfo.version, INTF_VERS_LEN);strncpy(entry->firmware_vers, drvinfo.fw_version, INTF_VERS_LEN);return0;}#elsestaticint intf_get_drv_info(intf_t*intf,struct intf_entry *entry){return-1;}#endif
3.7 设置接口配置
// src/intf.c:294-406int intf_set(intf_t*intf,conststruct intf_entry *entry){struct ifreq ifr;struct intf_entry *orig;struct addr bcast;u_char buf[BUFSIZ];// 1. 读取当前配置orig =(struct intf_entry *)buf;orig->intf_len =sizeof(buf);strcpy(orig->intf_name, entry->intf_name);if(intf_get(intf, orig)<0)return(-1);// 2. 删除现有别名if(_intf_delete_aliases(intf, orig)<0)return(-1);// 3. 删除现有地址if(_intf_delete_addrs(intf, orig)<0)return(-1);memset(&ifr,0,sizeof(ifr));strlcpy(ifr.ifr_name, entry->intf_name,sizeof(ifr.ifr_name));// 4. 设置MTUif(entry->intf_mtu !=0){ifr.ifr_mtu = entry->intf_mtu;#ifdef SIOCSIFMTUif(ioctl(intf->fd, SIOCSIFMTU,&ifr)<0)#endifreturn(-1);}// 5. 设置IP地址if(entry->intf_addr.addr_type == ADDR_TYPE_IP){#if defined(BSD) && !defined(__OPENBSD__)// BSD:必须先设置子网掩码if(addr_btos(entry->intf_addr.addr_bits,&ifr.ifr_addr)==0){if(ioctl(intf->fd, SIOCSIFNETMASK,&ifr)<0)return(-1);}#endifif(addr_ntos(&entry->intf_addr,&ifr.ifr_addr)<0)return(-1);if(ioctl(intf->fd, SIOCSIFADDR,&ifr)<0&& errno != EEXIST)return(-1);// 设置子网掩码if(addr_btos(entry->intf_addr.addr_bits,&ifr.ifr_addr)==0#ifdef __linux__&& entry->intf_addr.addr_ip !=0#endif){if(ioctl(intf->fd, SIOCSIFNETMASK,&ifr)<0)return(-1);}// 设置广播地址if(addr_bcast(&entry->intf_addr,&bcast)==0){if(addr_ntos(&bcast,&ifr.ifr_broadaddr)==0){ioctl(intf->fd, SIOCSIFBRDADDR,&ifr);}}}// 6. 设置MAC地址if(entry->intf_link_addr.addr_type == ADDR_TYPE_ETH &&addr_cmp(&entry->intf_link_addr,&orig->intf_link_addr)!=0){#if defined(SIOCSIFHWADDR)// Linux:标准方式if(addr_ntos(&entry->intf_link_addr,&ifr.ifr_hwaddr)<0)return(-1);if(ioctl(intf->fd, SIOCSIFHWADDR,&ifr)<0)return(-1);#elif defined (SIOCSIFLLADDR)// BSD:替代方式memcpy(ifr.ifr_addr.sa_data,&entry->intf_link_addr.addr_eth,ETH_ADDR_LEN);ifr.ifr_addr.sa_len = ETH_ADDR_LEN;if(ioctl(intf->fd, SIOCSIFLLADDR,&ifr)<0)return(-1);#else// 回退:使用eth模块eth_t*eth;if((eth = eth_open(entry->intf_name))== NULL)return(-1);if(eth_set(eth,&entry->intf_link_addr.addr_eth)<0){eth_close(eth);return(-1);}eth_close(eth);#endif}// 7. 设置点对点目标地址if(entry->intf_dst_addr.addr_type == ADDR_TYPE_IP){if(addr_ntos(&entry->intf_dst_addr,&ifr.ifr_dstaddr)<0)return(-1);if(ioctl(intf->fd, SIOCSIFDSTADDR,&ifr)<0&&errno != EEXIST)return(-1);}// 8. 添加别名if(_intf_add_aliases(intf, entry)<0)return(-1);// 9. 设置接口标志if(ioctl(intf->fd, SIOCGIFFLAGS,&ifr)<0)return(-1);ifr.ifr_flags = intf_flags_to_iff(entry->intf_flags, ifr.ifr_flags);if(ioctl(intf->fd, SIOCSIFFLAGS,&ifr)<0)return(-1);return(0);}
关键ioctl命令:
|
|
|
|---|---|
SIOCSIFMTU |
|
SIOCSIFADDR |
|
SIOCSIFNETMASK |
|
SIOCSIFBRDADDR |
|
SIOCSIFHWADDR |
|
SIOCSIFLLADDR |
|
SIOCSIFDSTADDR |
|
SIOCSIFFLAGS |
|
3.8 根据源/目标地址查找接口
3.8.1 根据源地址查找
// src/intf.c:835-861staticint _match_intf_src(conststruct intf_entry *entry,void*arg){struct intf_entry *save =(struct intf_entry *)arg;int matched =0, cnt;// 检查主地址if(entry->intf_addr.addr_type == ADDR_TYPE_IP &&entry->intf_addr.addr_ip == save->intf_addr.addr_ip)matched =1;// 检查别名地址for(cnt =0;!matched && cnt <(int) entry->intf_alias_num; cnt++){if(entry->intf_alias_addrs[cnt].addr_type != ADDR_TYPE_IP)continue;if(entry->intf_alias_addrs[cnt].addr_ip == save->intf_addr.addr_ip)matched =1;}if(matched){if(save->intf_len < entry->intf_len)memcpy(save, entry, save->intf_len);elsememcpy(save, entry, entry->intf_len);return(1);}return(0);}int intf_get_src(intf_t*intf,struct intf_entry *entry,struct addr *src){memcpy(&entry->intf_addr, src,sizeof(*src));if(intf_loop(intf, _match_intf_src, entry)!=1){errno = ENXIO;return(-1);}return(0);}
3.8.2 根据目标地址查找
// src/intf.c:875-901int intf_get_dst(intf_t*intf,struct intf_entry *entry,struct addr *dst){struct sockaddr_in sin;socklen_t n;if(dst->addr_type != ADDR_TYPE_IP){errno = EINVAL;return(-1);}addr_ntos(dst,(struct sockaddr *)&sin);sin.sin_port = htons(666);// 尝试连接到目标地址if(connect(intf->fd,(struct sockaddr *)&sin,sizeof(sin))<0)return(-1);// 获取本地地址n =sizeof(sin);if(getsockname(intf->fd,(struct sockaddr *)&sin,&n)<0)return(-1);addr_ston((struct sockaddr *)&sin,&entry->intf_addr);// 查找对应的接口if(intf_loop(intf, _match_intf_src, entry)!=1)return(-1);return(0);}
工作原理:
- 创建到目标地址的UDP连接(不实际发送数据)
- 获取本地绑定的源IP地址
- 遍历所有接口找到匹配该IP的接口
3.9 标志转换
// src/intf.c:106-146// 将libdnet标志转换为系统标志staticint intf_flags_to_iff(u_short flags,int iff){if(flags & INTF_FLAG_UP)iff |= IFF_UP;elseiff &=~IFF_UP;if(flags & INTF_FLAG_NOARP)iff |= IFF_NOARP;elseiff &=~IFF_NOARP;return(iff);}// 将系统标志转换为libdnet标志static u_int intf_iff_to_flags(uint64_t iff){u_int n =0;if(iff & IFF_UP)n |= INTF_FLAG_UP;if(iff & IFF_LOOPBACK)n |= INTF_FLAG_LOOPBACK;if(iff & IFF_POINTOPOINT)n |= INTF_FLAG_POINTOPOINT;if(iff & IFF_NOARP)n |= INTF_FLAG_NOARP;if(iff & IFF_BROADCAST)n |= INTF_FLAG_BROADCAST;if(iff & IFF_MULTICAST)n |= INTF_FLAG_MULTICAST;#ifdef IFF_IPMP// Solaris IPMP接口:清除广播和多播标志if(iff & IFF_IPMP)n &=~(INTF_FLAG_BROADCAST | INTF_FLAG_MULTICAST);#endifreturn(n);}
4. macOS/BSD平台实现
4.1 实现文件
主文件: src/intf.c(通用实现,包含BSD特定代码)
4.2 BSD特殊ioctl(lifreq)
Solaris和BSD系统支持长接口请求结构(lifreq):
// src/intf.c:422-487#ifdef SIOCGLIFCONFint _intf_get_noalias(intf_t*intf,struct intf_entry *entry){struct lifreq lifr;int fd;entry->intf_index = if_nametoindex(entry->intf_name);if(entry->intf_index ==0)return(-1);strlcpy(lifr.lifr_name, entry->intf_name,sizeof(lifr.lifr_name));// 自动选择IPv4或IPv6 socketif(ioctl(intf->fd, SIOCGLIFFLAGS,&lifr)>=0)fd = intf->fd;elseif(intf->fd6 !=-1&& ioctl(intf->fd6, SIOCGLIFFLAGS,&lifr)>=0)fd = intf->fd6;elsereturn(-1);entry->intf_flags = intf_iff_to_flags(lifr.lifr_flags);_intf_set_type(entry);#ifdef SIOCGLIFMTUif(ioctl(fd, SIOCGLIFMTU,&lifr)<0)#endifreturn(-1);entry->intf_mtu = lifr.lifr_mtu;entry->intf_addr.addr_type = entry->intf_dst_addr.addr_type =entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;if(ioctl(fd, SIOCGLIFADDR,&lifr)==0){addr_ston((struct sockaddr *)&lifr.lifr_addr,&entry->intf_addr);if(ioctl(fd, SIOCGLIFNETMASK,&lifr)<0)return(-1);addr_stob((struct sockaddr *)&lifr.lifr_addr,&entry->intf_addr.addr_bits);}if(entry->intf_type == INTF_TYPE_TUN){if(ioctl(fd, SIOCGLIFDSTADDR,&lifr)==0){if(addr_ston((struct sockaddr *)&lifr.lifr_addr,&entry->intf_dst_addr)<0)return(-1);}}elseif(entry->intf_type == INTF_TYPE_ETH){eth_t*eth;if((eth = eth_open(entry->intf_name))!= NULL){if(!eth_get(eth,&entry->intf_link_addr.addr_eth)){entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;}eth_close(eth);}}return(0);}#endif
BSD专用ioctl命令:
|
|
|
|
|---|---|---|
SIOCGLIFCONF |
|
|
SIOCGLIFADDR |
|
|
SIOCGLIFNETMASK |
|
|
SIOCGLIFDSTADDR |
|
|
SIOCGLIFMTU |
|
|
SIOCGLIFFLAGS |
|
|
4.3 BSD接口遍历
// src/intf.c:1000-1077#elif defined(SIOCGLIFCONF)int intf_loop(intf_t*intf, intf_handler callback,void*arg){struct intf_entry *entry;struct lifreq *lifr,*llifr,*plifr;char*p, ebuf[BUFSIZ];int ret;entry =(struct intf_entry *)ebuf;// 配置lifconfintf->lifc.lifc_family = AF_UNSPEC;intf->lifc.lifc_flags =0;#ifdef LIFC_UNDER_IPMPintf->lifc.lifc_flags |= LIFC_UNDER_IPMP;// 包含IPMP底层接口#endifintf->lifc.lifc_buf =(caddr_t)intf->ifcbuf;intf->lifc.lifc_len =sizeof(intf->ifcbuf);if(ioctl(intf->fd, SIOCGLIFCONF,&intf->lifc)<0)return(-1);llifr =(struct lifreq *)&intf->lifc.lifc_buf[intf->lifc.lifc_len];for(lifr = intf->lifc.lifc_req; lifr < llifr; lifr = NEXTLIFR(lifr)){// 处理别名(:分隔符)if((p = strchr(lifr->lifr_name,':'))!= NULL)*p ='\0';// 跳过重复的接口for(plifr = intf->lifc.lifc_req; plifr < lifr; plifr = NEXTLIFR(lifr)){if(strcmp(lifr->lifr_name, plifr->lifr_name)==0)break;}if(lifr > intf->lifc.lifc_req && plifr < lifr)continue;memset(ebuf,0,sizeof(ebuf));strlcpy(entry->intf_name, lifr->lifr_name,sizeof(entry->intf_name));strlcpy(entry->os_intf_name, lifr->lifr_name,sizeof(entry->os_intf_name));strlcpy(entry->pcap_intf_name, lifr->lifr_name,sizeof(entry->pcap_intf_name));intf_get_drv_info(intf, entry);entry->intf_len =sizeof(ebuf);// 恢复别名名称if(p)*p =':';// 跳过IPMP虚拟接口if(ioctl(intf->fd, SIOCGLIFFLAGS, lifr)>=0);elseif(intf->fd6 !=-1&& ioctl(intf->fd6, SIOCGLIFFLAGS, lifr)>=0);elsereturn(-1);#ifdef IFF_IPMPif(lifr->lifr_flags & IFF_IPMP){continue;// IPMP接口不支持某些操作(如抓包)}#endifif(_intf_get_noalias(intf, entry)<0)return(-1);if(_intf_get_aliases(intf, entry)<0)return(-1);if((ret =(*callback)(entry, arg))!=0)return(ret);}return(0);}#endif
4.4 BSD别名处理
// src/intf.c:628-692#elif defined(SIOCGLIFCONF)staticint _intf_get_aliases(intf_t*intf,struct intf_entry *entry){struct lifreq *lifr,*llifr;struct lifreq tmplifr;struct addr *ap,*lap;char*p;if(intf->lifc.lifc_len <(int)sizeof(*lifr)){errno = EINVAL;return(-1);}entry->intf_alias_num =0;ap = entry->intf_alias_addrs;llifr =(struct lifreq *)intf->lifc.lifc_buf +(intf->lifc.lifc_len /sizeof(*llifr));lap =(struct addr *)((u_char *)entry + entry->intf_len);// 遍历lifreq数组for(lifr = intf->lifc.lifc_req; lifr < llifr &&(ap +1)< lap;lifr = NEXTLIFR(lifr)){// 处理别名if((p = strchr(lifr->lifr_name,':'))!= NULL)*p ='\0';if(strcmp(lifr->lifr_name, entry->intf_name)!=0){if(p)*p =':';continue;}if(p)*p =':';if(addr_ston((struct sockaddr *)&lifr.lifr_addr, ap)<0)continue;// 跳过MAC地址(已在主地址中处理)if(ap->addr_type == ADDR_TYPE_ETH){memcpy(&entry->intf_link_addr, ap,sizeof(*ap));continue;}elseif(ap->addr_type == ADDR_TYPE_IP){if(ap->addr_ip == entry->intf_addr.addr_ip ||ap->addr_ip == entry->intf_dst_addr.addr_ip)continue;strlcpy(tmplifr.lifr_name, lifr->lifr_name,sizeof(tmplifr.lifr_name));if(ioctl(intf->fd, SIOCGIFNETMASK,&tmplifr)==0)addr_stob((struct sockaddr *)&tmplifr.lifr_addr,&ap->addr_bits);}elseif(ap->addr_type == ADDR_TYPE_IP6 && intf->fd6 !=-1){if(memcmp(&ap->addr_ip6,&entry->intf_addr.addr_ip6, IP6_ADDR_LEN)==0||memcmp(&ap->addr_ip6,&entry->intf_dst_addr.addr_ip6, IP6_ADDR_LEN)==0)continue;strlcpy(tmplifr.lifr_name, lifr->lifr_name,sizeof(tmplifr.lifr_name));if(ioctl(intf->fd6, SIOCGLIFNETMASK,&tmplifr)==0){addr_stob((struct sockaddr *)&tmplifr.lifr_addr,&ap->addr_bits);}}ap++, entry->intf_alias_num++;}entry->intf_len =(u_char *)ap -(u_char *)entry;return(0);}#endif
4.5 BSD sockaddr对齐处理
BSD系统使用 sa_len字段处理变长sockaddr:
说明:
- BSD:sockaddr包含
sa_len字段,需要按实际大小对齐 - Linux:sockaddr固定长度,直接递增指针
5. Windows平台实现
5.1 实现文件
主文件: src/intf-win32.c
5.2 接口句柄结构
// src/intf-win32.c:35-38struct intf_handle {struct ifcombo ifcombo[MIB_IF_TYPE_MAX];// 按类型分组的接口索引映射IP_ADAPTER_ADDRESSES *iftable;// 适配器地址表};
5.3 接口组合结构
// src/intf-win32.c:22-29struct ifcombo {struct{DWORD ipv4;// IPv4接口索引DWORD ipv6;// IPv6接口索引}*idx;int cnt;// 当前接口数量int max;// 最大接口数量};
作用:将Windows的原始接口索引映射为libdnet的友好接口名(eth0, eth1等)
5.4 打开接口句柄
// src/intf-win32.c:306-310intf_t*intf_open(void){return(calloc(1,sizeof(intf_t)));}
注意:Windows实现延迟初始化,在首次操作时刷新接口表。
5.5 刷新接口表
// src/intf-win32.c:219-269staticint _refresh_tables(intf_t*intf){IP_ADAPTER_ADDRESSES *p;DWORD ret;ULONG len;p = NULL;len =16384;// 初始缓冲区大小do{free(p);p = malloc(len);if(p == NULL)return(-1);// 获取适配器地址信息ret =GetAdaptersAddresses(AF_UNSPEC,GAA_FLAG_INCLUDE_PREFIX |GAA_FLAG_SKIP_ANYCAST |GAA_FLAG_SKIP_MULTICAST,NULL, p,&len);}while(ret == ERROR_BUFFER_OVERFLOW);if(ret != NO_ERROR){free(p);return(-1);}intf->iftable = p;// 建立接口索引映射for(p = intf->iftable; p != NULL; p = p->Next){int type;type = _if_type_canonicalize(p->IfType);if(type < MIB_IF_TYPE_MAX)_ifcombo_add(&intf->ifcombo[type], p->IfIndex, p->Ipv6IfIndex);elsereturn(-1);}return(0);}
GetAdaptersAddresses参数:
|
|
|
|---|---|
GAA_FLAG_INCLUDE_PREFIX |
|
GAA_FLAG_SKIP_ANYCAST |
|
GAA_FLAG_SKIP_MULTICAST |
|
5.6 接口类型转换
// src/intf-win32.c:51-72staticchar*_ifcombo_name(int type){char*name ="eth";if(type == IF_TYPE_ISO88025_TOKENRING){name ="tr";}elseif(type == IF_TYPE_PPP){name ="ppp";}elseif(type == IF_TYPE_SOFTWARE_LOOPBACK){name ="lo";}elseif(type == IF_TYPE_TUNNEL){name ="tun";}return(name);}// src/intf-win32.c:118-122staticint _if_type_canonicalize(int type){return _ifcombo_type(_ifcombo_name(type));}
Windows接口类型:
IF_TYPE_ETHERNET_CSMACD(6) → “eth”IF_TYPE_SOFTWARE_LOOPBACK(24) → “lo”IF_TYPE_PPP(23) → “ppp”IF_TYPE_TUNNEL(131) → “tun”
5.7 适配器地址转换
// src/intf-win32.c:124-217staticvoid _adapter_address_to_entry(intf_t*intf, IP_ADAPTER_ADDRESSES *a,struct intf_entry *entry){struct addr *ap,*lap;int i;int type;IP_ADAPTER_UNICAST_ADDRESS *addr;u_int intf_len = entry->intf_len;memset(entry,0,sizeof(*entry));entry->intf_len = intf_len;// 1. 确定接口索引type = _if_type_canonicalize(a->IfType);for(i =0; i < intf->ifcombo[type].cnt; i++){if(intf->ifcombo[type].idx[i].ipv4 == a->IfIndex&&intf->ifcombo[type].idx[i].ipv6 == a->Ipv6IfIndex){break;}}// 2. 设置接口名称snprintf(entry->intf_name,sizeof(entry->intf_name),"%s%u",_ifcombo_name(a->IfType), i);entry->intf_type =(uint16_t)type;// 3. 设置操作系统接口名称char friendly_name[MAX_INTERFACE_NAME_LEN];strncpy_wchar(friendly_name, a->FriendlyName, MAX_INTERFACE_NAME_LEN);snprintf(entry->os_intf_name, MAX_INTERFACE_NAME_LEN,"%s %s", friendly_name,a->AdapterName);// 4. 设置pcap接口名称constchar*name_start = strstr(a->AdapterName,"{");if(name_start){snprintf(entry->pcap_intf_name, MAX_INTERFACE_NAME_LEN -1,"\\Device\\NPF_%s", name_start);}// 5. 设置驱动名称strncpy_wchar(entry->driver_name, a->Description,sizeof(entry->driver_name));// 6. 设置接口标志entry->intf_flags =0;if(a->OperStatus==IfOperStatusUp)entry->intf_flags |= INTF_FLAG_UP;if(a->IfType== IF_TYPE_SOFTWARE_LOOPBACK)entry->intf_flags |= INTF_FLAG_LOOPBACK;elseentry->intf_flags |= INTF_FLAG_MULTICAST;// 7. 设置MTUentry->intf_mtu = a->Mtu;// 8. 设置MAC地址if(a->PhysicalAddressLength== ETH_ADDR_LEN){entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;memcpy(&entry->intf_link_addr.addr_eth, a->PhysicalAddress,ETH_ADDR_LEN);}// 9. 设置IP地址和别名ap = entry->intf_alias_addrs;lap = ap +((entry->intf_len -sizeof(*entry))/sizeof(entry->intf_alias_addrs[0]));for(addr = a->FirstUnicastAddress; addr != NULL; addr = addr->Next){IP_ADAPTER_PREFIX *prefix;unsignedshort bits;// 查找前缀长度(网络掩码位数)bits =0;for(prefix = a->FirstPrefix; prefix != NULL; prefix = prefix->Next){if(prefix->Address.lpSockaddr->sa_family ==addr->Address.lpSockaddr->sa_family){bits =(unsignedshort) prefix->PrefixLength;break;}}if(entry->intf_addr.addr_type == ADDR_TYPE_NONE){// 设置主地址addr_ston(addr->Address.lpSockaddr,&entry->intf_addr);entry->intf_addr.addr_bits = bits;}elseif(ap < lap){// 设置别名addr_ston(addr->Address.lpSockaddr, ap);ap->addr_bits = bits;ap++;entry->intf_alias_num++;}}entry->intf_len =(u_int)((u_char *)ap -(u_char *)entry);}
5.8 获取接口信息
// src/intf-win32.c:312-327int intf_get(intf_t*intf,struct intf_entry *entry){IP_ADAPTER_ADDRESSES *a;if(_refresh_tables(intf)<0)return(-1);a = _find_adapter_address(intf, entry->intf_name);if(a == NULL)return(-1);_adapter_address_to_entry(intf, a, entry);return(0);}
5.9 通过索引查找接口
// src/intf-win32.c:271-289static IP_ADAPTER_ADDRESSES *_find_adapter_address(intf_t*intf,constchar*device){IP_ADAPTER_ADDRESSES *a;char*p =(char*)device;int n, type = _ifcombo_type(device);// 解析接口名:eth0 → type=INTF_TYPE_ETH, n=0while(isalpha((int)(unsignedchar)*p)) p++;n = atoi(p);for(a = intf->iftable; a != NULL; a = a->Next){if(intf->ifcombo[type].idx[n].ipv4 == a->IfIndex&&intf->ifcombo[type].idx[n].ipv6 == a->Ipv6IfIndex){return a;}}return NULL;}// src/intf-win32.c:291-304static IP_ADAPTER_ADDRESSES *_find_adapter_address_by_index(intf_t*intf,int af,unsignedint index){IP_ADAPTER_ADDRESSES *a;for(a = intf->iftable; a != NULL; a = a->Next){if(af == AF_INET && index == a->IfIndex)return a;if(af == AF_INET6 && index == a->Ipv6IfIndex)return a;}return NULL;}int intf_get_index(intf_t*intf,struct intf_entry *entry,int af,unsignedint index){IP_ADAPTER_ADDRESSES *a;if(_refresh_tables(intf)<0)return(-1);a = _find_adapter_address_by_index(intf, af, index);if(a == NULL)return(-1);_adapter_address_to_entry(intf, a, entry);return(0);}
5.10 根据源地址查找接口
// src/intf-win32.c:347-373int intf_get_src(intf_t*intf,struct intf_entry *entry,struct addr *src){IP_ADAPTER_ADDRESSES *a;IP_ADAPTER_UNICAST_ADDRESS *addr;if(src->addr_type != ADDR_TYPE_IP){errno = EINVAL;return(-1);}if(_refresh_tables(intf)<0)return(-1);for(a = intf->iftable; a != NULL; a = a->Next){for(addr = a->FirstUnicastAddress; addr != NULL; addr = addr->Next){struct addr dnet_addr;addr_ston(addr->Address.lpSockaddr,&dnet_addr);if(addr_cmp(&dnet_addr, src)==0){_adapter_address_to_entry(intf, a, entry);return(0);}}}errno = ENXIO;return(-1);}
5.11 遍历所有接口
// src/intf-win32.c:396-416int intf_loop(intf_t*intf, intf_handler callback,void*arg){IP_ADAPTER_ADDRESSES *a;struct intf_entry *entry;u_char ebuf[1024];int ret;if(_refresh_tables(intf)<0)return(-1);entry =(struct intf_entry *)ebuf;for(a = intf->iftable; a != NULL; a = a->Next){entry->intf_len =sizeof(ebuf);_adapter_address_to_entry(intf, a, entry);if((ret =(*callback)(entry, arg))!=0)break;}return(ret);}
5.12 关闭接口句柄
// src/intf-win32.c:418-433intf_t*intf_close(intf_t*intf){int i;if(intf != NULL){for(i =0; i < MIB_IF_TYPE_MAX; i++){if(intf->ifcombo[i].idx)free(intf->ifcombo[i].idx);}if(intf->iftable)free(intf->iftable);free(intf);}return(NULL);}
5.13 Windows限制
不支持的操作:
// src/intf-win32.c:375-394// 根据目标地址查找接口(Windows不支持)int intf_get_dst(intf_t*intf,struct intf_entry *entry,struct addr *dst){errno = ENOSYS;SetLastError(ERROR_NOT_SUPPORTED);return(-1);}// 设置接口配置(Windows支持有限)int intf_set(intf_t*intf,conststruct intf_entry *entry){// 可以使用SetIfEntry()设置接口up/down// 但无法设置地址等配置errno = ENOSYS;SetLastError(ERROR_NOT_SUPPORTED);return(-1);}
6. 跨平台对比分析
6.1 API设计对比
|
|
|
|
|
|---|---|---|---|
| 获取接口列表 | SIOCGIFCONF
/proc/net/dev |
SIOCGLIFCONF |
GetAdaptersAddresses() |
| 获取接口信息 | ioctl()
ifreq |
ioctl()
lifreq |
GetAdaptersAddresses() |
| 设置接口信息 | ioctl()
ifreq |
ioctl()
ifreq/lifreq |
|
| 获取MAC地址 | SIOCGIFHWADDR
eth模块 |
eth
|
IP_ADAPTER_ADDRESSES.PhysicalAddress |
| 获取驱动信息 | ethtool |
|
IP_ADAPTER_ADDRESSES.Description |
| 别名支持 | eth0:1
|
eth0:1
|
|
| IPv6支持 | /proc/net/if_inet6 |
SIOCGLIFCONF |
|
6.2 数据结构对比
|
|
|
|
|---|---|---|
structifreq |
|
|
structlifreq |
|
|
structifconf |
|
|
structlifconf |
|
|
IP_ADAPTER_ADDRESSES |
|
|
IP_ADAPTER_UNICAST_ADDRESS |
|
|
6.3 操作方式对比
Linux/Unix
// 1. 创建socketint fd = socket(AF_INET, SOCK_DGRAM,0);// 2. 配置ifreqstruct ifreq ifr;strlcpy(ifr.ifr_name,"eth0", IFNAMSIZ);// 3. 执行ioctlioctl(fd, SIOCGIFADDR,&ifr);// 4. 解析结果addr_ston(&ifr.ifr_addr,&addr);
Windows
// 1. 分配缓冲区ULONG len =16384;IP_ADAPTER_ADDRESSES *p = malloc(len);// 2. 获取适配器信息GetAdaptersAddresses(AF_UNSPEC, flags, NULL, p,&len);// 3. 遍历适配器列表for(IP_ADAPTER_ADDRESSES *a = p; a != NULL; a = a->Next){// 处理适配器}// 4. 释放缓冲区free(p);
6.4 性能对比
|
|
|
|
|
|---|---|---|---|
| 获取单个接口 |
|
|
|
| 遍历所有接口 |
|
|
|
| 查找接口 |
|
|
|
| 设置接口 |
|
|
|
6.5 权限要求对比
|
|
|
|
|
|---|---|---|---|
| 读取接口信息 |
|
|
|
| 设置接口信息 |
|
|
|
| 获取驱动信息 |
|
|
|
6.6 IPv6支持对比
|
|
|
|
|---|---|---|
| Linux | /proc/net/if_inet6
SIOCGIFNETMASK_IN6 |
|
| macOS/BSD | SIOCGLIFCONF |
|
| Windows | GetAdaptersAddresses() |
|
| Solaris | SIOCGLIFCONF |
|
6.7 代码复杂度对比
|
|
|
|
|---|---|---|
src/intf.c |
|
|
src/intf-win32.c |
|
|
复杂度分析:
- Unix版本:需要处理多个平台的差异(ioctl变体、proc文件系统、getkerninfo等)
- Windows版本:逻辑简单但API复杂(需要处理Unicode、适配器地址链表等)
6.8 维护性分析
|
|
|
|
|---|---|---|
| API稳定性 |
|
|
| 平台差异 |
|
|
| 代码复用 |
|
|
| 测试难度 |
|
|
7. 实际应用示例
7.1 基本用法:显示所有接口
#include<stdio.h>#include<dnet.h>staticint print_intf(conststruct intf_entry *entry,void*arg){int i;printf("接口: %s\n", entry->intf_name);printf(" 类型: %d\n", entry->intf_type);printf(" 标志: 0x%x\n", entry->intf_flags);printf(" MTU: %u\n", entry->intf_mtu);printf(" 索引: %u\n", entry->intf_index);if(entry->intf_addr.addr_type == ADDR_TYPE_IP){printf(" IP地址: %s/%d\n",addr_ntoa(&entry->intf_addr),entry->intf_addr.addr_bits);}if(entry->intf_link_addr.addr_type == ADDR_TYPE_ETH){printf(" MAC地址: %s\n",addr_ntoa(&entry->intf_link_addr));}for(i =0; i < entry->intf_alias_num; i++){printf(" 别名%d: %s/%d\n", i +1,addr_ntoa(&entry->intf_alias_addrs[i]),entry->intf_alias_addrs[i].addr_bits);}printf("\n");return(0);}int main(void){intf_t*intf;if((intf = intf_open())== NULL){perror("intf_open");return(1);}if(intf_loop(intf, print_intf, NULL)<0){perror("intf_loop");return(1);}intf_close(intf);return(0);}
7.2 获取指定接口信息
int main(void){intf_t*intf;struct intf_entry entry;char buf[1024];memset(&entry,0,sizeof(entry));entry.intf_len =sizeof(buf);if((intf = intf_open())== NULL){perror("intf_open");return(1);}// 获取eth0接口信息strlcpy(entry.intf_name,"eth0",sizeof(entry.intf_name));if(intf_get(intf,&entry)<0){perror("intf_get");return(1);}printf("接口: %s\n", entry.intf_name);printf(" IP: %s\n", addr_ntoa(&entry.intf_addr));printf(" MAC: %s\n", addr_ntoa(&entry.intf_link_addr));printf(" MTU: %u\n", entry.intf_mtu);intf_close(intf);return(0);}
7.3 根据源地址查找接口
int main(void){intf_t*intf;struct intf_entry entry;struct addr src;char buf[1024];memset(&entry,0,sizeof(entry));entry.intf_len =sizeof(buf);if((intf = intf_open())== NULL){perror("intf_open");return(1);}// 解析源地址if(addr_pton("192.168.1.100",&src)<0){perror("addr_pton");return(1);}// 查找包含该地址的接口if(intf_get_src(intf,&entry,&src)<0){perror("intf_get_src");return(1);}printf("包含地址 %s 的接口: %s\n",addr_ntoa(&src), entry.intf_name);intf_close(intf);return(0);}
7.4 设置接口配置(Unix)
int main(void){intf_t*intf;struct intf_entry entry;struct addr addr;char buf[1024];memset(&entry,0,sizeof(entry));entry.intf_len =sizeof(buf);if((intf = intf_open())== NULL){perror("intf_open");return(1);}// 设置接口名称strlcpy(entry.intf_name,"eth0",sizeof(entry.intf_name));// 设置IP地址if(addr_pton("192.168.1.100/24",&addr)<0){perror("addr_pton");return(1);}entry.intf_addr = addr;// 设置MAC地址if(addr_pton("00:11:22:33:44:55",&addr)<0){perror("addr_pton");return(1);}entry.intf_link_addr = addr;// 设置MTUentry.intf_mtu =1500;// 设置标志entry.intf_flags = INTF_FLAG_UP | INTF_FLAG_BROADCAST;// 应用配置if(intf_set(intf,&entry)<0){perror("intf_set");return(1);}printf("接口配置成功\n");intf_close(intf);return(0);}
7.5 网络接口监控工具
#include<dnet.h>#include<unistd.h>staticint print_intf(conststruct intf_entry *entry,void*arg){printf("%-10s %-15s %-20s MTU:%5u Flags:%04x\n",entry->intf_name,entry->intf_addr.addr_type == ADDR_TYPE_IP ?addr_ntoa(&entry->intf_addr):"N/A",entry->intf_link_addr.addr_type == ADDR_TYPE_ETH ?addr_ntoa(&entry->intf_link_addr):"N/A",entry->intf_mtu,entry->intf_flags);return(0);}int main(int argc,char*argv[]){intf_t*intf;int interval =5;if(argc >1)interval = atoi(argv[1]);if((intf = intf_open())== NULL){perror("intf_open");return(1);}printf("网络接口监控(每%d秒刷新):\n", interval);printf("%-10s %-15s %-20s %s\n","接口","IP地址","MAC地址","配置");printf("--------------------------------------------------------\n");while(1){system("clear");// Linux/macOS// system("cls"); // Windowsif(intf_loop(intf, print_intf, NULL)<0){perror("intf_loop");return(1);}sleep(interval);}intf_close(intf);return(0);}
7.6 使用dnet命令行工具
# 显示所有接口$ dnet intf show# 获取指定接口$ dnet intf get eth0# 根据源地址查找接口$ dnet intf src 192.168.1.100# 根据目标地址查找接口$ dnet intf dst 8.8.8.8# 设置接口(Unix)$ sudo dnet intf set eth0 inet 192.168.1.100/24 up# 添加别名地址$ sudo dnet intf set eth0 alias 192.168.1.101/24# 禁用接口$ sudo dnet intf set eth0 down
8. 常见问题与解决方案
8.1 权限问题
问题:
intf_set:Operationnot permitted
原因:修改接口配置需要root权限
解决方案:
# Linux/macOSsudo ./your_program# Windows(管理员权限)右键→以管理员身份运行
8.2 接口未找到
问题:
intf_get:No such device
原因:接口名称错误或接口不存在
解决方案:
// 首先列出所有接口intf_loop(intf, print_intf, NULL);// 确认接口名称后再获取strlcpy(entry.intf_name,"eth0",sizeof(entry.intf_name));
8.3 IPv6别名未显示
问题:IPv6别名地址未出现在列表中
原因:Linux需要从 /proc/net/if_inet6读取
解决方案:
8.4 macOS/BSD sockaddr对齐问题
问题:遍历接口时出现段错误或数据错误
原因:BSD系统的sockaddr结构是变长的
解决方案:
8.5 Windows GetAdaptersAddresses失败
问题:
_refresh_tables: ERROR_INVALID_PARAMETER
原因:缓冲区大小不足或多次调用失败
解决方案:
// 使用足够大的初始缓冲区ULONG len =16384;// 16KB初始缓冲区// 或者动态调整大小do{free(p);len *=2;// 每次翻倍p = malloc(len);ret =GetAdaptersAddresses(..., p,&len);}while(ret == ERROR_BUFFER_OVERFLOW);
8.6 Solaris IPMP接口问题
问题:IPMP虚拟接口无法正常工作
原因:IPMP接口不支持某些操作(如抓包)
解决方案:
8.7 AIX MAC地址获取失败
问题: SIOCGIFHWADDR在AIX上失败
原因:AIX不使用标准ioctl获取MAC地址
解决方案:
// 使用getkerninfo获取size = getkerninfo(KINFO_NDD,0,0,0);nddp =(struct kinfo_ndd *)malloc(size);getkerninfo(KINFO_NDD, nddp,&size,0);// 查找匹配的接口while(nddp < end){if(!strcmp(nddp->ndd_alias, entry->intf_name)){addr_pack(&entry->intf_link_addr, ADDR_TYPE_ETH,ETH_ADDR_BITS, nddp->ndd_addr, ETH_ADDR_LEN);break;}nddp++;}
8.8 Linux接口名称过长
问题:
intf_set:Numerical result out of range
原因:接口名称超过 IFNAMSIZ限制
解决方案:
// 检查接口名称长度if(strlen(entry->intf_name)>= IFNAMSIZ){fprintf(stderr,"接口名称过长(最大 %d 字符)\n", IFNAMSIZ -1);return(-1);}// 安全复制strlcpy(ifr.ifr_name, entry->intf_name,sizeof(ifr.ifr_name));
8.9 性能优化:缓存接口表
问题:频繁调用 intf_get导致性能下降
原因:Windows实现每次都刷新整个接口表
解决方案:
// 应用层缓存struct intf_cache {time_t timestamp;intf_t*intf;struct intf_entry entries[MAX_INTERFACES];int count;};staticstruct intf_cache cache ={0};int cached_intf_get(constchar*name,struct intf_entry *entry){time_t now = time(NULL);// 缓存有效期为60秒if(now - cache.timestamp >60){// 刷新缓存cache.timestamp = now;cache.count =0;intf_loop(cache.intf, cache_intf_callback,&cache);}// 从缓存中查找for(int i =0; i < cache.count; i++){if(strcmp(cache.entries[i].intf_name, name)==0){memcpy(entry,&cache.entries[i], entry->intf_len);return(0);}}return(-1);}
8.10 调试技巧
启用详细日志:
使用系统工具验证:
# Linuxip addr showifconfigcat /proc/net/devcat /proc/net/if_inet6# macOS/BSDifconfignetstat -in# Windowsipconfig /all
总结
本文档深入分析了libdnet网络接口控制模块的跨平台实现,涵盖了以下核心内容:
主要特点
- 三大平台支持:
- Linux/Unix:基于ioctl和proc文件系统
- macOS/BSD:基于ioctl和长接口请求(lifreq)
- Windows:基于IP Helper API
- 核心功能:
- 接口信息查询(IP、MAC、MTU、标志等)
- 接口配置(仅Unix支持)
- 别名地址管理
- 接口遍历和查找
- 驱动信息获取(Linux)
- 跨平台适配:
- 条件编译处理平台差异
- 统一的API接口
- 灵活的数据结构设计
实现亮点
- Linux:混合使用ioctl、proc文件系统和ethtool
- BSD:支持lifreq处理IPv6,智能sockaddr对齐
- Windows:完整的IP Helper API集成,友好的接口名映射
- AIX:使用getkerninfo获取MAC地址
使用建议
- 读取优先:优先使用读取操作,避免频繁修改
- 缓存机制:对频繁访问的接口信息实现缓存
- 错误处理:正确处理平台特定的错误码
- 权限管理:修改配置需要root/管理员权限
通过本文档,开发者可以全面理解libdnet网络接口控制模块的设计思想和实现细节,并在不同平台上正确使用该库。
-
公众号:安全狗的自我修养
-
vx:2207344074
-
http://gitee.com/haidragon
-
http://github.com/haidragon
-
bilibili:haidragonx
-


夜雨聆风