乐于分享
好东西不私藏

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

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

网:http://securitytech.cc

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

MAC地址处理深入分析

目录

  1. MAC地址基础概念
  2. libdnet以太网接口设计
  3. Linux平台实现
  4. macOS/BSD平台实现
  5. Solaris平台实现
  6. HP-UX平台实现
  7. AIX平台实现
  8. Windows平台实现
  9. 跨平台对比分析
  10. 实际应用示例
  11. 常见问题与解决方案

1. MAC地址基础概念

1.1 MAC地址简介

MAC地址(Media Access Control Address)是网络设备的物理地址,用于数据链路层的设备标识。MAC地址具有以下特点:

  • 长度:48位(6字节)
  • 格式:十六进制表示,如 00:11:22:33:44:55
  • 唯一性:理论上全球唯一
  • 可变性:大多数设备可以修改

1.2 MAC地址结构

  1. // include/dnet/eth.h:25-27
  2. typedefstruct eth_addr {
  3. uint8_t data[ETH_ADDR_LEN];// 6字节数组
  4. }eth_addr_t;

1.3 MAC地址分类

1.3.1 单播地址(Unicast)

  • 第一个字节的最低位为0
  • 例如: 00:11:22:33:44:55
  • 用于点对点通信

1.3.2 多播地址(Multicast)

  • 第一个字节的最低位为1
  • 例如: 01:00:5e:00:00:01
  • 用于组播通信

1.3.3 广播地址(Broadcast)

  • 所有48位都为1
  • ff:ff:ff:ff:ff:ff
  • 用于广播通信

判断宏

  1. // include/dnet/eth.h:70
  2. #define ETH_IS_MULTICAST(ea)(*(ea)&0x01)// 检查是否为多播/广播

1.4 以太网帧结构

  1. // include/dnet/eth.h:29-33
  2. struct eth_hdr {
  3. eth_addr_t eth_dst;// 目标MAC地址(6字节)
  4. eth_addr_t eth_src;// 源MAC地址(6字节)
  5. uint16_t eth_type;// 以太网类型/长度(2字节)
  6. };

以太网帧长度

  • 最小帧长:64字节(包含4字节CRC)
  • 最大帧长:1518字节(包含4字节CRC)
  • MTU:1500字节(不包括以太网头和CRC)

常量定义

  1. // include/dnet/eth.h:13-23
  2. #define ETH_ADDR_LEN     6// MAC地址长度
  3. #define ETH_ADDR_BITS    48// MAC地址位数
  4. #define ETH_TYPE_LEN     2// 以太网类型字段长度
  5. #define ETH_CRC_LEN      4// CRC校验长度
  6. #define ETH_HDR_LEN      14// 以太网头长度
  7. #define ETH_LEN_MIN      64// 最小帧长
  8. #define ETH_LEN_MAX      1518// 最大帧长
  9. #define ETH_MTU          1500// 最大传输单元

1.5 以太网类型(EtherType)

常见的以太网类型值:

EtherType
名称
说明
0x0200
PUP
Xerox PUP协议
0x0800
IP
IPv4协议
0x0806
ARP
地址解析协议
0x8035
RARP
反向地址解析协议
0x8100
802.1Q
VLAN标签
0x86DD
IPv6
IPv6协议
0x8847
MPLS
MPLS单播
0x8848
MPLS MCAST
MPLS组播
0x8863
PPPOEDISC
PPPoE发现阶段
0x8864
PPPOE
PPPoE会话阶段
0x88a8
802.1ad
双标签VLAN
0x9000
Loopback
回环测试
0x9100/9200/9300
802.1ad
Cisco双标签VLAN

1.6 VLAN标签结构

  1. // include/dnet/eth.h:54-61
  2. struct eth_8021q_hdr {
  3. uint16_t priority_c_vid;// 优先级|VLAN ID(TCI)
  4. uint16_t len_eth_type;// 长度或类型
  5. };
  6. #define ETH_8021Q_PRIMASK   0x0007// 优先级掩码(3位)
  7. #define ETH_8021Q_CFIMASK   0x0001// 规范格式标识符(1位)
  8. #define ETH_8021Q_VIDMASK   0x0fff// VLAN ID掩码(12位)

VLAN类型判断

  1. // include/dnet/eth.h:63-68
  2. #define ETH_TYPE_IS_VLAN(type) \
  3. (((type)== ETH_TYPE_8021Q)|| \
  4. ((type)== ETH_TYPE_8021ad_0)|| \
  5. ((type)== ETH_TYPE_8021ad_1)|| \
  6. ((type)== ETH_TYPE_8021ad_2)|| \
  7. ((type)== ETH_TYPE_8021ad_3))

2. libdnet以太网接口设计

2.1 核心API接口

  1. // include/dnet/eth.h:84-93
  2. eth_t*eth_open(constchar*device);// 打开以太网设备
  3. int eth_get(eth_t*e,eth_addr_t*ea);// 获取MAC地址
  4. int eth_set(eth_t*e,consteth_addr_t*ea);// 设置MAC地址
  5. ssize_t eth_send(eth_t*e,constvoid*buf,size_t len);// 发送以太网帧
  6. eth_t*eth_close(eth_t*e);// 关闭以太网设备
  7. char*eth_ntop(consteth_addr_t*eth,char*dst,size_t len);// MAC转字符串
  8. int eth_pton(constchar*src,eth_addr_t*dst);// 字符串转MAC
  9. char*eth_ntoa(consteth_addr_t*eth);// MAC转字符串(简写)

2.2 以太网帧打包宏

  1. // include/dnet/eth.h:74-79
  2. #define eth_pack_hdr(h, dst, src, type)do{           \
  3. struct eth_hdr *eth_pack_p =(struct eth_hdr *)(h);   \
  4.     memcpy(&eth_pack_p->eth_dst,&(dst), ETH_ADDR_LEN);  \
  5.     memcpy(&eth_pack_p->eth_src,&(src), ETH_ADDR_LEN);  \
  6.     eth_pack_p->eth_type = htons(type);                 \
  7. }while(0)

使用示例

  1. eth_addr_t dst ={0xff,0xff,0xff,0xff,0xff,0xff};// 广播
  2. eth_addr_t src ={0x00,0x11,0x22,0x33,0x44,0x55};
  3. u_char frame[ETH_LEN_MAX];
  4. eth_pack_hdr(frame, dst, src, ETH_TYPE_ARP);

2.3 MAC地址转换函数

