乐于分享
好东西不私藏

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

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

网:http://securitytech.cc

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

网络接口控制实现深入分析

目录

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

1. 网络接口基础概念

1.1 网络接口简介

网络接口(Network Interface)是操作系统与网络硬件之间的抽象层,负责:

  • 数据链路层通信:处理MAC地址、以太网帧等
  • IP地址管理:配置IPv4/IPv6地址
  • 接口状态控制:启用/禁用接口、设置MTU
  • 别名支持:一个接口配置多个IP地址
  • 多播和广播支持:组播和广播功能

1.2 接口类型(MIB-II标准)

libdnet支持以下接口类型(基于IANA ifType MIB):

类型常量
描述
INTF_TYPE_OTHER
1
其他类型接口
INTF_TYPE_ETH
6
以太网接口
INTF_TYPE_TOKENRING
9
令牌环网
INTF_TYPE_FDDI
15
光纤分布式数据接口
INTF_TYPE_PPP
23
点对点协议
INTF_TYPE_LOOPBACK
24
软件回环接口
INTF_TYPE_SLIP
28
串行线路接口协议
INTF_TYPE_TUN
53
专有虚拟/内部接口

1.3 接口标志

接口标志用于描述接口的当前状态和能力:

标志常量
描述
读写属性
INTF_FLAG_UP
0x01
接口已启用
读/写
INTF_FLAG_LOOPBACK
0x02
回环接口
只读
INTF_FLAG_POINTOPOINT
0x04
点对点链路
只读
INTF_FLAG_NOARP
0x08
禁用ARP
读/写
INTF_FLAG_BROADCAST
0x10
支持广播
只读
INTF_FLAG_MULTICAST
0x20
支持多播
只读

1.4 地址结构体

libdnet使用统一的 structaddr表示不同类型的地址:

  1. // include/dnet/addr.h:18-30
  2. struct addr {
  3. uint16_t addr_type;// 地址类型:ADDR_TYPE_ETH/IP/IP6/NONE
  4. uint16_t addr_bits;// 地址前缀长度(网络掩码位数)
  5. union{
  6. eth_addr_t __eth;// 6字节MAC地址
  7. ip_addr_t __ip;// 4字节IPv4地址
  8. ip6_addr_t __ip6;// 16字节IPv6地址
  9. uint8_t __data8[16];
  10. uint16_t __data16[8];
  11. uint32_t __data32[4];
  12. } __addr_u;
  13. };

2. libdnet接口设计

2.1 接口条目结构

  1. // include/dnet/intf.h:25-50
  2. struct intf_entry {
  3. uint32_t intf_len;// 条目总长度(包括别名)
  4. char intf_name[INTF_NAME_LEN];// 接口名称(eth0, lo等)
  5. char os_intf_name[...];// 操作系统原始接口名
  6. char pcap_intf_name[...];// libpcap使用的接口名
  7. char driver_name[...];// 驱动程序名称
  8. char driver_vers[...];// 驱动版本
  9. char firmware_vers[...];// 固件版本
  10. uint32_t intf_index;// 接口索引(只读)
  11. uint16_t intf_type;// 接口类型
  12. uint16_t intf_flags;// 接口标志
  13. uint32_t intf_mtu;// 最大传输单元
  14. struct addr intf_addr;// 主IP地址
  15. struct addr intf_dst_addr;// 点对点目标地址
  16. struct addr intf_link_addr;// 链路层地址(MAC)
  17. uint32_t intf_alias_num;// 别名地址数量
  18. struct addr intf_alias_addrs[];// 别名地址数组(柔性数组)
  19. };

2.2 核心API接口

  1. // include/dnet/intf.h:79-87
  2. intf_t*intf_open(void);// 打开接口句柄
  3. int intf_get(intf_t*i,struct intf_entry *entry);// 获取接口信息
  4. int intf_get_index(intf_t*intf,struct intf_entry *entry,int af,
  5. unsignedint index);// 通过索引获取接口
  6. int intf_get_src(intf_t*i,struct intf_entry *entry,
  7. struct addr *src);// 根据源地址查找接口
  8. int intf_get_dst(intf_t*i,struct intf_entry *entry,
  9. struct addr *dst);// 根据目标地址查找接口
  10. int intf_set(intf_t*i,conststruct intf_entry *entry);// 设置接口配置
  11. int intf_loop(intf_t*i, intf_handler callback,void*arg);// 遍历所有接口
  12. intf_t*intf_close(intf_t*i);// 关闭接口句柄

2.3 回调函数类型

  1. // include/dnet/intf.h:76
  2. typedefint(*intf_handler)(conststruct intf_entry *entry,void*arg);

2.4 平台适配机制

libdnet通过编译时宏选择不同实现:

  • Unix/Linux: src/intf.c
  • Windows: src/intf-win32.c

配置系统在 configure.ac中检测平台特性:

  1. # 检测IPv6支持
  2. AC_CHECK_DECL([SIOCGLIFCONF],...)
  3. # 检测proc文件系统
  4. AC_CHECK_FILE([/proc/net/dev],...)
  5. # 检测getkerninfo(AIX)
  6. AC_CHECK_FUNCS([getkerninfo],...)

3. Unix/Linux平台实现

3.1 实现文件

主文件: src/intf.c(适用于Linux、macOS/BSD、HP-UX、AIX、Solaris等)

3.2 接口句柄结构

  1. // src/intf.c:96-104
  2. struct intf_handle {
  3. int fd;// IPv4 socket (AF_INET, SOCK_DGRAM)
  4. int fd6;// IPv6 socket (AF_INET6, SOCK_DGRAM)
  5. struct ifconf ifc;// IPv4接口配置
  6. #ifdef SIOCGLIFCONF
  7. struct lifconf lifc;// IPv6/长接口配置(Solaris/BSD)
  8. #endif
  9.     u_char ifcbuf[4192];// 缓冲区,存储ifconf结果
  10. };

