如何在macOS中用C获取设备所连节点的目的MAC地址
在macOS下用C语言实现ARP请求获取目的MAC地址
直接构造并发送ARP请求报文,通过原始套接字接收响应是最高效的方案,无需解析arp命令或依赖已废弃的ioctl接口。以下是具体实现步骤和代码示例:
关键实现步骤
- 创建链路层原始套接字:macOS使用
PF_LINK域的原始套接字处理链路层报文,协议指定为ETHERTYPE_ARP。 - 构造ARP请求报文:包含以太网头部(目的MAC设为广播地址)和ARP请求帧(指定目标IP,目的MAC暂设为全0)。
- 绑定到目标网卡:通过
getifaddrs获取网卡的接口索引,用setsockopt将套接字绑定到该接口,确保报文从指定网卡发出。 - 发送请求并监听响应:发送ARP广播请求后,循环接收报文,过滤出对应目标IP的ARP响应,提取其中的MAC地址。
代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <net/if.h> #include <net/if_arp.h> #include <netinet/in.h> #include <arpa/inet.h> #include <ifaddrs.h> #include <net/ethernet.h> // 以太网头部结构体 typedef struct ethernet_header { uint8_t dst_mac[ETHER_ADDR_LEN]; uint8_t src_mac[ETHER_ADDR_LEN]; uint16_t ether_type; } ethernet_header_t; // ARP报文结构体(以太网+IP场景) typedef struct arp_packet { uint16_t hardware_type; uint16_t protocol_type; uint8_t hardware_addr_len; uint8_t protocol_addr_len; uint16_t operation; uint8_t sender_mac[ETHER_ADDR_LEN]; uint8_t sender_ip[4]; uint8_t target_mac[ETHER_ADDR_LEN]; uint8_t target_ip[4]; } arp_packet_t; // 获取指定网卡的接口索引和MAC地址 int get_if_info(const char* if_name, uint32_t* if_index, uint8_t* mac_addr) { struct ifaddrs *ifap, *ifa; int ret = -1; if (getifaddrs(&ifap) == -1) { perror("getifaddrs"); return ret; } for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) continue; if (strcmp(ifa->ifa_name, if_name) == 0 && ifa->ifa_addr->sa_family == AF_LINK) { struct sockaddr_dl* sdl = (struct sockaddr_dl*)ifa->ifa_addr; *if_index = sdl->sdl_index; memcpy(mac_addr, LLADDR(sdl), ETHER_ADDR_LEN); ret = 0; break; } } freeifaddrs(ifap); return ret; } // 获取指定网卡的本机IP int get_if_ip(const char* if_name, uint8_t* ip_addr) { struct ifaddrs *ifap, *ifa; int ret = -1; if (getifaddrs(&ifap) == -1) { perror("getifaddrs"); return ret; } for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) continue; if (strcmp(ifa->ifa_name, if_name) == 0 && ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)ifa->ifa_addr; memcpy(ip_addr, &sin->sin_addr.s_addr, 4); ret = 0; break; } } freeifaddrs(ifap); return ret; } // 发送ARP请求并获取目标MAC int arp_request(const char* if_name, const char* target_ip_str, uint8_t* target_mac) { int sockfd; uint32_t if_index; uint8_t src_mac[ETHER_ADDR_LEN], src_ip[4]; struct sockaddr_ll sll; uint8_t packet[sizeof(ethernet_header_t) + sizeof(arp_packet_t)]; ethernet_header_t* eth_hdr = (ethernet_header_t*)packet; arp_packet_t* arp_pkt = (arp_packet_t*)(packet + sizeof(ethernet_header_t)); struct in_addr target_ip; // 解析目标IP if (inet_aton(target_ip_str, &target_ip) == 0) { fprintf(stderr, "Invalid target IP\n"); return -1; } // 获取网卡MAC和接口索引 if (get_if_info(if_name, &if_index, src_mac) != 0) { fprintf(stderr, "Failed to get interface MAC\n"); return -1; } // 获取网卡本机IP if (get_if_ip(if_name, src_ip) != 0) { fprintf(stderr, "Failed to get interface IP\n"); return -1; } // 创建原始套接字 sockfd = socket(PF_LINK, SOCK_RAW, htons(ETHERTYPE_ARP)); if (sockfd == -1) { perror("socket"); return -1; } // 绑定到指定网卡 memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_LINK; sll.sll_ifindex = if_index; if (bind(sockfd, (struct sockaddr*)&sll, sizeof(sll)) == -1) { perror("bind"); close(sockfd); return -1; } // 构造以太网头部 memset(eth_hdr->dst_mac, 0xff, ETHER_ADDR_LEN); // 广播地址 memcpy(eth_hdr->src_mac, src_mac, ETHER_ADDR_LEN); eth_hdr->ether_type = htons(ETHERTYPE_ARP); // 构造ARP请求报文 arp_pkt->hardware_type = htons(ARPHRD_ETHER); arp_pkt->protocol_type = htons(ETHERTYPE_IP); arp_pkt->hardware_addr_len = ETHER_ADDR_LEN; arp_pkt->protocol_addr_len = 4; arp_pkt->operation = htons(ARPOP_REQUEST); memcpy(arp_pkt->sender_mac, src_mac, ETHER_ADDR_LEN); memcpy(arp_pkt->sender_ip, src_ip, 4); memset(arp_pkt->target_mac, 0, ETHER_ADDR_LEN); memcpy(arp_pkt->target_ip, &target_ip.s_addr, 4); // 发送ARP请求 ssize_t sent = send(sockfd, packet, sizeof(packet), 0); if (sent == -1) { perror("send"); close(sockfd); return -1; } // 监听ARP响应 uint8_t recv_buf[1024]; struct sockaddr_ll recv_sll; socklen_t recv_sll_len = sizeof(recv_sll); while (1) { ssize_t recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&recv_sll, &recv_sll_len); if (recv_len == -1) { perror("recvfrom"); close(sockfd); return -1; } if (recv_len < sizeof(ethernet_header_t) + sizeof(arp_packet_t)) continue; ethernet_header_t* recv_eth_hdr = (ethernet_header_t*)recv_buf; arp_packet_t* recv_arp_pkt = (arp_packet_t*)(recv_buf + sizeof(ethernet_header_t)); // 过滤目标ARP响应 if (ntohs(recv_arp_pkt->operation) == ARPOP_REPLY && memcmp(recv_arp_pkt->sender_ip, &target_ip.s_addr, 4) == 0 && memcmp(recv_arp_pkt->target_ip, src_ip, 4) == 0) { memcpy(target_mac, recv_arp_pkt->sender_mac, ETHER_ADDR_LEN); close(sockfd); return 0; } } } int main(int argc, char* argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <interface> <target_ip>\n", argv[0]); return 1; } uint8_t target_mac[ETHER_ADDR_LEN]; if (arp_request(argv[1], argv[2], target_mac) == 0) { printf("Target MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", target_mac[0], target_mac[1], target_mac[2], target_mac[3], target_mac[4], target_mac[5]); return 0; } else { fprintf(stderr, "Failed to get target MAC\n"); return 1; } }
注意事项
- 必须以root权限运行程序(原始套接字需要系统特权)。
- 运行时需指定正确的网卡名称(如
en0)和目标IP地址。 - 所有网络协议字段需用
htons/ntohs完成主机字节序与网络字节序的转换。 - 接收响应时严格过滤,避免误处理无关ARP报文。
内容的提问来源于stack exchange,提问作者Spencer Nold
相关产品推荐
相关产品推荐