2.3.1 MAC转字符串

  1. // src/addr-util.c:75-92
  2. char*eth_ntop(consteth_addr_t*eth,char*dst,size_t len)
  3. {
  4. constchar*x;
  5. char*= dst;
  6. int i;
  7. if(len <18)// 需要至少18字节("xx:xx:xx:xx:xx:xx" + '\0')
  8. return(NULL);
  9. for(=0; i < ETH_ADDR_LEN; i++){
  10. for(= octet2hex[eth->data[i]];(*=*x)!='\0'; x++, p++)
  11. ;
  12. *p++=':';
  13. }
  14.     p[-1]='\0';// 替换最后的':'为'\0'
  15. return(dst);
  16. }

格式: 00:11:22:33:44:55

2.3.2 字符串转MAC

  1. // src/addr-util.c:104-119
  2. int eth_pton(constchar*p,eth_addr_t*eth)
  3. {
  4. char*ep;
  5. long l;
  6. int i;
  7. for(=0; i < ETH_ADDR_LEN; i++){
  8.         l = strtol(p,&ep,16);// 十六进制解析
  9. if(ep == p || l <0|| l >0xff||
  10. (< ETH_ADDR_LEN -1&&*ep !=':'))
  11. break;
  12.         eth->data[i]=(u_char)l;
  13.         p = ep +1;
  14. }
  15. return((== ETH_ADDR_LEN &&*ep =='\0')?0:-1);
  16. }

支持的格式

  • 00:11:22:33:44:55(推荐)
  • 00-11-22-33-44-55
  • 0011:2233:4455(部分格式)

2.4 平台适配机制

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

实现文件
平台
说明
eth-linux.c
Linux
Packet Socket
eth-bsd.c
macOS/BSD
BPF
eth-dlpi.c
Solaris/HP-UX
DLPI
eth-snoop.c
Solaris
SNOOP
eth-pfilt.c
Tru64
Packet Filter
eth-ndd.c
AIX
NDD
eth-win32.c
Windows
不支持
eth-none.c
空实现

3. Linux平台实现

3.1 实现文件

主文件: src/eth-linux.c

3.2 数据结构

  1. // src/eth-linux.c:36-40
  2. struct eth_handle {
  3. int                 fd;// Packet Socket文件描述符
  4. struct ifreq        ifr;// 接口请求结构
  5. struct sockaddr_ll  sll;// 链路层socket地址
  6. };

3.3 打开以太网设备

  1. // src/eth-linux.c:42-67
  2. eth_t*eth_open(constchar*device)
  3. {
  4. eth_t*e;
  5. int n;
  6. if((= calloc(1,sizeof(*e)))!= NULL){
  7. // 创建Packet Socket
  8. if((e->fd = socket(PF_PACKET, SOCK_RAW,
  9.                  htons(ETH_P_ALL)))<0)
  10. return(eth_close(e));
  11. #ifdef SO_BROADCAST
  12. // 允许广播
  13.         n =1;
  14. if(setsockopt(e->fd, SOL_SOCKET, SO_BROADCAST,&n,
  15. sizeof(n))<0)
  16. return(eth_close(e));
  17. #endif
  18. // 保存接口名称
  19.         strlcpy(e->ifr.ifr_name, device,sizeof(e->ifr.ifr_name));
  20. // 获取接口索引
  21. if(ioctl(e->fd, SIOCGIFINDEX,&e->ifr)<0)
  22. return(eth_close(e));
  23. // 配置链路层socket地址
  24.         e->sll.sll_family = AF_PACKET;
  25.         e->sll.sll_ifindex = e->ifr.ifr_ifindex;
  26. }
  27. return(e);
  28. }

关键技术

  1. Packet Socket
  • PF_PACKET:协议族为数据包协议
  • SOCK_RAW:原始套接字
  • ETH_P_ALL:接收所有以太网帧
  1. SIOCGIFINDEX
  • 获取接口的索引号
  • 用于 sockaddr_ll的 sll_ifindex字段
  1. SO_BROADCAST
  • 允许发送广播帧

3.4 发送以太网帧

  1. // src/eth-linux.c:69-78
  2. ssize_t eth_send(eth_t*e,constvoid*buf,size_t len)
  3. {
  4. struct eth_hdr *eth =(struct eth_hdr *)buf;
  5. // 设置协议类型
  6.     e->sll.sll_protocol = eth->eth_type;
  7. // 发送数据帧
  8. return(sendto(e->fd, buf, len,0,(struct sockaddr *)&e->sll,
  9. sizeof(e->sll)));
  10. }

说明

  • eth->eth_type:以太网类型字段
  • sendto:使用链路层地址发送

3.5 获取MAC地址

  1. // src/eth-linux.c:91-104
  2. int eth_get(eth_t*e,eth_addr_t*ea)
  3. {
  4. struct addr ha;
  5. // 使用ioctl获取硬件地址
  6. if(ioctl(e->fd, SIOCGIFHWADDR,&e->ifr)<0)
  7. return(-1);
  8. // 转换为libdnet地址格式
  9. if(addr_ston(&e->ifr.ifr_hwaddr,&ha)<0)
  10. return(-1);
  11. // 提取MAC地址
  12.     memcpy(ea,&ha.addr_eth,sizeof(*ea));
  13. return(0);
  14. }

ioctl命令

  • SIOCGIFHWADDR:获取接口硬件地址

3.6 设置MAC地址

  1. // src/eth-linux.c:106-118
  2. int eth_set(eth_t*e,consteth_addr_t*ea)
  3. {
  4. struct addr ha;
  5. // 构造地址结构
  6.     ha.addr_type = ADDR_TYPE_ETH;
  7.     ha.addr_bits = ETH_ADDR_BITS;
  8.     memcpy(&ha.addr_eth, ea, ETH_ADDR_LEN);
  9. // 转换为sockaddr格式
  10.     addr_ntos(&ha,&e->ifr.ifr_hwaddr);
  11. // 设置硬件地址
  12. return(ioctl(e->fd, SIOCSIFHWADDR,&e->ifr));
  13. }

ioctl命令

  • SIOCSIFHWADDR:设置接口硬件地址

注意:需要root权限

3.7 关闭以太网设备

  1. // src/eth-linux.c:80-89
  2. eth_t*eth_close(eth_t*e)
  3. {
  4. if(!= NULL){
  5. if(e->fd >=0)
  6.             close(e->fd);
  7.         free(e);
  8. }
  9. return(NULL);
  10. }

4. macOS/BSD平台实现

4.1 实现文件

主文件: src/eth-bsd.c

4.2 数据结构

  1. // src/eth-bsd.c:34-37
  2. struct eth_handle {
  3. int     fd;// BPF文件描述符
  4. char    device[16];// 接口名称
  5. };