3.3 打开接口句柄

  1. // src/intf.c:148-173
  2. intf_t*intf_open(void)
  3. {
  4. intf_t*intf;
  5. int one =1;
  6. if((intf = calloc(1,sizeof(*intf)))!= NULL){
  7.         intf->fd = intf->fd6 =-1;
  8. // 创建IPv4 socket用于ioctl操作
  9. if((intf->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
  10. return(intf_close(intf));
  11. // 允许广播
  12.         setsockopt(intf->fd, SOL_SOCKET, SO_BROADCAST,
  13. (constchar*)&one,sizeof(one));
  14. // 创建IPv6 socket(如果支持)
  15. #if defined(SIOCGLIFCONF) || defined(SIOCGIFNETMASK_IN6) || defined(SIOCGIFNETMASK6)
  16. if((intf->fd6 = socket(AF_INET6, SOCK_DGRAM,0))<0){
  17. #ifdef EPROTONOSUPPORT
  18. if(errno != EPROTONOSUPPORT)
  19. #endif
  20. return(intf_close(intf));
  21. }
  22. #endif
  23. }
  24. return(intf);
  25. }

关键点

  • 使用 AF_INET socket作为ioctl操作的句柄
  • 设置 SO_BROADCAST选项以支持广播操作
  • 可选创建IPv6 socket处理IPv6相关ioctl

3.4 ioctl接口操作

3.4.1 获取接口信息(无别名)

Linux平台使用标准ioctl系列:

  1. // src/intf.c:489-598
  2. staticint _intf_get_noalias(intf_t*intf,struct intf_entry *entry)
  3. {
  4. struct ifreq ifr;
  5. #ifdef HAVE_GETKERNINFO
  6. int size;
  7. struct kinfo_ndd *nddp;
  8. void*end;
  9. #endif
  10. // 1. 获取接口索引
  11.     entry->intf_index = if_nametoindex(entry->intf_name);
  12. if(entry->intf_index ==0)
  13. return(-1);
  14.     strlcpy(ifr.ifr_name, entry->intf_name,sizeof(ifr.ifr_name));
  15. // 2. 获取接口标志
  16. if(ioctl(intf->fd, SIOCGIFFLAGS,&ifr)<0)
  17. return(-1);
  18.     entry->intf_flags = intf_iff_to_flags(ifr.ifr_flags);
  19.     _intf_set_type(entry);
  20. // 3. 获取接口MTU
  21. #ifdef SIOCGIFMTU
  22. if(ioctl(intf->fd, SIOCGIFMTU,&ifr)<0)
  23. #endif
  24. return(-1);
  25.     entry->intf_mtu = ifr.ifr_mtu;
  26.     entry->intf_addr.addr_type = entry->intf_dst_addr.addr_type =
  27.         entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;
  28. // 4. 获取主IP地址
  29. if(ioctl(intf->fd, SIOCGIFADDR,&ifr)==0){
  30.         addr_ston(&ifr.ifr_addr,&entry->intf_addr);
  31. if(ioctl(intf->fd, SIOCGIFNETMASK,&ifr)<0)
  32. return(-1);
  33.         addr_stob(&ifr.ifr_addr,&entry->intf_addr.addr_bits);
  34. }
  35. // 5. 获取其他地址
  36. if(entry->intf_type == INTF_TYPE_TUN){
  37. // 点对点目标地址
  38. if(ioctl(intf->fd, SIOCGIFDSTADDR,&ifr)==0){
  39. if(addr_ston(&ifr.ifr_addr,
  40. &entry->intf_dst_addr)<0)
  41. return(-1);
  42. }
  43. }elseif(entry->intf_type == INTF_TYPE_ETH){
  44. #if defined(HAVE_GETKERNINFO)
  45. // AIX:使用getkerninfo获取MAC地址
  46.         size = getkerninfo(KINFO_NDD,0,0,0);
  47. if(size <=0)return-1;
  48.         nddp =(struct kinfo_ndd *)malloc(size);
  49. if(!nddp)return-1;
  50. if(getkerninfo(KINFO_NDD, nddp,&size,0)<0){
  51.             free(nddp);
  52. return-1;
  53. }
  54.         end =(void*)nddp + size;
  55. while((void*)nddp < end){
  56. if(!strcmp(nddp->ndd_alias, entry->intf_name)||
  57. !strcmp(nddp->ndd_name, entry->intf_name)){
  58.                 addr_pack(&entry->intf_link_addr, ADDR_TYPE_ETH, ETH_ADDR_BITS,
  59.                     nddp->ndd_addr, ETH_ADDR_LEN);
  60. break;
  61. }else
  62.                 nddp++;
  63. }
  64.         free(nddp);
  65. #elif defined(SIOCGIFHWADDR)
  66. // Linux:标准方式
  67. if(ioctl(intf->fd, SIOCGIFHWADDR,&ifr)<0)
  68. return(-1);
  69. if(addr_ston(&ifr.ifr_addr,&entry->intf_link_addr)<0)
  70. return(-1);
  71. #elif defined(SIOCRPHYSADDR)
  72. // Tru64:特殊ioctl
  73. struct ifdevea *ifd =(struct ifdevea *)&ifr;
  74. if(ioctl(intf->fd, SIOCRPHYSADDR, ifd)<0)
  75. return(-1);
  76.         addr_pack(&entry->intf_link_addr, ADDR_TYPE_ETH, ETH_ADDR_BITS,
  77.             ifd->current_pa, ETH_ADDR_LEN);
  78. #else
  79. // 回退:使用eth模块
  80. eth_t*eth;
  81. if((eth = eth_open(entry->intf_name))!= NULL){
  82. if(!eth_get(eth,&entry->intf_link_addr.addr_eth)){
  83.                 entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;
  84.                 entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;
  85. }
  86.             eth_close(eth);
  87. }
  88. #endif
  89. }
  90. return(0);
  91. }

ioctl命令说明

ioctl命令
功能
平台支持
SIOCGIFFLAGS
获取接口标志
所有Unix
SIOCGIFMTU
获取MTU
Linux/BSD
SIOCGIFADDR
获取IP地址
所有Unix
SIOCGIFNETMASK
获取子网掩码
所有Unix
SIOCGIFDSTADDR
获取点对点目标地址
Unix
SIOCGIFHWADDR
获取MAC地址(Linux)
Linux
SIOCRPHYSADDR
获取物理地址(Tru64)
Tru64

3.4.2 获取别名地址

  1. // src/intf.c:694-802
  2. staticint _intf_get_aliases(intf_t*intf,struct intf_entry *entry)
  3. {
  4. struct ifreq *ifr,*lifr;
  5. struct ifreq tmpifr;
  6. struct addr *ap,*lap;
  7. char*p;
  8. if(intf->ifc.ifc_len <(int)sizeof(*ifr)){
  9.         errno = EINVAL;
  10. return(-1);
  11. }
  12.     entry->intf_alias_num =0;
  13.     ap = entry->intf_alias_addrs;
  14.     lifr =(struct ifreq *)intf->ifc.ifc_buf +
  15. (intf->ifc.ifc_len /sizeof(*lifr));
  16.     lap =(struct addr *)((u_char *)entry + entry->intf_len);
  17. // 遍历所有ifreq条目
  18. for(ifr = intf->ifc.ifc_req; ifr < lifr &&(ap +1)< lap;
  19.         ifr = NEXTIFR(ifr)){
  20. // Linux/Solaris:别名接口名格式为eth0:1
  21. if((= strchr(ifr->ifr_name,':'))!= NULL)
  22. *='\0';
  23. if(strcmp(ifr->ifr_name, entry->intf_name)!=0){
  24. if(p)*=':';
  25. continue;
  26. }
  27. // 恢复别名名称
  28. if(p)*=':';
  29. if(addr_ston(&ifr.ifr_addr, ap)<0)
  30. continue;
  31. // 特殊处理
  32. if(ap->addr_type == ADDR_TYPE_ETH){
  33.             memcpy(&entry->intf_link_addr, ap,sizeof(*ap));
  34. continue;
  35. }elseif(ap->addr_type == ADDR_TYPE_IP){
  36. // 跳过主地址和点对点地址
  37. if(ap->addr_ip == entry->intf_addr.addr_ip ||
  38.                 ap->addr_ip == entry->intf_dst_addr.addr_ip)
  39. continue;
  40.             strlcpy(tmpifr.ifr_name, ifr->ifr_name,sizeof(tmpifr.ifr_name));
  41. if(ioctl(intf->fd, SIOCGIFNETMASK,&tmpifr)==0)
  42.                 addr_stob(&tmpifr.ifr_addr,&ap->addr_bits);
  43. }
  44. #ifdef SIOCGIFNETMASK_IN6
  45. elseif(ap->addr_type == ADDR_TYPE_IP6 && intf->fd6 !=-1){
  46. struct in6_ifreq ifr6;
  47.             memcpy(&ifr6, ifr,sizeof(ifr6));
  48. if(ioctl(intf->fd6, SIOCGIFNETMASK_IN6,&ifr6)==0){
  49.                 addr_stob((struct sockaddr *)&ifr6.ifr_addr,
  50. &ap->addr_bits);
  51. }
  52. }
  53. #endif
  54.         ap++, entry->intf_alias_num++;
  55. }
  56. #ifdef HAVE_LINUX_PROCFS
  57. // Linux:从/proc/net/if_inet6读取IPv6地址
  58. #define PROC_INET6_FILE    "/proc/net/if_inet6"
  59. {
  60. FILE*f;
  61. char buf[256], s[8][5], name[INTF_NAME_LEN];
  62.         u_int idx, bits, scope, flags;
  63. if((= fopen(PROC_INET6_FILE,"r"))!= NULL){
  64. while(ap < lap &&
  65.                    fgets(buf,sizeof(buf), f)!= NULL){
  66.                 sscanf(buf,"%04s%04s%04s%04s%04s%04s%04s%04s %x %02x %02x %02x %32s\n",
  67.                     s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
  68. &idx,&bits,&scope,&flags, name);
  69. if(strcmp(name, entry->intf_name)==0){
  70.                     snprintf(buf,sizeof(buf),"%s:%s:%s:%s:%s:%s:%s:%s/%d",
  71.                         s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], bits);
  72.                     addr_aton(buf, ap);
  73.                     ap++, entry->intf_alias_num++;
  74. }
  75. }
  76.             fclose(f);
  77. }
  78. }
  79. #endif
  80.     entry->intf_len =(u_char *)ap -(u_char *)entry;
  81. return(0);
  82. }

别名处理

  • Linux:别名接口名为 eth0:1、 eth0:2等形式
  • IPv6:从 /proc/net/if_inet6读取
  • NEXTIFR宏:处理sockaddr的变长对齐(BSD)

3.5 Linux特定实现:proc文件系统

Linux使用 /proc/net/dev遍历接口列表:

  1. // src/intf.c:943-999
  2. #ifdef HAVE_LINUX_PROCFS
  3. #define PROC_DEV_FILE    "/proc/net/dev"
  4. int intf_loop(intf_t*intf, intf_handler callback,void*arg)
  5. {
  6. FILE*fp;
  7. struct intf_entry *entry;
  8. char*p, buf[BUFSIZ], ebuf[BUFSIZ];
  9. int ret;
  10.     entry =(struct intf_entry *)ebuf;
  11. // 打开/proc/net/dev
  12. if((fp = fopen(PROC_DEV_FILE,"r"))== NULL)
  13. return(-1);
  14. // 获取所有接口配置
  15.     intf->ifc.ifc_buf =(caddr_t)intf->ifcbuf;
  16.     intf->ifc.ifc_len =sizeof(intf->ifcbuf);
  17. if(ioctl(intf->fd, SIOCGIFCONF,&intf->ifc)<0){
  18.         fclose(fp);
  19. return(-1);
  20. }
  21.     ret =0;
  22. while(fgets(buf,sizeof(buf), fp)!= NULL){
  23. if((= strchr(buf,':'))== NULL)
  24. continue;
  25. *='\0';
  26. for(= buf;*==' '; p++)
  27. ;// 跳过前导空格
  28.         memset(ebuf,0,sizeof(ebuf));
  29.         strlcpy(entry->intf_name, p,sizeof(entry->intf_name));
  30.         strlcpy(entry->os_intf_name, p,sizeof(entry->os_intf_name));
  31.         strlcpy(entry->pcap_intf_name, p,sizeof(entry->pcap_intf_name));
  32. // 获取驱动信息(ethtool)
  33.         intf_get_drv_info(intf, entry);
  34.         entry->intf_len =sizeof(ebuf);
  35. // 获取接口详细信息
  36. if(_intf_get_noalias(intf, entry)<0){
  37.             ret =-1;
  38. break;
  39. }
  40. // 获取别名地址
  41. if(_intf_get_aliases(intf, entry)<0){
  42.             ret =-1;
  43. break;
  44. }
  45. // 调用回调函数
  46. if((ret =(*callback)(entry, arg))!=0)
  47. break;
  48. }
  49. if(ferror(fp))
  50.         ret =-1;
  51.     fclose(fp);
  52. return(ret);
  53. }
  54. #endif

/proc/net/dev格式示例

  1. Inter-|Receive|Transmit
  2.  face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
  3.   eth0:123456787650000009876545432000000
  4.   lo:45678923450000004567892345000000

3.6 Linux驱动信息获取(ethtool)

Linux使用ethtool接口获取驱动和固件信息:

  1. // src/intf.c:903-941
  2. #if defined(__linux__)
  3. #include<linux/types.h>
  4. typedef __u64 u64;
  5. typedef __u32 u32;
  6. typedef __u16 u16;
  7. typedef __u8 u8;
  8. #include<linux/sockios.h>
  9. #include<linux/ethtool.h>
  10. staticint intf_get_drv_info(intf_t*intf,struct intf_entry *entry)
  11. {
  12. struct ifreq ifr;
  13. struct ethtool_drvinfo drvinfo;
  14.     memset(&ifr,0,sizeof(ifr));
  15.     memset(&drvinfo,0,sizeof(drvinfo));
  16.     drvinfo.cmd = ETHTOOL_GDRVINFO;
  17.     ifr.ifr_data =(caddr_t)&drvinfo;
  18. if(strlen(entry->intf_name)> IFNAMSIZ +1){
  19. return-1;
  20. }
  21.     strlcpy(ifr.ifr_name, entry->intf_name, IFNAMSIZ);
  22. if(ioctl(intf->fd, SIOCETHTOOL,&ifr)){
  23. return-1;
  24. }
  25.     strncpy(entry->driver_name, drvinfo.driver, INTF_VERS_LEN);
  26.     strncpy(entry->driver_vers, drvinfo.version, INTF_VERS_LEN);
  27.     strncpy(entry->firmware_vers, drvinfo.fw_version, INTF_VERS_LEN);
  28. return0;
  29. }
  30. #else
  31. staticint intf_get_drv_info(intf_t*intf,struct intf_entry *entry)
  32. {
  33. return-1;
  34. }
  35. #endif

3.7 设置接口配置

  1. // src/intf.c:294-406
  2. int intf_set(intf_t*intf,conststruct intf_entry *entry)
  3. {
  4. struct ifreq ifr;
  5. struct intf_entry *orig;
  6. struct addr bcast;
  7.     u_char buf[BUFSIZ];
  8. // 1. 读取当前配置
  9.     orig =(struct intf_entry *)buf;
  10.     orig->intf_len =sizeof(buf);
  11.     strcpy(orig->intf_name, entry->intf_name);
  12. if(intf_get(intf, orig)<0)
  13. return(-1);
  14. // 2. 删除现有别名
  15. if(_intf_delete_aliases(intf, orig)<0)
  16. return(-1);
  17. // 3. 删除现有地址
  18. if(_intf_delete_addrs(intf, orig)<0)
  19. return(-1);
  20.     memset(&ifr,0,sizeof(ifr));
  21.     strlcpy(ifr.ifr_name, entry->intf_name,sizeof(ifr.ifr_name));
  22. // 4. 设置MTU
  23. if(entry->intf_mtu !=0){
  24.         ifr.ifr_mtu = entry->intf_mtu;
  25. #ifdef SIOCSIFMTU
  26. if(ioctl(intf->fd, SIOCSIFMTU,&ifr)<0)
  27. #endif
  28. return(-1);
  29. }
  30. // 5. 设置IP地址
  31. if(entry->intf_addr.addr_type == ADDR_TYPE_IP){
  32. #if defined(BSD) && !defined(__OPENBSD__)
  33. // BSD:必须先设置子网掩码
  34. if(addr_btos(entry->intf_addr.addr_bits,
  35. &ifr.ifr_addr)==0){
  36. if(ioctl(intf->fd, SIOCSIFNETMASK,&ifr)<0)
  37. return(-1);
  38. }
  39. #endif
  40. if(addr_ntos(&entry->intf_addr,&ifr.ifr_addr)<0)
  41. return(-1);
  42. if(ioctl(intf->fd, SIOCSIFADDR,&ifr)<0&& errno != EEXIST)
  43. return(-1);
  44. // 设置子网掩码
  45. if(addr_btos(entry->intf_addr.addr_bits,&ifr.ifr_addr)==0
  46. #ifdef __linux__
  47. && entry->intf_addr.addr_ip !=0
  48. #endif
  49. ){
  50. if(ioctl(intf->fd, SIOCSIFNETMASK,&ifr)<0)
  51. return(-1);
  52. }
  53. // 设置广播地址
  54. if(addr_bcast(&entry->intf_addr,&bcast)==0){
  55. if(addr_ntos(&bcast,&ifr.ifr_broadaddr)==0){
  56.                 ioctl(intf->fd, SIOCSIFBRDADDR,&ifr);
  57. }
  58. }
  59. }
  60. // 6. 设置MAC地址
  61. if(entry->intf_link_addr.addr_type == ADDR_TYPE_ETH &&
  62.         addr_cmp(&entry->intf_link_addr,&orig->intf_link_addr)!=0){
  63. #if defined(SIOCSIFHWADDR)
  64. // Linux:标准方式
  65. if(addr_ntos(&entry->intf_link_addr,&ifr.ifr_hwaddr)<0)
  66. return(-1);
  67. if(ioctl(intf->fd, SIOCSIFHWADDR,&ifr)<0)
  68. return(-1);
  69. #elif defined (SIOCSIFLLADDR)
  70. // BSD:替代方式
  71.         memcpy(ifr.ifr_addr.sa_data,&entry->intf_link_addr.addr_eth,
  72.             ETH_ADDR_LEN);
  73.         ifr.ifr_addr.sa_len = ETH_ADDR_LEN;
  74. if(ioctl(intf->fd, SIOCSIFLLADDR,&ifr)<0)
  75. return(-1);
  76. #else
  77. // 回退:使用eth模块
  78. eth_t*eth;
  79. if((eth = eth_open(entry->intf_name))== NULL)
  80. return(-1);
  81. if(eth_set(eth,&entry->intf_link_addr.addr_eth)<0){
  82.             eth_close(eth);
  83. return(-1);
  84. }
  85.         eth_close(eth);
  86. #endif
  87. }
  88. // 7. 设置点对点目标地址
  89. if(entry->intf_dst_addr.addr_type == ADDR_TYPE_IP){
  90. if(addr_ntos(&entry->intf_dst_addr,&ifr.ifr_dstaddr)<0)
  91. return(-1);
  92. if(ioctl(intf->fd, SIOCSIFDSTADDR,&ifr)<0&&
  93.             errno != EEXIST)
  94. return(-1);
  95. }
  96. // 8. 添加别名
  97. if(_intf_add_aliases(intf, entry)<0)
  98. return(-1);
  99. // 9. 设置接口标志
  100. if(ioctl(intf->fd, SIOCGIFFLAGS,&ifr)<0)
  101. return(-1);
  102.     ifr.ifr_flags = intf_flags_to_iff(entry->intf_flags, ifr.ifr_flags);
  103. if(ioctl(intf->fd, SIOCSIFFLAGS,&ifr)<0)
  104. return(-1);
  105. return(0);
  106. }

关键ioctl命令

ioctl命令
功能
SIOCSIFMTU
设置MTU
SIOCSIFADDR
设置IP地址
SIOCSIFNETMASK
设置子网掩码
SIOCSIFBRDADDR
设置广播地址
SIOCSIFHWADDR
设置MAC地址(Linux)
SIOCSIFLLADDR
设置链路层地址(BSD)
SIOCSIFDSTADDR
设置点对点目标地址
SIOCSIFFLAGS
设置接口标志

3.8 根据源/目标地址查找接口

3.8.1 根据源地址查找

  1. // src/intf.c:835-861
  2. staticint _match_intf_src(conststruct intf_entry *entry,void*arg)
  3. {
  4. struct intf_entry *save =(struct intf_entry *)arg;
  5. int matched =0, cnt;
  6. // 检查主地址
  7. if(entry->intf_addr.addr_type == ADDR_TYPE_IP &&
  8.         entry->intf_addr.addr_ip == save->intf_addr.addr_ip)
  9.         matched =1;
  10. // 检查别名地址
  11. for(cnt =0;!matched && cnt <(int) entry->intf_alias_num; cnt++){
  12. if(entry->intf_alias_addrs[cnt].addr_type != ADDR_TYPE_IP)
  13. continue;
  14. if(entry->intf_alias_addrs[cnt].addr_ip == save->intf_addr.addr_ip)
  15.             matched =1;
  16. }
  17. if(matched){
  18. if(save->intf_len < entry->intf_len)
  19.             memcpy(save, entry, save->intf_len);
  20. else
  21.             memcpy(save, entry, entry->intf_len);
  22. return(1);
  23. }
  24. return(0);
  25. }
  26. int intf_get_src(intf_t*intf,struct intf_entry *entry,struct addr *src)
  27. {
  28.     memcpy(&entry->intf_addr, src,sizeof(*src));
  29. if(intf_loop(intf, _match_intf_src, entry)!=1){
  30.         errno = ENXIO;
  31. return(-1);
  32. }
  33. return(0);
  34. }

3.8.2 根据目标地址查找

  1. // src/intf.c:875-901
  2. int intf_get_dst(intf_t*intf,struct intf_entry *entry,struct addr *dst)
  3. {
  4. struct sockaddr_in sin;
  5. socklen_t n;
  6. if(dst->addr_type != ADDR_TYPE_IP){
  7.         errno = EINVAL;
  8. return(-1);
  9. }
  10.     addr_ntos(dst,(struct sockaddr *)&sin);
  11.     sin.sin_port = htons(666);
  12. // 尝试连接到目标地址
  13. if(connect(intf->fd,(struct sockaddr *)&sin,sizeof(sin))<0)
  14. return(-1);
  15. // 获取本地地址
  16.     n =sizeof(sin);
  17. if(getsockname(intf->fd,(struct sockaddr *)&sin,&n)<0)
  18. return(-1);
  19.     addr_ston((struct sockaddr *)&sin,&entry->intf_addr);
  20. // 查找对应的接口
  21. if(intf_loop(intf, _match_intf_src, entry)!=1)
  22. return(-1);
  23. return(0);
  24. }

工作原理

  1. 创建到目标地址的UDP连接(不实际发送数据)
  2. 获取本地绑定的源IP地址
  3. 遍历所有接口找到匹配该IP的接口

3.9 标志转换

  1. // src/intf.c:106-146
  2. // 将libdnet标志转换为系统标志
  3. staticint intf_flags_to_iff(u_short flags,int iff)
  4. {
  5. if(flags & INTF_FLAG_UP)
  6.         iff |= IFF_UP;
  7. else
  8.         iff &=~IFF_UP;
  9. if(flags & INTF_FLAG_NOARP)
  10.         iff |= IFF_NOARP;
  11. else
  12.         iff &=~IFF_NOARP;
  13. return(iff);
  14. }
  15. // 将系统标志转换为libdnet标志
  16. static u_int intf_iff_to_flags(uint64_t iff)
  17. {
  18.     u_int n =0;
  19. if(iff & IFF_UP)
  20.         n |= INTF_FLAG_UP;
  21. if(iff & IFF_LOOPBACK)
  22.         n |= INTF_FLAG_LOOPBACK;
  23. if(iff & IFF_POINTOPOINT)
  24.         n |= INTF_FLAG_POINTOPOINT;
  25. if(iff & IFF_NOARP)
  26.         n |= INTF_FLAG_NOARP;
  27. if(iff & IFF_BROADCAST)
  28.         n |= INTF_FLAG_BROADCAST;
  29. if(iff & IFF_MULTICAST)
  30.         n |= INTF_FLAG_MULTICAST;
  31. #ifdef IFF_IPMP
  32. // Solaris IPMP接口:清除广播和多播标志
  33. if(iff & IFF_IPMP)
  34.         n &=~(INTF_FLAG_BROADCAST | INTF_FLAG_MULTICAST);
  35. #endif
  36. return(n);
  37. }

4. macOS/BSD平台实现

4.1 实现文件

主文件: src/intf.c(通用实现,包含BSD特定代码)

4.2 BSD特殊ioctl(lifreq)

Solaris和BSD系统支持长接口请求结构(lifreq):

  1. // src/intf.c:422-487
  2. #ifdef SIOCGLIFCONF
  3. int _intf_get_noalias(intf_t*intf,struct intf_entry *entry)
  4. {
  5. struct lifreq lifr;
  6. int fd;
  7.     entry->intf_index = if_nametoindex(entry->intf_name);
  8. if(entry->intf_index ==0)
  9. return(-1);
  10.     strlcpy(lifr.lifr_name, entry->intf_name,sizeof(lifr.lifr_name));
  11. // 自动选择IPv4或IPv6 socket
  12. if(ioctl(intf->fd, SIOCGLIFFLAGS,&lifr)>=0)
  13.         fd = intf->fd;
  14. elseif(intf->fd6 !=-1&& ioctl(intf->fd6, SIOCGLIFFLAGS,&lifr)>=0)
  15.         fd = intf->fd6;
  16. else
  17. return(-1);
  18.     entry->intf_flags = intf_iff_to_flags(lifr.lifr_flags);
  19.     _intf_set_type(entry);
  20. #ifdef SIOCGLIFMTU
  21. if(ioctl(fd, SIOCGLIFMTU,&lifr)<0)
  22. #endif
  23. return(-1);
  24.     entry->intf_mtu = lifr.lifr_mtu;
  25.     entry->intf_addr.addr_type = entry->intf_dst_addr.addr_type =
  26.         entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;
  27. if(ioctl(fd, SIOCGLIFADDR,&lifr)==0){
  28.         addr_ston((struct sockaddr *)&lifr.lifr_addr,&entry->intf_addr);
  29. if(ioctl(fd, SIOCGLIFNETMASK,&lifr)<0)
  30. return(-1);
  31.         addr_stob((struct sockaddr *)&lifr.lifr_addr,&entry->intf_addr.addr_bits);
  32. }
  33. if(entry->intf_type == INTF_TYPE_TUN){
  34. if(ioctl(fd, SIOCGLIFDSTADDR,&lifr)==0){
  35. if(addr_ston((struct sockaddr *)&lifr.lifr_addr,
  36. &entry->intf_dst_addr)<0)
  37. return(-1);
  38. }
  39. }elseif(entry->intf_type == INTF_TYPE_ETH){
  40. eth_t*eth;
  41. if((eth = eth_open(entry->intf_name))!= NULL){
  42. if(!eth_get(eth,&entry->intf_link_addr.addr_eth)){
  43.                 entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;
  44.                 entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;
  45. }
  46.             eth_close(eth);
  47. }
  48. }
  49. return(0);
  50. }
  51. #endif

BSD专用ioctl命令

ioctl命令
功能
平台
SIOCGLIFCONF
获取长接口配置列表
Solaris/BSD
SIOCGLIFADDR
获取长接口地址
Solaris/BSD
SIOCGLIFNETMASK
获取长接口网络掩码
Solaris/BSD
SIOCGLIFDSTADDR
获取长接口目标地址
Solaris/BSD
SIOCGLIFMTU
获取长接口MTU
Solaris/BSD
SIOCGLIFFLAGS
获取长接口标志
Solaris/BSD

4.3 BSD接口遍历

  1. // src/intf.c:1000-1077
  2. #elif defined(SIOCGLIFCONF)
  3. int intf_loop(intf_t*intf, intf_handler callback,void*arg)
  4. {
  5. struct intf_entry *entry;
  6. struct lifreq *lifr,*llifr,*plifr;
  7. char*p, ebuf[BUFSIZ];
  8. int ret;
  9.     entry =(struct intf_entry *)ebuf;
  10. // 配置lifconf
  11.     intf->lifc.lifc_family = AF_UNSPEC;
  12.     intf->lifc.lifc_flags =0;
  13. #ifdef LIFC_UNDER_IPMP
  14.     intf->lifc.lifc_flags |= LIFC_UNDER_IPMP;// 包含IPMP底层接口
  15. #endif
  16.     intf->lifc.lifc_buf =(caddr_t)intf->ifcbuf;
  17.     intf->lifc.lifc_len =sizeof(intf->ifcbuf);
  18. if(ioctl(intf->fd, SIOCGLIFCONF,&intf->lifc)<0)
  19. return(-1);
  20.     llifr =(struct lifreq *)&intf->lifc.lifc_buf[intf->lifc.lifc_len];
  21. for(lifr = intf->lifc.lifc_req; lifr < llifr; lifr = NEXTLIFR(lifr)){
  22. // 处理别名(:分隔符)
  23. if((= strchr(lifr->lifr_name,':'))!= NULL)
  24. *='\0';
  25. // 跳过重复的接口
  26. for(plifr = intf->lifc.lifc_req; plifr < lifr; plifr = NEXTLIFR(lifr)){
  27. if(strcmp(lifr->lifr_name, plifr->lifr_name)==0)
  28. break;
  29. }
  30. if(lifr > intf->lifc.lifc_req && plifr < lifr)
  31. continue;
  32.         memset(ebuf,0,sizeof(ebuf));
  33.         strlcpy(entry->intf_name, lifr->lifr_name,
  34. sizeof(entry->intf_name));
  35.         strlcpy(entry->os_intf_name, lifr->lifr_name,
  36. sizeof(entry->os_intf_name));
  37.         strlcpy(entry->pcap_intf_name, lifr->lifr_name,
  38. sizeof(entry->pcap_intf_name));
  39.         intf_get_drv_info(intf, entry);
  40.         entry->intf_len =sizeof(ebuf);
  41. // 恢复别名名称
  42. if(p)*=':';
  43. // 跳过IPMP虚拟接口
  44. if(ioctl(intf->fd, SIOCGLIFFLAGS, lifr)>=0)
  45. ;
  46. elseif(intf->fd6 !=-1&& ioctl(intf->fd6, SIOCGLIFFLAGS, lifr)>=0)
  47. ;
  48. else
  49. return(-1);
  50. #ifdef IFF_IPMP
  51. if(lifr->lifr_flags & IFF_IPMP){
  52. continue;// IPMP接口不支持某些操作(如抓包)
  53. }
  54. #endif
  55. if(_intf_get_noalias(intf, entry)<0)
  56. return(-1);
  57. if(_intf_get_aliases(intf, entry)<0)
  58. return(-1);
  59. if((ret =(*callback)(entry, arg))!=0)
  60. return(ret);
  61. }
  62. return(0);
  63. }
  64. #endif

4.4 BSD别名处理

  1. // src/intf.c:628-692
  2. #elif defined(SIOCGLIFCONF)
  3. staticint _intf_get_aliases(intf_t*intf,struct intf_entry *entry)
  4. {
  5. struct lifreq *lifr,*llifr;
  6. struct lifreq tmplifr;
  7. struct addr *ap,*lap;
  8. char*p;
  9. if(intf->lifc.lifc_len <(int)sizeof(*lifr)){
  10.         errno = EINVAL;
  11. return(-1);
  12. }
  13.     entry->intf_alias_num =0;
  14.     ap = entry->intf_alias_addrs;
  15.     llifr =(struct lifreq *)intf->lifc.lifc_buf +
  16. (intf->lifc.lifc_len /sizeof(*llifr));
  17.     lap =(struct addr *)((u_char *)entry + entry->intf_len);
  18. // 遍历lifreq数组
  19. for(lifr = intf->lifc.lifc_req; lifr < llifr &&(ap +1)< lap;
  20.         lifr = NEXTLIFR(lifr)){
  21. // 处理别名
  22. if((= strchr(lifr->lifr_name,':'))!= NULL)
  23. *='\0';
  24. if(strcmp(lifr->lifr_name, entry->intf_name)!=0){
  25. if(p)*=':';
  26. continue;
  27. }
  28. if(p)*=':';
  29. if(addr_ston((struct sockaddr *)&lifr.lifr_addr, ap)<0)
  30. continue;
  31. // 跳过MAC地址(已在主地址中处理)
  32. if(ap->addr_type == ADDR_TYPE_ETH){
  33.             memcpy(&entry->intf_link_addr, ap,sizeof(*ap));
  34. continue;
  35. }elseif(ap->addr_type == ADDR_TYPE_IP){
  36. if(ap->addr_ip == entry->intf_addr.addr_ip ||
  37.                 ap->addr_ip == entry->intf_dst_addr.addr_ip)
  38. continue;
  39.             strlcpy(tmplifr.lifr_name, lifr->lifr_name,sizeof(tmplifr.lifr_name));
  40. if(ioctl(intf->fd, SIOCGIFNETMASK,&tmplifr)==0)
  41.                 addr_stob((struct sockaddr *)&tmplifr.lifr_addr,&ap->addr_bits);
  42. }elseif(ap->addr_type == ADDR_TYPE_IP6 && intf->fd6 !=-1){
  43. if(memcmp(&ap->addr_ip6,&entry->intf_addr.addr_ip6, IP6_ADDR_LEN)==0||
  44.                 memcmp(&ap->addr_ip6,&entry->intf_dst_addr.addr_ip6, IP6_ADDR_LEN)==0)
  45. continue;
  46.             strlcpy(tmplifr.lifr_name, lifr->lifr_name,sizeof(tmplifr.lifr_name));
  47. if(ioctl(intf->fd6, SIOCGLIFNETMASK,&tmplifr)==0){
  48.                 addr_stob((struct sockaddr *)&tmplifr.lifr_addr,
  49. &ap->addr_bits);
  50. }
  51. }
  52.         ap++, entry->intf_alias_num++;
  53. }
  54.     entry->intf_len =(u_char *)ap -(u_char *)entry;
  55. return(0);
  56. }
  57. #endif

4.5 BSD sockaddr对齐处理

BSD系统使用 sa_len字段处理变长sockaddr:

  1. // src/intf.c:70-77
  2. #ifdef HAVE_SOCKADDR_SA_LEN
  3. #define max(a, b)((a)>(b)?(a):(b))
  4. #define NEXTIFR(i)((struct ifreq *) \
  5.                 max((u_char *)+sizeof(struct ifreq), \
  6. (u_char *)&i->ifr_addr + i->ifr_addr.sa_len))
  7. #else
  8. #define NEXTIFR(i)(+1)
  9. #endif
  10. #define NEXTLIFR(i)(+1)

