如何从已建立连接的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
相关产品推荐
相关产品推荐