4.3 打开以太网设备

  1. // src/eth-bsd.c:39-74
  2. eth_t*eth_open(constchar*device)
  3. {
  4. struct ifreq ifr;
  5. char file[32];
  6. eth_t*e;
  7. int i;
  8. if((= calloc(1,sizeof(*e)))!= NULL){
  9. // 尝试打开BPF设备(/dev/bpf0 - /dev/bpf127)
  10. for(=0; i <128; i++){
  11.             snprintf(file,sizeof(file),"/dev/bpf%d", i);
  12. /* Mac OS X 10.6的bug:O_WRONLY会导致其他进程无法接收流量 */
  13.             e->fd = open(file, O_RDWR);
  14. if(e->fd !=-1|| errno != EBUSY)
  15. break;
  16. }
  17. if(e->fd <0)
  18. return(eth_close(e));
  19. // 绑定到指定接口
  20.         memset(&ifr,0,sizeof(ifr));
  21.         strlcpy(ifr.ifr_name, device,sizeof(ifr.ifr_name));
  22. if(ioctl(e->fd, BIOCSETIF,(char*)&ifr)<0)
  23. return(eth_close(e));
  24. #ifdef BIOCSHDRCMPLT
  25. // 设置标志:驱动不自动填充源MAC地址
  26.         i =1;
  27. if(ioctl(e->fd, BIOCSHDRCMPLT,&i)<0)
  28. return(eth_close(e));
  29. #endif
  30. // 保存设备名称
  31.         strlcpy(e->device, device,sizeof(e->device));
  32. }
  33. return(e);
  34. }

关键技术

  1. BPF设备
  • BSD Packet Filter
  • 设备文件: /dev/bpf0到 /dev/bpf127
  • 自动查找可用的BPF设备
  1. BIOCSETIF
  • 将BPF绑定到指定网络接口
  1. BIOCSHDRCMPLT
  • 指示驱动程序不自动完成以太网头
  • 允许应用程序发送自定义源MAC地址

4.4 发送以太网帧

  1. // src/eth-bsd.c:76-80
  2. ssize_t eth_send(eth_t*e,constvoid*buf,size_t len)
  3. {
  4. return(write(e->fd, buf, len));
  5. }

说明

  • 直接向BPF文件描述符写入数据
  • 驱动程序自动处理以太网帧的发送

4.5 获取MAC地址(sysctl方式)

  1. // src/eth-bsd.c:93-138
  2. #if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_ROUTE_RT_MSGHDR)
  3. int eth_get(eth_t*e,eth_addr_t*ea)
  4. {
  5. struct if_msghdr *ifm;
  6. struct sockaddr_dl *sdl;
  7. struct addr ha;
  8.     u_char *p,*buf;
  9. size_t len;
  10. int mib[]={ CTL_NET, AF_ROUTE,0, AF_LINK, NET_RT_IFLIST,0};
  11. // 获取接口列表所需的缓冲区大小
  12. if(sysctl(mib,6, NULL,&len, NULL,0)<0)
  13. return(-1);
  14. // 分配缓冲区
  15. if((buf = malloc(len))== NULL)
  16. return(-1);
  17. // 获取接口列表
  18. if(sysctl(mib,6, buf,&len, NULL,0)<0){
  19.         free(buf);
  20. return(-1);
  21. }
  22. // 遍历接口列表
  23. for(= buf; p < buf + len; p += ifm->ifm_msglen){
  24.         ifm =(struct if_msghdr *)p;
  25.         sdl =(struct sockaddr_dl *)(ifm +1);
  26. if(ifm->ifm_type != RTM_IFINFO ||
  27. (ifm->ifm_addrs & RTA_IFP)==0)
  28. continue;
  29. // 检查接口名称匹配
  30. if(sdl->sdl_family != AF_LINK || sdl->sdl_nlen ==0||
  31.             memcmp(sdl->sdl_data, e->device, sdl->sdl_nlen)!=0)
  32. continue;
  33. // 提取MAC地址
  34. if(addr_ston((struct sockaddr *)sdl,&ha)==0)
  35. break;
  36. }
  37.     free(buf);
  38. if(>= buf + len){
  39.         errno = ESRCH;
  40. return(-1);
  41. }
  42.     memcpy(ea,&ha.addr_eth,sizeof(*ea));
  43. return(0);
  44. }
  45. #endif

关键技术

  1. sysctl
  • 系统控制接口
  • 获取内核信息
  1. MIB标识符
  • CTL_NET:网络相关
  • AF_ROUTE:路由套接字
  • AF_LINK:链路层地址
  • NET_RT_IFLIST:接口列表
  1. sockaddr_dl
  • 链路层socket地址
  • 包含接口名称和MAC地址

4.6 设置MAC地址

  1. // src/eth-bsd.c:148-164
  2. #if defined(SIOCSIFLLADDR)
  3. int eth_set(eth_t*e,consteth_addr_t*ea)
  4. {
  5. struct ifreq ifr;
  6. struct addr ha;
  7. // 构造地址结构
  8.     ha.addr_type = ADDR_TYPE_ETH;
  9.     ha.addr_bits = ETH_ADDR_BITS;
  10.     memcpy(&ha.addr_eth, ea, ETH_ADDR_LEN);
  11. // 配置ifreq
  12.     memset(&ifr,0,sizeof(ifr));
  13.     strlcpy(ifr.ifr_name, e->device,sizeof(ifr.ifr_name));
  14.     addr_ntos(&ha,&ifr.ifr_addr);
  15. // 设置链路层地址
  16. return(ioctl(e->fd, SIOCSIFLLADDR,&ifr));
  17. }
  18. #endif

ioctl命令

  • SIOCSIFLLADDR:设置接口链路层地址(BSD特定)

5. Solaris平台实现

5.1 实现文件

主文件: src/eth-dlpi.c、 src/eth-snoop.c

5.2 DLPI实现(Data Link Provider Interface)

5.2.1 数据结构

  1. // src/eth-dlpi.c:42-45
  2. struct eth_handle {
  3. int     fd;// DLPI流文件描述符
  4. int     sap_len;// SAP(服务访问点)长度
  5. };