说明

  • BSD:sockaddr包含 sa_len字段,需要按实际大小对齐
  • Linux:sockaddr固定长度,直接递增指针

5. Windows平台实现

5.1 实现文件

主文件: src/intf-win32.c

5.2 接口句柄结构

  1. // src/intf-win32.c:35-38
  2. struct intf_handle {
  3. struct ifcombo  ifcombo[MIB_IF_TYPE_MAX];// 按类型分组的接口索引映射
  4.     IP_ADAPTER_ADDRESSES *iftable;// 适配器地址表
  5. };

5.3 接口组合结构

  1. // src/intf-win32.c:22-29
  2. struct ifcombo {
  3. struct{
  4.         DWORD   ipv4;// IPv4接口索引
  5.         DWORD   ipv6;// IPv6接口索引
  6. }*idx;
  7. int     cnt;// 当前接口数量
  8. int     max;// 最大接口数量
  9. };

作用:将Windows的原始接口索引映射为libdnet的友好接口名(eth0, eth1等)

5.4 打开接口句柄

  1. // src/intf-win32.c:306-310
  2. intf_t*intf_open(void)
  3. {
  4. return(calloc(1,sizeof(intf_t)));
  5. }

注意:Windows实现延迟初始化,在首次操作时刷新接口表。

5.5 刷新接口表

  1. // src/intf-win32.c:219-269
  2. staticint _refresh_tables(intf_t*intf)
  3. {
  4.     IP_ADAPTER_ADDRESSES *p;
  5.     DWORD ret;
  6.     ULONG len;
  7.     p = NULL;
  8.     len =16384;// 初始缓冲区大小
  9. do{
  10.         free(p);
  11.         p = malloc(len);
  12. if(== NULL)
  13. return(-1);
  14. // 获取适配器地址信息
  15.         ret =GetAdaptersAddresses(AF_UNSPEC,
  16.                                   GAA_FLAG_INCLUDE_PREFIX |
  17.                                   GAA_FLAG_SKIP_ANYCAST |
  18.                                   GAA_FLAG_SKIP_MULTICAST,
  19.                                   NULL, p,&len);
  20. }while(ret == ERROR_BUFFER_OVERFLOW);
  21. if(ret != NO_ERROR){
  22.         free(p);
  23. return(-1);
  24. }
  25.     intf->iftable = p;
  26. // 建立接口索引映射
  27. for(= intf->iftable; p != NULL; p = p->Next){
  28. int type;
  29.         type = _if_type_canonicalize(p->IfType);
  30. if(type < MIB_IF_TYPE_MAX)
  31.             _ifcombo_add(&intf->ifcombo[type], p->IfIndex, p->Ipv6IfIndex);
  32. else
  33. return(-1);
  34. }
  35. return(0);
  36. }

