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

如何从已建立连接的Socket获取本地MAC地址?

获取已建立TCP连接对应的本地MAC地址

你提到的「先拿本地IP再查MAC」的方案确实有硬伤——如果多个网卡绑定了相同IP,根本没法区分连接实际用的是哪个接口。要精准拿到TCP连接关联的本地MAC,核心是找到连接对应的出口网卡索引,再通过索引查MAC,这样就能避开IP重复的问题。

下面以Linux为例,给出具体实现思路和代码:

核心逻辑

TCP连接建立时,内核会根据路由表选择去往远端IP的出口网卡。我们可以按以下步骤操作:

  • 从已连接的socket获取远端IP地址
  • 查询系统路由表,找到去往该远端IP的出口网卡索引
  • 通过网卡索引读取对应的MAC地址

这个逻辑和内核选路逻辑完全一致,哪怕多网卡IP相同,也能精准定位到实际使用的接口。

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/rtnetlink.h>

// 通过远端IP获取出口接口索引
int get_outgoing_if_index(uint32_t dst_ip) {
    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0) {
        perror("socket");
        return -1;
    }

    struct nlmsghdr *nlh = malloc(NLMSG_SPACE(sizeof(struct rtmsg)));
    struct rtmsg *rtm = (struct rtmsg*)NLMSG_DATA(nlh);
    struct rtattr *rta = RTM_RTA(rtm);

    memset(nlh, 0, NLMSG_SPACE(sizeof(struct rtmsg)));
    nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
    nlh->nlmsg_type = RTM_GETROUTE;
    nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
    nlh->nlmsg_seq = 1;
    nlh->nlmsg_pid = getpid();

    rtm->rtm_family = AF_INET;
    rtm->rtm_dst_len = 32;
    rtm->rtm_table = RT_TABLE_MAIN;

    // 设置目的IP
    rta->rta_type = RTA_DST;
    rta->rta_len = RTA_LENGTH(sizeof(uint32_t));
    memcpy(RTA_DATA(rta), &dst_ip, sizeof(uint32_t));
    nlh->nlmsg_len += rta->rta_len;

    if (send(sock, nlh, nlh->nlmsg_len, 0) < 0) {
        perror("send");
        close(sock);
        free(nlh);
        return -1;
    }

    char buf[8192];
    int len = recv(sock, buf, sizeof(buf), 0);
    if (len < 0) {
        perror("recv");
        close(sock);
        free(nlh);
        return -1;
    }

    int if_index = -1;
    for (nlh = (struct nlmsghdr*)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
        if (nlh->nlmsg_type == NLMSG_DONE) break;
        if (nlh->nlmsg_type != RTM_NEWROUTE) continue;

        rtm = (struct rtmsg*)NLMSG_DATA(nlh);
        if (rtm->rtm_family != AF_INET) continue;

        rta = RTM_RTA(rtm);
        int rta_len = RTM_PAYLOAD(nlh);
        while (RTA_OK(rta, rta_len)) {
            if (rta->rta_type == RTA_OIF) {
                if_index = *(int*)RTA_DATA(rta);
                break;
            }
            rta = RTA_NEXT(rta, rta_len);
        }
        if (if_index != -1) break;
    }

    close(sock);
    free(nlh);
    return if_index;
}

// 通过接口索引获取MAC地址
int get_mac_by_if_index(int if_index, unsigned char mac[6]) {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket");
        return -1;
    }

    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_ifindex = if_index;
    if (ioctl(sock, SIOCGIFNAME, &ifr) < 0) {
        perror("SIOCGIFNAME");
        close(sock);
        return -1;
    }

    if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
        perror("SIOCGIFHWADDR");
        close(sock);
        return -1;
    }

    memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
    close(sock);
    return 0;
}

int main() {
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in tgt = {
        .sin_family = AF_INET,
        .sin_port = htons(443),
        .sin_addr.s_addr = htonl(0x08080808) // 8.8.8.8
    };

    if (connect(fd, (struct sockaddr*)&tgt, sizeof(tgt)) < 0) {
        perror("connect");
        close(fd);
        return 1;
    }

    // 获取远端IP
    struct sockaddr_in peer_addr;
    socklen_t peer_len = sizeof(peer_addr);
    if (getpeername(fd, (struct sockaddr*)&peer_addr, &peer_len) < 0) {
        perror("getpeername");
        close(fd);
        return 1;
    }
    uint32_t dst_ip = ntohl(peer_addr.sin_addr.s_addr);

    // 获取出口接口索引
    int if_index = get_outgoing_if_index(dst_ip);
    if (if_index == -1) {
        close(fd);
        return 1;
    }

    // 获取MAC地址
    unsigned char mac[6];
    if (get_mac_by_if_index(if_index, mac) < 0) {
        close(fd);
        return 1;
    }

    printf("本地MAC地址: %02x:%02x:%02x:%02x:%02x:%02x\n",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    close(fd);
    return 0;
}

注意事项

  • 该代码仅适用于Linux系统,Windows平台需要使用GetIpForwardTable2等API实现路由查询逻辑
  • 程序需要root权限(或添加CAP_NET_ADMIN权限)才能调用NETLINK_ROUTE相关操作
  • 路由表查询逻辑会完全匹配内核选路规则,确保接口索引的准确性

内容的提问来源于stack exchange,提问作者Bogudan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 18:41:04