5.2.2 打开以太网设备

  1. // src/eth-dlpi.c:132-205
  2. eth_t*eth_open(constchar*device)
  3. {
  4. union DL_primitives *dlp;
  5. uint32_t buf[8192];
  6. char*p, dev[16];
  7. eth_t*e;
  8. int ppa;
  9. if((= calloc(1,sizeof(*e)))== NULL)
  10. return(NULL);
  11. #ifdef HAVE_SYS_DLPIHDR_H
  12. // OSF1/Tru64:使用流设备
  13. if((e->fd = open("/dev/streams/dlb", O_RDWR))<0)
  14. return(eth_close(e));
  15. if((ppa = eth_match_ppa(e, device))<0){
  16.         errno = ESRCH;
  17. return(eth_close(e));
  18. }
  19. #else
  20. // Solaris/HP-UX:使用DLPI设备
  21.     e->fd =-1;
  22.     snprintf(dev,sizeof(dev),"/dev/%s", device);
  23. // 查找PPA(物理点访问点)
  24. if((= dev_find_ppa(dev))== NULL){
  25.         errno = EINVAL;
  26. return(eth_close(e));
  27. }
  28.     ppa = atoi(p);
  29. *='\0';
  30. // 尝试打开设备
  31. if((e->fd = open(dev, O_RDWR))<0){
  32.         snprintf(dev,sizeof(dev),"/dev/%s", device);
  33. if((e->fd = open(dev, O_RDWR))<0){
  34.             snprintf(dev,sizeof(dev),"/dev/net/%s", device);
  35. if((e->fd = open(dev, O_RDWR))<0)
  36. return(eth_close(e));
  37. }
  38. }
  39. #endif
  40. // 获取DLPI提供者信息
  41.     dlp =(union DL_primitives *)buf;
  42.     dlp->info_req.dl_primitive = DL_INFO_REQ;
  43. if(dlpi_msg(e->fd, dlp, DL_INFO_REQ_SIZE, RS_HIPRI,
  44.         DL_INFO_ACK, DL_INFO_ACK_SIZE,sizeof(buf))<0)
  45. return(eth_close(e));
  46.     e->sap_len = dlp->info_ack.dl_sap_length;
  47. // STYLE2:需要附加到PPA
  48. if(dlp->info_ack.dl_provider_style == DL_STYLE2){
  49.         dlp->attach_req.dl_primitive = DL_ATTACH_REQ;
  50.         dlp->attach_req.dl_ppa = ppa;
  51. if(dlpi_msg(e->fd, dlp, DL_ATTACH_REQ_SIZE,0,
  52.             DL_OK_ACK, DL_OK_ACK_SIZE,sizeof(buf))<0)
  53. return(eth_close(e));
  54. }
  55. // 绑定到SAP
  56.     memset(&dlp->bind_req,0, DL_BIND_REQ_SIZE);
  57.     dlp->bind_req.dl_primitive = DL_BIND_REQ;
  58. #ifdef DL_HP_RAWDLS
  59. // HP-UX:原始数据链路服务
  60.     dlp->bind_req.dl_sap =24;
  61.     dlp->bind_req.dl_service_mode = DL_HP_RAWDLS;
  62. #else
  63.     dlp->bind_req.dl_sap = DL_ETHER;
  64.     dlp->bind_req.dl_service_mode = DL_CLDLS;
  65. #endif
  66. if(dlpi_msg(e->fd, dlp, DL_BIND_REQ_SIZE,0,
  67.         DL_BIND_ACK, DL_BIND_ACK_SIZE,sizeof(buf))<0)
  68. return(eth_close(e));
  69. #ifdef DLIOCRAW
  70. // 设置原始模式
  71. if(strioctl(e->fd, DLIOCRAW,0, NULL)<0)
  72. return(eth_close(e));
  73. #endif
  74. return(e);
  75. }

关键技术

  1. DLPI原语
  • DL_INFO_REQ:请求提供者信息
  • DL_ATTACH_REQ:附加到PPA(STYLE2)
  • DL_BIND_REQ:绑定到SAP
  • DL_OK_ACK:操作成功确认
  1. DLPI风格
  • STYLE1:打开时直接附加到PPA
  • STYLE2:需要显式附加到PPA
  1. DLIOCRAW
  • 设置原始模式
  • 允许发送完整的以太网帧

5.2.3 发送以太网帧

  1. // src/eth-dlpi.c:207-257
  2. ssize_t eth_send(eth_t*e,constvoid*buf,size_t len)
  3. {
  4. #if defined(DLIOCRAW)
  5. // 原始模式:直接写入
  6. return(write(e->fd, buf, len));
  7. #else
  8. union DL_primitives *dlp;
  9. struct strbuf ctl, data;
  10. struct eth_hdr *eth;
  11. uint32_t ctlbuf[8192];
  12.     u_char sap[4]={0,0,0,0};
  13. int dlen;
  14.     dlp =(union DL_primitives *)ctlbuf;
  15. #ifdef DL_HP_RAWDATA_REQ
  16.     dlp->dl_primitive = DL_HP_RAWDATA_REQ;
  17.     dlen = DL_HP_RAWDATA_REQ_SIZE;
  18. #else
  19.     dlp->unitdata_req.dl_primitive = DL_UNITDATA_REQ;
  20.     dlp->unitdata_req.dl_dest_addr_length = ETH_ADDR_LEN;
  21.     dlp->unitdata_req.dl_dest_addr_offset = DL_UNITDATA_REQ_SIZE;
  22.     dlp->unitdata_req.dl_priority.dl_min =
  23.         dlp->unitdata_req.dl_priority.dl_max =0;
  24.     dlen = DL_UNITDATA_REQ_SIZE;
  25. #endif
  26. // 提取以太网类型
  27.     eth =(struct eth_hdr *)buf;
  28. *(uint16_t*)sap = ntohs(eth->eth_type);
  29. // 构造控制消息
  30.     ctl.maxlen =0;
  31.     ctl.len = dlen + ETH_ADDR_LEN + abs(e->sap_len);
  32.     ctl.buf =(char*)ctlbuf;
  33. // 设置SAP和目标地址
  34. if(e->sap_len >=0){
  35.         memcpy(ctlbuf + dlen, sap, e->sap_len);
  36.         memcpy(ctlbuf + dlen + e->sap_len,
  37.             eth->eth_dst.data, ETH_ADDR_LEN);
  38. }else{
  39.         memcpy(ctlbuf + dlen, eth->eth_dst.data, ETH_ADDR_LEN);
  40.         memcpy(ctlbuf + dlen + ETH_ADDR_LEN, sap, abs(e->sap_len));
  41. }
  42. // 设置数据消息
  43.     data.maxlen =0;
  44.     data.len = len;
  45.     data.buf =(char*)buf;
  46. // 发送消息
  47. if(putmsg(e->fd,&ctl,&data,0)<0)
  48. return(-1);
  49. return(len);
  50. #endif
  51. }

STREAMS消息

  • putmsg:发送消息
  • strbuf:控制消息和数据消息