GetAdaptersAddresses参数

标志
说明
GAA_FLAG_INCLUDE_PREFIX
包含前缀信息(网络掩码)
GAA_FLAG_SKIP_ANYCAST
跳过任播地址
GAA_FLAG_SKIP_MULTICAST
跳过多播地址

5.6 接口类型转换

  1. // src/intf-win32.c:51-72
  2. staticchar*_ifcombo_name(int type)
  3. {
  4. char*name ="eth";
  5. if(type == IF_TYPE_ISO88025_TOKENRING){
  6.         name ="tr";
  7. }elseif(type == IF_TYPE_PPP){
  8.         name ="ppp";
  9. }elseif(type == IF_TYPE_SOFTWARE_LOOPBACK){
  10.         name ="lo";
  11. }elseif(type == IF_TYPE_TUNNEL){
  12.         name ="tun";
  13. }
  14. return(name);
  15. }
  16. // src/intf-win32.c:118-122
  17. staticint _if_type_canonicalize(int type)
  18. {
  19. return _ifcombo_type(_ifcombo_name(type));
  20. }

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 适配器地址转换

  1. // src/intf-win32.c:124-217
  2. staticvoid _adapter_address_to_entry(intf_t*intf, IP_ADAPTER_ADDRESSES *a,
  3. struct intf_entry *entry)
  4. {
  5. struct addr *ap,*lap;
  6. int i;
  7. int type;
  8.     IP_ADAPTER_UNICAST_ADDRESS *addr;
  9.     u_int intf_len = entry->intf_len;
  10.     memset(entry,0,sizeof(*entry));
  11.     entry->intf_len = intf_len;
  12. // 1. 确定接口索引
  13.     type = _if_type_canonicalize(a->IfType);
  14. for(=0; i < intf->ifcombo[type].cnt; i++){
  15. if(intf->ifcombo[type].idx[i].ipv4 == a->IfIndex&&
  16.             intf->ifcombo[type].idx[i].ipv6 == a->Ipv6IfIndex){
  17. break;
  18. }
  19. }
  20. // 2. 设置接口名称
  21.     snprintf(entry->intf_name,sizeof(entry->intf_name),"%s%u",
  22.         _ifcombo_name(a->IfType), i);
  23.     entry->intf_type =(uint16_t)type;
  24. // 3. 设置操作系统接口名称
  25. char friendly_name[MAX_INTERFACE_NAME_LEN];
  26.     strncpy_wchar(friendly_name, a->FriendlyName, MAX_INTERFACE_NAME_LEN);
  27.     snprintf(entry->os_intf_name, MAX_INTERFACE_NAME_LEN,"%s %s", friendly_name,
  28.         a->AdapterName);
  29. // 4. 设置pcap接口名称
  30. constchar*name_start = strstr(a->AdapterName,"{");
  31. if(name_start){
  32.         snprintf(entry->pcap_intf_name, MAX_INTERFACE_NAME_LEN -1,
  33. "\\Device\\NPF_%s", name_start);
  34. }
  35. // 5. 设置驱动名称
  36.     strncpy_wchar(entry->driver_name, a->Description,sizeof(entry->driver_name));
  37. // 6. 设置接口标志
  38.     entry->intf_flags =0;
  39. if(a->OperStatus==IfOperStatusUp)
  40.         entry->intf_flags |= INTF_FLAG_UP;
  41. if(a->IfType== IF_TYPE_SOFTWARE_LOOPBACK)
  42.         entry->intf_flags |= INTF_FLAG_LOOPBACK;
  43. else
  44.         entry->intf_flags |= INTF_FLAG_MULTICAST;
  45. // 7. 设置MTU
  46.     entry->intf_mtu = a->Mtu;
  47. // 8. 设置MAC地址
  48. if(a->PhysicalAddressLength== ETH_ADDR_LEN){
  49.         entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;
  50.         entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;
  51.         memcpy(&entry->intf_link_addr.addr_eth, a->PhysicalAddress,
  52.             ETH_ADDR_LEN);
  53. }
  54. // 9. 设置IP地址和别名
  55.     ap = entry->intf_alias_addrs;
  56.     lap = ap +((entry->intf_len -sizeof(*entry))/
  57. sizeof(entry->intf_alias_addrs[0]));
  58. for(addr = a->FirstUnicastAddress; addr != NULL; addr = addr->Next){
  59.         IP_ADAPTER_PREFIX *prefix;
  60. unsignedshort bits;
  61. // 查找前缀长度(网络掩码位数)
  62.         bits =0;
  63. for(prefix = a->FirstPrefix; prefix != NULL; prefix = prefix->Next){
  64. if(prefix->Address.lpSockaddr->sa_family ==
  65.                 addr->Address.lpSockaddr->sa_family){
  66.                 bits =(unsignedshort) prefix->PrefixLength;
  67. break;
  68. }
  69. }
  70. if(entry->intf_addr.addr_type == ADDR_TYPE_NONE){
  71. // 设置主地址
  72.             addr_ston(addr->Address.lpSockaddr,&entry->intf_addr);
  73.             entry->intf_addr.addr_bits = bits;
  74. }elseif(ap < lap){
  75. // 设置别名
  76.             addr_ston(addr->Address.lpSockaddr, ap);
  77.             ap->addr_bits = bits;
  78.             ap++;
  79.             entry->intf_alias_num++;
  80. }
  81. }
  82.     entry->intf_len =(u_int)((u_char *)ap -(u_char *)entry);
  83. }

