You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 04:47:33