5.2.4 获取MAC地址

  1. // src/eth-dlpi.c:270-287
  2. int eth_get(eth_t*e,eth_addr_t*ea)
  3. {
  4. union DL_primitives *dlp;
  5.     u_char buf[2048];
  6.     dlp =(union DL_primitives *)buf;
  7.     dlp->physaddr_req.dl_primitive = DL_PHYS_ADDR_REQ;
  8.     dlp->physaddr_req.dl_addr_type = DL_CURR_PHYS_ADDR;
  9. if(dlpi_msg(e->fd, dlp, DL_PHYS_ADDR_REQ_SIZE,0,
  10.         DL_PHYS_ADDR_ACK, DL_PHYS_ADDR_ACK_SIZE,sizeof(buf))<0)
  11. return(-1);
  12.     memcpy(ea, buf + dlp->physaddr_ack.dl_addr_offset,sizeof(*ea));
  13. return(0);
  14. }

5.2.5 设置MAC地址

  1. // src/eth-dlpi.c:289-304
  2. int eth_set(eth_t*e,consteth_addr_t*ea)
  3. {
  4. union DL_primitives *dlp;
  5.     u_char buf[2048];
  6.     dlp =(union DL_primitives *)buf;
  7.     dlp->set_physaddr_req.dl_primitive = DL_SET_PHYS_ADDR_REQ;
  8.     dlp->set_physaddr_req.dl_addr_length = ETH_ADDR_LEN;
  9.     dlp->set_physaddr_req.dl_addr_offset = DL_SET_PHYS_ADDR_REQ_SIZE;
  10.     memcpy(buf + DL_SET_PHYS_ADDR_REQ_SIZE, ea,sizeof(*ea));
  11. return(dlpi_msg(e->fd, dlp, DL_SET_PHYS_ADDR_REQ_SIZE + ETH_ADDR_LEN,
  12. 0, DL_OK_ACK, DL_OK_ACK_SIZE,sizeof(buf)));
  13. }

5.3 SNOOP实现(Solaris)

5.3.1 数据结构

  1. // src/eth-snoop.c:25-28
  2. struct eth_handle {
  3. int     fd;// SNOOP socket
  4. struct ifreq ifr;// 接口请求结构
  5. };

5.3.2 打开以太网设备

  1. // src/eth-snoop.c:30-57
  2. eth_t*eth_open(constchar*device)
  3. {
  4. struct sockaddr_raw sr;
  5. eth_t*e;
  6. int n;
  7. if((= calloc(1,sizeof(*e)))== NULL)
  8. return(NULL);
  9. // 创建SNOOP原始socket
  10. if((e->fd = socket(PF_RAW, SOCK_RAW, RAWPROTO_SNOOP))<0)
  11. return(eth_close(e));
  12. // 配置socket地址
  13.     memset(&sr,0,sizeof(sr));
  14.     sr.sr_family = AF_RAW;
  15.     strlcpy(sr.sr_ifname, device,sizeof(sr.sr_ifname));
  16. // 绑定到接口
  17. if(bind(e->fd,(struct sockaddr *)&sr,sizeof(sr))<0)
  18. return(eth_close(e));
  19. // 设置发送缓冲区
  20.     n =60000;
  21. if(setsockopt(e->fd, SOL_SOCKET, SO_SNDBUF,&n,sizeof(n))<0)
  22. return(eth_close(e));
  23.     strlcpy(e->ifr.ifr_name, device,sizeof(e->ifr.ifr_name));
  24. return(e);
  25. }

5.3.3 获取/设置MAC地址

  1. // src/eth-snoop.c:59-92
  2. int eth_get(eth_t*e,eth_addr_t*ea)
  3. {
  4. struct addr ha;
  5. if(ioctl(e->fd, SIOCGIFADDR,&e->ifr)<0)
  6. return(-1);
  7. if(addr_ston(&e->ifr.ifr_addr,&ha)<0)
  8. return(-1);
  9. if(ha.addr_type != ADDR_TYPE_ETH){
  10.         errno = EINVAL;
  11. return(-1);
  12. }
  13.     memcpy(ea,&ha.addr_eth,sizeof(*ea));
  14. return(0);
  15. }
  16. int eth_set(eth_t*e,consteth_addr_t*ea)
  17. {
  18. struct addr ha;
  19.     ha.addr_type = ADDR_TYPE_ETH;
  20.     ha.addr_bits = ETH_ADDR_BITS;
  21.     memcpy(&ha.addr_eth, ea, ETH_ADDR_LEN);
  22. if(addr_ntos(&ha,&e->ifr.ifr_addr)<0)
  23. return(-1);
  24. return(ioctl(e->fd, SIOCSIFADDR,&e->ifr));
  25. }

6. HP-UX平台实现

HP-UX使用DLPI实现,已在5.2节详细说明。

特殊之处

  1. // src/eth-dlpi.c:190-192
  2. #ifdef DL_HP_RAWDLS
  3.     dlp->bind_req.dl_sap =24;// HP-UX专用值
  4.     dlp->bind_req.dl_service_mode = DL_HP_RAWDLS;
  5. #endif

7. AIX平台实现

7.1 实现文件

主文件: src/eth-ndd.c

7.2 数据结构

  1. // src/eth-ndd.c:25-28
  2. struct eth_handle {
  3. char    device[16];// 接口名称
  4. int     fd;// NDD socket
  5. };

7.3 打开以太网设备

  1. // src/eth-ndd.c:30-58
  2. eth_t*eth_open(constchar*device)
  3. {
  4. struct sockaddr_ndd_8022 sa;
  5. eth_t*e;
  6. if((= calloc(1,sizeof(*e)))== NULL)
  7. return(NULL);
  8. // 创建NDD socket
  9. if((e->fd = socket(AF_NDD, SOCK_DGRAM, NDD_PROT_ETHER))<0)
  10. return(eth_close(e));
  11. // 配置socket地址
  12.     sa.sndd_8022_family = AF_NDD;
  13.     sa.sndd_8022_len =sizeof(sa);
  14.     sa.sndd_8022_filtertype = NS_ETHERTYPE;
  15.     sa.sndd_8022_ethertype =0;
  16.     sa.sndd_8022_filterlen =sizeof(struct ns_8022);
  17.     strlcpy(sa.sndd_8022_nddname, device,sizeof(sa.sndd_8022_nddname));
  18. // 绑定socket
  19. if(bind(e->fd,(struct sockaddr *)&sa,sizeof(sa))<0)
  20. return(eth_close(e));
  21. // 连接socket
  22. if(connect(e->fd,(struct sockaddr *)&sa,sizeof(sa))<0)
  23. return(eth_close(e));
  24. return(e);
  25. }