5.8 获取接口信息

  1. // src/intf-win32.c:312-327
  2. int intf_get(intf_t*intf,struct intf_entry *entry)
  3. {
  4.     IP_ADAPTER_ADDRESSES *a;
  5. if(_refresh_tables(intf)<0)
  6. return(-1);
  7.     a = _find_adapter_address(intf, entry->intf_name);
  8. if(== NULL)
  9. return(-1);
  10.     _adapter_address_to_entry(intf, a, entry);
  11. return(0);
  12. }

5.9 通过索引查找接口

  1. // src/intf-win32.c:271-289
  2. static IP_ADAPTER_ADDRESSES *
  3. _find_adapter_address(intf_t*intf,constchar*device)
  4. {
  5.     IP_ADAPTER_ADDRESSES *a;
  6. char*=(char*)device;
  7. int n, type = _ifcombo_type(device);
  8. // 解析接口名:eth0 → type=INTF_TYPE_ETH, n=0
  9. while(isalpha((int)(unsignedchar)*p)) p++;
  10.     n = atoi(p);
  11. for(= intf->iftable; a != NULL; a = a->Next){
  12. if(intf->ifcombo[type].idx[n].ipv4 == a->IfIndex&&
  13.             intf->ifcombo[type].idx[n].ipv6 == a->Ipv6IfIndex){
  14. return a;
  15. }
  16. }
  17. return NULL;
  18. }
  19. // src/intf-win32.c:291-304
  20. static IP_ADAPTER_ADDRESSES *
  21. _find_adapter_address_by_index(intf_t*intf,int af,unsignedint index)
  22. {
  23.     IP_ADAPTER_ADDRESSES *a;
  24. for(= intf->iftable; a != NULL; a = a->Next){
  25. if(af == AF_INET && index == a->IfIndex)
  26. return a;
  27. if(af == AF_INET6 && index == a->Ipv6IfIndex)
  28. return a;
  29. }
  30. return NULL;
  31. }
  32. int intf_get_index(intf_t*intf,struct intf_entry *entry,int af,unsignedint index)
  33. {
  34.     IP_ADAPTER_ADDRESSES *a;
  35. if(_refresh_tables(intf)<0)
  36. return(-1);
  37.     a = _find_adapter_address_by_index(intf, af, index);
  38. if(== NULL)
  39. return(-1);
  40.     _adapter_address_to_entry(intf, a, entry);
  41. return(0);
  42. }

5.10 根据源地址查找接口

  1. // src/intf-win32.c:347-373
  2. int intf_get_src(intf_t*intf,struct intf_entry *entry,struct addr *src)
  3. {
  4.     IP_ADAPTER_ADDRESSES *a;
  5.     IP_ADAPTER_UNICAST_ADDRESS *addr;
  6. if(src->addr_type != ADDR_TYPE_IP){
  7.         errno = EINVAL;
  8. return(-1);
  9. }
  10. if(_refresh_tables(intf)<0)
  11. return(-1);
  12. for(= intf->iftable; a != NULL; a = a->Next){
  13. for(addr = a->FirstUnicastAddress; addr != NULL; addr = addr->Next){
  14. struct addr dnet_addr;
  15.             addr_ston(addr->Address.lpSockaddr,&dnet_addr);
  16. if(addr_cmp(&dnet_addr, src)==0){
  17.                 _adapter_address_to_entry(intf, a, entry);
  18. return(0);
  19. }
  20. }
  21. }
  22.     errno = ENXIO;
  23. return(-1);
  24. }

5.11 遍历所有接口

  1. // src/intf-win32.c:396-416
  2. int intf_loop(intf_t*intf, intf_handler callback,void*arg)
  3. {
  4.     IP_ADAPTER_ADDRESSES *a;
  5. struct intf_entry *entry;
  6.     u_char ebuf[1024];
  7. int ret;
  8. if(_refresh_tables(intf)<0)
  9. return(-1);
  10.     entry =(struct intf_entry *)ebuf;
  11. for(= intf->iftable; a != NULL; a = a->Next){
  12.         entry->intf_len =sizeof(ebuf);
  13.         _adapter_address_to_entry(intf, a, entry);
  14. if((ret =(*callback)(entry, arg))!=0)
  15. break;
  16. }
  17. return(ret);
  18. }

5.12 关闭接口句柄

  1. // src/intf-win32.c:418-433
  2. intf_t*intf_close(intf_t*intf)
  3. {
  4. int i;
  5. if(intf != NULL){
  6. for(=0; i < MIB_IF_TYPE_MAX; i++){
  7. if(intf->ifcombo[i].idx)
  8.                 free(intf->ifcombo[i].idx);
  9. }
  10. if(intf->iftable)
  11.             free(intf->iftable);
  12.         free(intf);
  13. }
  14. return(NULL);
  15. }

5.13 Windows限制