NDD(Network Device Driver)

  • AIX专用的网络设备驱动接口
  • 用于直接访问底层网络设备

7.4 获取MAC地址

  1. // src/eth-ndd.c:77-110
  2. int eth_get(eth_t*e,eth_addr_t*ea)
  3. {
  4. struct kinfo_ndd *nddp;
  5. int size;
  6. void*end;
  7. // 获取NDD信息所需的缓冲区大小
  8. if((size = getkerninfo(KINFO_NDD,0,0,0))==0){
  9.         errno = ENOENT;
  10. return(-1);
  11. }elseif(size <0)
  12. return(-1);
  13. // 分配缓冲区
  14. if((nddp = malloc(size))== NULL)
  15. return(-1);
  16. // 获取NDD信息
  17. if(getkerninfo(KINFO_NDD, nddp,&size,0)<0){
  18.         free(nddp);
  19. return(-1);
  20. }
  21. // 遍历NDD列表
  22. for(end =(void*)nddp + size;(void*)nddp < end; nddp++){
  23. if(strcmp(nddp->ndd_alias, e->device)==0||
  24.             strcmp(nddp->ndd_name, e->device)==0){
  25.             memcpy(ea, nddp->ndd_addr,sizeof(*ea));
  26. }
  27. }
  28.     free(nddp);
  29. if((void*)nddp >= end){
  30.         errno = ESRCH;
  31. return(-1);
  32. }
  33. return(0);
  34. }

getkerninfo

  • AIX的内核信息接口
  • KINFO_NDD:网络设备驱动信息

8. Windows平台实现

8.1 实现文件

主文件: src/eth-win32.c

8.2 当前状态

  1. // src/eth-win32.c:23-50
  2. eth_t*eth_open(constchar*device)
  3. {
  4. return(NULL);// 不支持
  5. }
  6. ssize_t eth_send(eth_t*eth,constvoid*buf,size_t len)
  7. {
  8. return(-1);// 不支持
  9. }
  10. eth_t*eth_close(eth_t*eth)
  11. {
  12. return(NULL);
  13. }
  14. int eth_get(eth_t*eth,eth_addr_t*ea)
  15. {
  16. return(-1);// 不支持
  17. }
  18. int eth_set(eth_t*eth,consteth_addr_t*ea)
  19. {
  20. return(-1);// 不支持
  21. }

说明

  • Windows平台不直接支持原始以太网帧发送
  • 需要使用第三方库(如WinPcap/Npcap)
  • libdnet的Windows实现仅提供框架

8.3 替代方案

在Windows上,可以使用以下库:

  1. WinPcap/Npcap:提供类似BPF的功能
  2. Raw Sockets:功能有限,仅支持部分协议
  3. Packet.dll:底层数据包捕获和发送

9. 跨平台对比分析

9.1 API设计对比

功能
Linux
macOS/BSD
Solaris
HP-UX
AIX
Windows
打开设备
Packet Socket
BPF
DLPI/SNOOP
DLPI
NDD
不支持
获取MAC
SIOCGIFHWADDR
sysctl
DLPHYSADDR_REQ
DLPHYSADDR_REQ
getkerninfo
不支持
设置MAC
SIOCSIFHWADDR
SIOCSIFLLADDR
DLSETPHYSADDRREQ
DLSETPHYSADDRREQ
不支持
不支持
发送帧
sendto
write
putmsg/write
putmsg/write
write
不支持
接收帧
recvfrom
read
getmsg
getmsg
read
不支持

9.2 数据结构对比

平台
主要结构
说明
Linux sockaddr_ll
链路层socket地址
macOS/BSD sockaddr_dl
链路层socket地址
Solaris DL_primitives
DLPI原语联合体
AIX sockaddr_ndd_8022
NDD socket地址

9.3 操作方式对比

Linux

  1. // Packet Socket方式
  2. int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  3. struct sockaddr_ll sll;
  4. sendto(fd, buf, len,0,(struct sockaddr *)&sll,sizeof(sll));

macOS/BSD

  1. // BPF方式
  2. int fd = open("/dev/bpf0", O_RDWR);
  3. ioctl(fd, BIOCSETIF,&ifr);
  4. write(fd, buf, len);

Solaris (DLPI)

  1. // DLPI方式
  2. int fd = open("/dev/hme", O_RDWR);
  3. putmsg(fd,&ctl,&data,0);

9.4 性能对比

操作
Linux
macOS/BSD
Solaris
AIX
打开设备
慢(STREAMS)
发送帧
中(putmsg)
获取MAC
快(ioctl)
中(sysctl)
慢(DLPI)
慢(getkerninfo)
设置MAC
快(ioctl)
快(ioctl)
慢(DLPI)
不支持

9.5 权限要求对比

操作
Linux
macOS/BSD
Solaris
HP-UX
AIX
打开设备
root/CAPNETRAW
root
root
root
root
获取MAC
普通用户
普通用户
普通用户
普通用户
普通用户
设置MAC
root
root
root
root
不支持
发送帧
root
root
root
root
root

9.6 代码复杂度对比

文件
行数
平台
复杂度
eth-linux.c
119
Linux
eth-bsd.c
173
macOS/BSD
eth-dlpi.c
305
Solaris/HP-UX
eth-snoop.c
110
Solaris
eth-pfilt.c
88
Tru64
eth-ndd.c
118
AIX
eth-win32.c
51
Windows
低(不支持)

9.7 维护性分析

方面
Linux
macOS/BSD
Solaris
AIX
API稳定性
平台差异
高(DLPI风格)
代码复用
测试难度

9.8 特殊功能对比

功能
Linux
macOS/BSD
Solaris
AIX
VLAN支持
原生
原生
原生
有限
混杂模式
支持
支持
支持
支持
自定义源MAC
支持
支持(BIOCSHDRCMPLT)
支持
有限
驱动信息
支持(ethtool)
不支持
不支持
不支持

10. 实际应用示例

10.1 基本用法:获取MAC地址

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. int main(void)
  4. {
  5. eth_t*eth;
  6. eth_addr_t ea;
  7. char buf[18];
  8. if((eth = eth_open("eth0"))== NULL){
  9.         perror("eth_open");
  10. return(1);
  11. }
  12. if(eth_get(eth,&ea)<0){
  13.         perror("eth_get");
  14. return(1);
  15. }
  16.     printf("MAC地址: %s\n", eth_ntop(&ea, buf,sizeof(buf)));
  17.     eth_close(eth);
  18. return(0);
  19. }