不支持的操作

  1. // src/intf-win32.c:375-394
  2. // 根据目标地址查找接口(Windows不支持)
  3. int intf_get_dst(intf_t*intf,struct intf_entry *entry,struct addr *dst)
  4. {
  5.     errno = ENOSYS;
  6. SetLastError(ERROR_NOT_SUPPORTED);
  7. return(-1);
  8. }
  9. // 设置接口配置(Windows支持有限)
  10. int intf_set(intf_t*intf,conststruct intf_entry *entry)
  11. {
  12. // 可以使用SetIfEntry()设置接口up/down
  13. // 但无法设置地址等配置
  14.     errno = ENOSYS;
  15. SetLastError(ERROR_NOT_SUPPORTED);
  16. return(-1);
  17. }

6. 跨平台对比分析

6.1 API设计对比

功能
Linux/Unix
macOS/BSD
Windows
获取接口列表 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
Linux/BSD
标准接口请求结构
structlifreq
Solaris/BSD
长接口请求结构(支持IPv6)
structifconf
Linux/BSD
接口配置列表
structlifconf
Solaris/BSD
长接口配置列表
IP_ADAPTER_ADDRESSES
Windows
适配器地址信息
IP_ADAPTER_UNICAST_ADDRESS
Windows
单播地址列表

6.3 操作方式对比

Linux/Unix

  1. // 1. 创建socket
  2. int fd = socket(AF_INET, SOCK_DGRAM,0);
  3. // 2. 配置ifreq
  4. struct ifreq ifr;
  5. strlcpy(ifr.ifr_name,"eth0", IFNAMSIZ);
  6. // 3. 执行ioctl
  7. ioctl(fd, SIOCGIFADDR,&ifr);
  8. // 4. 解析结果
  9. addr_ston(&ifr.ifr_addr,&addr);

Windows

  1. // 1. 分配缓冲区
  2. ULONG len =16384;
  3. IP_ADAPTER_ADDRESSES *= malloc(len);
  4. // 2. 获取适配器信息
  5. GetAdaptersAddresses(AF_UNSPEC, flags, NULL, p,&len);
  6. // 3. 遍历适配器列表
  7. for(IP_ADAPTER_ADDRESSES *= p; a != NULL; a = a->Next){
  8. // 处理适配器
  9. }
  10. // 4. 释放缓冲区
  11. free(p);

6.4 性能对比

操作
Linux
macOS/BSD
Windows
获取单个接口
快(ioctl)
快(ioctl)
慢(刷新整个表)
遍历所有接口
快(/proc)
中(ioctl列表)
慢(API调用)
查找接口
中(线性搜索)
中(线性搜索)
慢(线性搜索)
设置接口
快(ioctl)
快(ioctl)
不支持

6.5 权限要求对比

操作
Linux/Unix
macOS/BSD
Windows
读取接口信息
普通用户
普通用户
普通用户
设置接口信息
root
root
管理员(不支持)
获取驱动信息
root
不支持
普通用户

6.6 IPv6支持对比

平台
支持方式
备注
Linux /proc/net/if_inet6

 + SIOCGIFNETMASK_IN6
完整支持
macOS/BSD SIOCGLIFCONF
完整支持
Windows GetAdaptersAddresses()
原生支持
Solaris SIOCGLIFCONF
完整支持

6.7 代码复杂度对比

文件
行数
平台支持
src/intf.c
1146
Linux/BSD/Unix
src/intf-win32.c
434
Windows

复杂度分析

  • Unix版本:需要处理多个平台的差异(ioctl变体、proc文件系统、getkerninfo等)
  • Windows版本:逻辑简单但API复杂(需要处理Unicode、适配器地址链表等)

6.8 维护性分析

方面
Linux/Unix
Windows
API稳定性
高(标准POSIX)
中(Windows API变化)
平台差异
多(需要大量宏)
单(统一API)
代码复用
低(每个平台单独处理)
高(统一实现)
测试难度
高(需要多平台测试)
低(单一平台)

7. 实际应用示例

7.1 基本用法:显示所有接口

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. staticint print_intf(conststruct intf_entry *entry,void*arg)
  4. {
  5. int i;
  6.     printf("接口: %s\n", entry->intf_name);
  7.     printf("  类型: %d\n", entry->intf_type);
  8.     printf("  标志: 0x%x\n", entry->intf_flags);
  9.     printf("  MTU: %u\n", entry->intf_mtu);
  10.     printf("  索引: %u\n", entry->intf_index);
  11. if(entry->intf_addr.addr_type == ADDR_TYPE_IP){
  12.         printf("  IP地址: %s/%d\n",
  13.                addr_ntoa(&entry->intf_addr),
  14.                entry->intf_addr.addr_bits);
  15. }
  16. if(entry->intf_link_addr.addr_type == ADDR_TYPE_ETH){
  17.         printf("  MAC地址: %s\n",
  18.                addr_ntoa(&entry->intf_link_addr));
  19. }
  20. for(=0; i < entry->intf_alias_num; i++){
  21.         printf("  别名%d: %s/%d\n", i +1,
  22.                addr_ntoa(&entry->intf_alias_addrs[i]),
  23.                entry->intf_alias_addrs[i].addr_bits);
  24. }
  25.     printf("\n");
  26. return(0);
  27. }
  28. int main(void)
  29. {
  30. intf_t*intf;
  31. if((intf = intf_open())== NULL){
  32.         perror("intf_open");
  33. return(1);
  34. }
  35. if(intf_loop(intf, print_intf, NULL)<0){
  36.         perror("intf_loop");
  37. return(1);
  38. }
  39.     intf_close(intf);
  40. return(0);
  41. }

7.2 获取指定接口信息

  1. int main(void)
  2. {
  3. intf_t*intf;
  4. struct intf_entry entry;
  5. char buf[1024];
  6.     memset(&entry,0,sizeof(entry));
  7.     entry.intf_len =sizeof(buf);
  8. if((intf = intf_open())== NULL){
  9.         perror("intf_open");
  10. return(1);
  11. }
  12. // 获取eth0接口信息
  13.     strlcpy(entry.intf_name,"eth0",sizeof(entry.intf_name));
  14. if(intf_get(intf,&entry)<0){
  15.         perror("intf_get");
  16. return(1);
  17. }
  18.     printf("接口: %s\n", entry.intf_name);
  19.     printf("  IP: %s\n", addr_ntoa(&entry.intf_addr));
  20.     printf("  MAC: %s\n", addr_ntoa(&entry.intf_link_addr));
  21.     printf("  MTU: %u\n", entry.intf_mtu);
  22.     intf_close(intf);
  23. return(0);
  24. }

7.3 根据源地址查找接口

  1. int main(void)
  2. {
  3. intf_t*intf;
  4. struct intf_entry entry;
  5. struct addr src;
  6. char buf[1024];
  7.     memset(&entry,0,sizeof(entry));
  8.     entry.intf_len =sizeof(buf);
  9. if((intf = intf_open())== NULL){
  10.         perror("intf_open");
  11. return(1);
  12. }
  13. // 解析源地址
  14. if(addr_pton("192.168.1.100",&src)<0){
  15.         perror("addr_pton");
  16. return(1);
  17. }
  18. // 查找包含该地址的接口
  19. if(intf_get_src(intf,&entry,&src)<0){
  20.         perror("intf_get_src");
  21. return(1);
  22. }
  23.     printf("包含地址 %s 的接口: %s\n",
  24.            addr_ntoa(&src), entry.intf_name);
  25.     intf_close(intf);
  26. return(0);
  27. }

7.4 设置接口配置(Unix)

  1. int main(void)
  2. {
  3. intf_t*intf;
  4. struct intf_entry entry;
  5. struct addr addr;
  6. char buf[1024];
  7.     memset(&entry,0,sizeof(entry));
  8.     entry.intf_len =sizeof(buf);
  9. if((intf = intf_open())== NULL){
  10.         perror("intf_open");
  11. return(1);
  12. }
  13. // 设置接口名称
  14.     strlcpy(entry.intf_name,"eth0",sizeof(entry.intf_name));
  15. // 设置IP地址
  16. if(addr_pton("192.168.1.100/24",&addr)<0){
  17.         perror("addr_pton");
  18. return(1);
  19. }
  20.     entry.intf_addr = addr;
  21. // 设置MAC地址
  22. if(addr_pton("00:11:22:33:44:55",&addr)<0){
  23.         perror("addr_pton");
  24. return(1);
  25. }
  26.     entry.intf_link_addr = addr;
  27. // 设置MTU
  28.     entry.intf_mtu =1500;
  29. // 设置标志
  30.     entry.intf_flags = INTF_FLAG_UP | INTF_FLAG_BROADCAST;
  31. // 应用配置
  32. if(intf_set(intf,&entry)<0){
  33.         perror("intf_set");
  34. return(1);
  35. }
  36.     printf("接口配置成功\n");
  37.     intf_close(intf);
  38. return(0);
  39. }