10.2 设置MAC地址(需要root权限)

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. int main(void)
  4. {
  5. eth_t*eth;
  6. eth_addr_t new_ea ={0x00,0x11,0x22,0x33,0x44,0x55};
  7. if((eth = eth_open("eth0"))== NULL){
  8.         perror("eth_open");
  9. return(1);
  10. }
  11. if(eth_set(eth,&new_ea)<0){
  12.         perror("eth_set");
  13. return(1);
  14. }
  15.     printf("MAC地址设置成功\n");
  16.     eth_close(eth);
  17. return(0);
  18. }

10.3 发送ARP请求

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. #include<stdint.h>
  4. struct arp_hdr {
  5. uint16_t arp_hrd;// 硬件类型
  6. uint16_t arp_pro;// 协议类型
  7. uint8_t  arp_hln;// 硬件地址长度
  8. uint8_t  arp_pln;// 协议地址长度
  9. uint16_t arp_op;// 操作码
  10. eth_addr_t arp_sha;// 发送方硬件地址
  11. uint32_t  arp_spa;// 发送方协议地址
  12. eth_addr_t arp_tha;// 目标硬件地址
  13. uint32_t  arp_tpa;// 目标协议地址
  14. };
  15. int main(void)
  16. {
  17. eth_t*eth;
  18. eth_addr_t dst_mac ={0xff,0xff,0xff,0xff,0xff,0xff};
  19. eth_addr_t src_mac;
  20.     u_char frame[512];
  21. struct eth_hdr *eth_hdr;
  22. struct arp_hdr *arp_hdr;
  23. if((eth = eth_open("eth0"))== NULL){
  24.         perror("eth_open");
  25. return(1);
  26. }
  27. // 获取本地MAC地址
  28. if(eth_get(eth,&src_mac)<0){
  29.         perror("eth_get");
  30. return(1);
  31. }
  32. // 构造以太网头
  33.     eth_hdr =(struct eth_hdr *)frame;
  34.     eth_pack_hdr(eth_hdr, dst_mac, src_mac, ETH_TYPE_ARP);
  35. // 构造ARP头
  36.     arp_hdr =(struct arp_hdr *)(frame + ETH_HDR_LEN);
  37.     arp_hdr->arp_hrd = htons(1);// 以太网
  38.     arp_hdr->arp_pro = htons(0x0800);// IPv4
  39.     arp_hdr->arp_hln =6;
  40.     arp_hdr->arp_pln =4;
  41.     arp_hdr->arp_op = htons(1);// ARP请求
  42.     memcpy(&arp_hdr->arp_sha,&src_mac, ETH_ADDR_LEN);
  43.     arp_hdr->arp_spa = inet_addr("192.168.1.100");
  44.     memset(&arp_hdr->arp_tha,0, ETH_ADDR_LEN);
  45.     arp_hdr->arp_tpa = inet_addr("192.168.1.1");
  46. // 发送ARP请求
  47. if(eth_send(eth, frame, ETH_HDR_LEN +sizeof(struct arp_hdr))<0){
  48.         perror("eth_send");
  49. return(1);
  50. }
  51.     printf("ARP请求已发送\n");
  52.     eth_close(eth);
  53. return(0);
  54. }

10.4 MAC地址转换

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. int main(void)
  4. {
  5. eth_addr_t ea;
  6. char buf[18];
  7. // 字符串转MAC
  8. if(eth_pton("00:11:22:33:44:55",&ea)<0){
  9.         perror("eth_pton");
  10. return(1);
  11. }
  12. // MAC转字符串
  13.     printf("MAC地址: %s\n", eth_ntop(&ea, buf,sizeof(buf)));
  14. // MAC转字符串(简写)
  15.     printf("MAC地址: %s\n", eth_ntoa(&ea));
  16. return(0);
  17. }

10.5 扫描本地网络MAC地址

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. int scan_intf(conststruct intf_entry *entry,void*arg)
  4. {
  5. eth_t*eth;
  6. eth_addr_t ea;
  7. char buf[18];
  8. // 只处理以太网接口
  9. if(entry->intf_type != INTF_TYPE_ETH)
  10. return(0);
  11. // 只处理up状态的接口
  12. if(!(entry->intf_flags & INTF_FLAG_UP))
  13. return(0);
  14.     printf("接口: %s\n", entry->intf_name);
  15. if((eth = eth_open(entry->intf_name))== NULL){
  16.         printf("  无法打开: %s\n", strerror(errno));
  17. return(0);
  18. }
  19. if(eth_get(eth,&ea)<0){
  20.         printf("  无法获取MAC: %s\n", strerror(errno));
  21. }else{
  22.         printf("  MAC: %s\n", eth_ntop(&ea, buf,sizeof(buf)));
  23. }
  24.     eth_close(eth);
  25. return(0);
  26. }
  27. int main(void)
  28. {
  29. intf_t*intf;
  30. if((intf = intf_open())== NULL){
  31.         perror("intf_open");
  32. return(1);
  33. }
  34.     intf_loop(intf, scan_intf, NULL);
  35.     intf_close(intf);
  36. return(0);
  37. }

10.6 检测MAC地址重复

  1. #include<stdio.h>
  2. #include<dnet.h>
  3. #include<string.h>
  4. #define MAX_INTERFACES 32
  5. struct mac_entry {
  6. char name[16];
  7. eth_addr_t mac;
  8. };
  9. int main(void)
  10. {
  11. intf_t*intf;
  12. eth_t*eth;
  13. struct intf_entry entry;
  14. struct mac_entry macs[MAX_INTERFACES];
  15. int count =0, i, j;
  16. char buf[18], ebuf[1024];
  17. if((intf = intf_open())== NULL){
  18.         perror("intf_open");
  19. return(1);
  20. }
  21.     memset(&entry,0,sizeof(entry));
  22.     entry.intf_len =sizeof(ebuf);
  23. // 遍历所有接口
  24. while(count < MAX_INTERFACES){
  25. if(intf_loop(intf, NULL, NULL)<0)
  26. break;
  27. // 简化示例:实际需要完整遍历
  28. break;
  29. }
  30. // 检查重复MAC
  31. for(=0; i < count; i++){
  32. for(= i +1; j < count; j++){
  33. if(memcmp(&macs[i].mac,&macs[j].mac, ETH_ADDR_LEN)==0){
  34.                 printf("警告: MAC地址重复!\n");
  35.                 printf("  %s: %s\n", macs[i].name,
  36.                        eth_ntop(&macs[i].mac, buf,sizeof(buf)));
  37.                 printf("  %s: %s\n", macs[j].name,
  38.                        eth_ntop(&macs[j].mac, buf,sizeof(buf)));
  39. }
  40. }
  41. }
  42.     intf_close(intf);
  43. return(0);
  44. }