7.5 网络接口监控工具

  1. #include<dnet.h>
  2. #include<unistd.h>
  3. staticint print_intf(conststruct intf_entry *entry,void*arg)
  4. {
  5.     printf("%-10s %-15s %-20s MTU:%5u Flags:%04x\n",
  6.            entry->intf_name,
  7.            entry->intf_addr.addr_type == ADDR_TYPE_IP ?
  8.                addr_ntoa(&entry->intf_addr):"N/A",
  9.            entry->intf_link_addr.addr_type == ADDR_TYPE_ETH ?
  10.                addr_ntoa(&entry->intf_link_addr):"N/A",
  11.            entry->intf_mtu,
  12.            entry->intf_flags);
  13. return(0);
  14. }
  15. int main(int argc,char*argv[])
  16. {
  17. intf_t*intf;
  18. int interval =5;
  19. if(argc >1)
  20.         interval = atoi(argv[1]);
  21. if((intf = intf_open())== NULL){
  22.         perror("intf_open");
  23. return(1);
  24. }
  25.     printf("网络接口监控(每%d秒刷新):\n", interval);
  26.     printf("%-10s %-15s %-20s %s\n",
  27. "接口","IP地址","MAC地址","配置");
  28.     printf("--------------------------------------------------------\n");
  29. while(1){
  30.         system("clear");// Linux/macOS
  31. // system("cls");  // Windows
  32. if(intf_loop(intf, print_intf, NULL)<0){
  33.             perror("intf_loop");
  34. return(1);
  35. }
  36.         sleep(interval);
  37. }
  38.     intf_close(intf);
  39. return(0);
  40. }

7.6 使用dnet命令行工具

  1. # 显示所有接口
  2. $ dnet intf show
  3. # 获取指定接口
  4. $ dnet intf get eth0
  5. # 根据源地址查找接口
  6. $ dnet intf src 192.168.1.100
  7. # 根据目标地址查找接口
  8. $ dnet intf dst 8.8.8.8
  9. # 设置接口(Unix)
  10. $ sudo dnet intf set eth0 inet 192.168.1.100/24 up
  11. # 添加别名地址
  12. $ sudo dnet intf set eth0 alias 192.168.1.101/24
  13. # 禁用接口
  14. $ sudo dnet intf set eth0 down

8. 常见问题与解决方案

8.1 权限问题

问题

  1. intf_set:Operationnot permitted

原因:修改接口配置需要root权限

解决方案

  1. # Linux/macOS
  2. sudo ./your_program
  3. # Windows(管理员权限)
  4. 右键以管理员身份运行

8.2 接口未找到

问题

  1. intf_get:No such device

原因:接口名称错误或接口不存在

解决方案

  1. // 首先列出所有接口
  2. intf_loop(intf, print_intf, NULL);
  3. // 确认接口名称后再获取
  4. strlcpy(entry.intf_name,"eth0",sizeof(entry.intf_name));

8.3 IPv6别名未显示

问题:IPv6别名地址未出现在列表中

原因:Linux需要从 /proc/net/if_inet6读取

解决方案

  1. // 确保编译时启用了proc文件系统支持
  2. #ifdef HAVE_LINUX_PROCFS
  3. // 代码会自动从/proc/net/if_inet6读取
  4. #endif

8.4 macOS/BSD sockaddr对齐问题

问题:遍历接口时出现段错误或数据错误

原因:BSD系统的sockaddr结构是变长的

解决方案

  1. // 使用NEXTIFR宏正确处理对齐
  2. #ifdef HAVE_SOCKADDR_SA_LEN
  3. #define NEXTIFR(i)((struct ifreq *) \
  4.     max((u_char *)+sizeof(struct ifreq), \
  5. (u_char *)&i->ifr_addr + i->ifr_addr.sa_len))
  6. #else
  7. #define NEXTIFR(i)(+1)
  8. #endif

8.5 Windows GetAdaptersAddresses失败

问题

  1. _refresh_tables: ERROR_INVALID_PARAMETER

原因:缓冲区大小不足或多次调用失败

解决方案

  1. // 使用足够大的初始缓冲区
  2. ULONG len =16384;// 16KB初始缓冲区
  3. // 或者动态调整大小
  4. do{
  5.     free(p);
  6.     len *=2;// 每次翻倍
  7.     p = malloc(len);
  8.     ret =GetAdaptersAddresses(..., p,&len);
  9. }while(ret == ERROR_BUFFER_OVERFLOW);

8.6 Solaris IPMP接口问题

问题:IPMP虚拟接口无法正常工作

原因:IPMP接口不支持某些操作(如抓包)

解决方案

  1. // 跳过IPMP接口
  2. #ifdef IFF_IPMP
  3. if(lifr->lifr_flags & IFF_IPMP){
  4. continue;// 使用底层物理接口
  5. }
  6. #endif

8.7 AIX MAC地址获取失败

问题: SIOCGIFHWADDR在AIX上失败

原因:AIX不使用标准ioctl获取MAC地址

解决方案

  1. // 使用getkerninfo获取
  2. size = getkerninfo(KINFO_NDD,0,0,0);
  3. nddp =(struct kinfo_ndd *)malloc(size);
  4. getkerninfo(KINFO_NDD, nddp,&size,0);
  5. // 查找匹配的接口
  6. while(nddp < end){
  7. if(!strcmp(nddp->ndd_alias, entry->intf_name)){
  8.         addr_pack(&entry->intf_link_addr, ADDR_TYPE_ETH,
  9.                   ETH_ADDR_BITS, nddp->ndd_addr, ETH_ADDR_LEN);
  10. break;
  11. }
  12.     nddp++;
  13. }

8.8 Linux接口名称过长

问题

  1. intf_set:Numerical result out of range

原因:接口名称超过 IFNAMSIZ限制

解决方案

  1. // 检查接口名称长度
  2. if(strlen(entry->intf_name)>= IFNAMSIZ){
  3.     fprintf(stderr,"接口名称过长(最大 %d 字符)\n", IFNAMSIZ -1);
  4. return(-1);
  5. }
  6. // 安全复制
  7. strlcpy(ifr.ifr_name, entry->intf_name,sizeof(ifr.ifr_name));

8.9 性能优化:缓存接口表

问题:频繁调用 intf_get导致性能下降

原因:Windows实现每次都刷新整个接口表

解决方案

  1. // 应用层缓存
  2. struct intf_cache {
  3. time_t timestamp;
  4. intf_t*intf;
  5. struct intf_entry entries[MAX_INTERFACES];
  6. int count;
  7. };
  8. staticstruct intf_cache cache ={0};
  9. int cached_intf_get(constchar*name,struct intf_entry *entry)
  10. {
  11. time_t now = time(NULL);
  12. // 缓存有效期为60秒
  13. if(now - cache.timestamp >60){
  14. // 刷新缓存
  15.         cache.timestamp = now;
  16.         cache.count =0;
  17.         intf_loop(cache.intf, cache_intf_callback,&cache);
  18. }
  19. // 从缓存中查找
  20. for(int i =0; i < cache.count; i++){
  21. if(strcmp(cache.entries[i].intf_name, name)==0){
  22.             memcpy(entry,&cache.entries[i], entry->intf_len);
  23. return(0);
  24. }
  25. }
  26. return(-1);
  27. }

8.10 调试技巧

启用详细日志

  1. #define DEBUG_INTF 1
  2. #ifdef DEBUG_INTF
  3. #define DEBUG(fmt,...) fprintf(stderr,"[DEBUG] " fmt "\n",##__VA_ARGS__)
  4. #else
  5. #define DEBUG(fmt,...)
  6. #endif
  7. // 在关键位置添加日志
  8. DEBUG("获取接口: %s", entry->intf_name);
  9. DEBUG("ioctl返回: %d", ret);
  10. DEBUG("errno: %d (%s)", errno, strerror(errno));

使用系统工具验证

  1. # Linux
  2. ip addr show
  3. ifconfig
  4. cat /proc/net/dev
  5. cat /proc/net/if_inet6
  6. # macOS/BSD
  7. ifconfig
  8. netstat -in
  9. # Windows
  10. ipconfig /all

总结

本文档深入分析了libdnet网络接口控制模块的跨平台实现,涵盖了以下核心内容:

主要特点

  1. 三大平台支持
  • Linux/Unix:基于ioctl和proc文件系统
  • macOS/BSD:基于ioctl和长接口请求(lifreq)
  • Windows:基于IP Helper API
  1. 核心功能
  • 接口信息查询(IP、MAC、MTU、标志等)
  • 接口配置(仅Unix支持)
  • 别名地址管理
  • 接口遍历和查找
  • 驱动信息获取(Linux)
  1. 跨平台适配
  • 条件编译处理平台差异
  • 统一的API接口
  • 灵活的数据结构设计

实现亮点

  • Linux:混合使用ioctl、proc文件系统和ethtool
  • BSD:支持lifreq处理IPv6,智能sockaddr对齐
  • Windows:完整的IP Helper API集成,友好的接口名映射
  • AIX:使用getkerninfo获取MAC地址

使用建议

  1. 读取优先:优先使用读取操作,避免频繁修改
  2. 缓存机制:对频繁访问的接口信息实现缓存
  3. 错误处理:正确处理平台特定的错误码
  4. 权限管理:修改配置需要root/管理员权限

通过本文档,开发者可以全面理解libdnet网络接口控制模块的设计思想和实现细节,并在不同平台上正确使用该库。

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

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx

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

猜你喜欢

  • 暂无文章