10.7 使用dnet命令行工具

  1. # 构造以太网帧
  2. $ echo "hello"| dnet eth type 0x0800 src 00:11:22:33:44:55 dst ff:ff:ff:ff:ff:ff > frame.bin
  3. # 发送以太网帧(需要root)
  4. $ sudo dnet send eth0 < frame.bin
  5. # 显示ARP缓存
  6. $ dnet arp show
  7. # 添加ARP条目
  8. $ sudo dnet arp add 192.168.1.100:11:22:33:44:55

11. 常见问题与解决方案

11.1 权限问题

问题

  1. eth_open:Permission denied

原因:打开原始socket需要root权限

解决方案

  1. # Linux/macOS/BSD
  2. sudo ./your_program
  3. # 添加CAP_NET_RAW能力(仅Linux)
  4. sudo setcap cap_net_raw+ep ./your_program

11.2 设备不存在

问题

  1. eth_open:No such device

原因:接口名称错误

解决方案

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

11.3 BPF设备全部忙碌

问题

  1. eth_open:Device busy

原因:所有BPF设备都被占用

解决方案

  1. // 检查BPF设备占用
  2. $ ls -/dev/bpf*
  3. $ lsof /dev/bpf*
  4. // 释放未使用的BPF设备
  5. // 或增加BPF设备数量(需要配置内核)

11.4 macOS/BSD无法发送自定义源MAC

问题:发送的帧源MAC总是被覆盖

原因:驱动程序自动完成以太网头

解决方案

  1. // 设置BIOCSHDRCMPLT标志
  2. int i =1;
  3. ioctl(fd, BIOCSHDRCMPLT,&i);

11.5 DLPI附加失败

问题

  1. eth_open: DLPI attach failed

原因:PPA值错误或设备不支持

解决方案

  1. // 检查PPA值
  2. $ ls -/dev/net/
  3. $ netstat -in
  4. // 尝试不同的DLPI风格

11.6 AIX getkerninfo失败

问题

  1. eth_get:No such file or directory

原因:NDD信息不可用

解决方案

  1. # 检查NDD配置
  2. $ lsdev -| grep -i ether
  3. $ netstat -ia
  4. # 可能需要加载NDD驱动

11.7 MAC地址格式错误

问题

  1. eth_pton:Invalid argument

原因:MAC地址格式不正确

解决方案

  1. // 支持的格式
  2. "00:11:22:33:44:55"// 推荐
  3. "00-11-22-33-44-55"
  4. "0011:2233:4455"
  5. // 不支持的格式
  6. "001122334455"
  7. "00:11:22:33:44:55:66"// 长度错误

11.8 发送帧长度错误

问题

  1. eth_send:Message too long

原因:帧长度超过MTU

解决方案

  1. // 检查帧长度
  2. if(len > ETH_MTU){
  3.     fprintf(stderr,"帧长度超过MTU: %zu > %d\n", len, ETH_MTU);
  4. return(-1);
  5. }
  6. // 或分片发送

11.9 性能优化:缓冲区管理

问题:频繁打开/关闭设备导致性能下降

解决方案

  1. // 缓存设备句柄
  2. staticeth_t*eth_cache[MAX_INTERFACES];
  3. staticchar dev_cache[MAX_INTERFACES][16];
  4. eth_t*eth_get_cached(constchar*device)
  5. {
  6. int i;
  7. // 查找缓存
  8. for(=0; i < MAX_INTERFACES; i++){
  9. if(dev_cache[i][0]!='\0'&&
  10.             strcmp(dev_cache[i], device)==0){
  11. return(eth_cache[i]);
  12. }
  13. }
  14. // 未找到,打开新设备
  15. for(=0; i < MAX_INTERFACES; i++){
  16. if(dev_cache[i][0]=='\0'){
  17.             eth_cache[i]= eth_open(device);
  18. if(eth_cache[i]!= NULL){
  19.                 strlcpy(dev_cache[i], device,sizeof(dev_cache[i]));
  20. }
  21. return(eth_cache[i]);
  22. }
  23. }
  24. return(NULL);
  25. }

11.10 调试技巧

启用详细日志

  1. #define DEBUG_ETH 1
  2. #ifdef DEBUG_ETH
  3. #define DEBUG(fmt,...) fprintf(stderr,"[DEBUG] " fmt "\n",##__VA_ARGS__)
  4. #else
  5. #define DEBUG(fmt,...)
  6. #endif
  7. // 在关键位置添加日志
  8. DEBUG("打开设备: %s", device);
  9. DEBUG("获取MAC: %s", eth_ntop(&ea, buf,sizeof(buf)));
  10. DEBUG("发送帧: %zu 字节", len);

使用系统工具验证

  1. # Linux
  2. $ ip link show
  3. $ ip link set eth0 address 00:11:22:33:44:55
  4. # macOS/BSD
  5. $ ifconfig en0
  6. $ ifconfig en0 ether 00:11:22:33:44:55
  7. # Solaris
  8. $ ifconfig -a
  9. $ ifconfig hme0 ether 00:11:22:33:44:55
  10. # AIX
  11. $ netstat -ia
  12. $ chdev -l en0 -a netaddr=0x001122334455

抓包验证

  1. # 使用tcpdump抓包
  2. $ sudo tcpdump -i eth0 -XX
  3. # 使用Wireshark分析
  4. $ sudo tshark -i eth0

总结

本文档深入分析了libdnet MAC地址处理模块的跨平台实现,涵盖了以下核心内容:

主要特点

  1. 七大平台支持
  • Linux:Packet Socket
  • macOS/BSD:BPF
  • Solaris:DLPI/SNOOP
  • HP-UX:DLPI
  • AIX:NDD
  • Tru64:Packet Filter
  • Windows:不支持(需要第三方库)
  1. 核心功能
  • MAC地址获取/设置
  • 以太网帧发送
  • MAC地址格式转换
  • 支持VLAN标签
  1. 跨平台适配
  • 条件编译处理平台差异
  • 统一的API接口
  • 灵活的数据结构设计

实现亮点

  • Linux:简单高效的Packet Socket
  • macOS/BSD:成熟的BPF机制,sysctl获取MAC
  • Solaris:复杂的DLPI STREAMS接口
  • AIX:独特的getkerninfo接口

使用建议

  1. 优先使用读取操作:避免频繁修改MAC地址
  2. 注意权限要求:大多数操作需要root权限
  3. 处理平台差异:不同平台支持的功能不同
  4. 错误处理:正确处理平台特定的错误码

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

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

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx

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

猜你喜欢

  • 暂无文章