You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何不依赖ip命令,通过系统内部API获取默认网络接口名称及其MAC地址

如何不依赖ip命令,通过系统内部API获取默认网络接口名称及其MAC地址

没问题!完全可以通过Linux原生的系统API(比如rtnetlinkgetifaddrs)实现这个需求,彻底摆脱对ipawk这类外部工具的依赖,这样代码的兼容性和稳定性都会更强。下面我给你一步步拆解实现思路,再附上完整的可运行代码。


核心思路拆解

1. 用rtnetlink获取默认网络接口

原来的代码通过解析ip route命令输出拿到默认接口,本质是让用户空间工具帮我们读内核路由表。现在我们直接用rtnetlink(Linux路由子系统的标准用户态通信接口)和内核交互,直接获取路由表信息,找到默认路由对应的网络接口。

rtnetlink的工作流程大概是:

  • 创建一个NETLINK_ROUTE类型的socket
  • 构造并发送路由信息请求(RTM_GETROUTE
  • 接收内核返回的路由消息,解析出默认路由(目标为0.0.0.0/0)对应的接口索引
  • 通过接口索引,用if_indextoname函数拿到接口名称

2. 用getifaddrsioctl获取MAC地址

你原来的net_get_mac_address函数已经用了getifaddrs,这是POSIX标准API,完全无外部依赖,完全可以保留。我也会补充一个ioctl的实现作为备选,两种方式都可靠。


完整实现代码

下面的代码完全替换了原来的外部命令调用,全部用系统API实现:

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

// 用rtnetlink获取默认网络接口名称
bool net_get_default_interface(char *if_name, size_t size) {
    if (if_name == NULL || size == 0) {
        fprintf(stderr, "net_get_default_interface: invalid parameters\n");
        return false;
    }

    int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sockfd < 0) {
        perror("net_get_default_interface: socket creation failed");
        return false;
    }

    // 构造rtnetlink请求
    struct {
        struct nlmsghdr nh;
        struct rtmsg rtm;
        char buf[1024];
    } req = {0};

    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
    req.nh.nlmsg_type = RTM_GETROUTE;
    req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
    req.rtm.rtm_family = AF_INET; // 只关心IPv4默认路由
    req.rtm.rtm_table = RT_TABLE_MAIN;

    // 发送请求到内核
    if (send(sockfd, &req, req.nh.nlmsg_len, 0) < 0) {
        perror("net_get_default_interface: send failed");
        close(sockfd);
        return false;
    }

    // 接收内核返回的消息
    char resp_buf[8192] = {0};
    ssize_t resp_len = recv(sockfd, resp_buf, sizeof(resp_buf), 0);
    if (resp_len < 0) {
        perror("net_get_default_interface: recv failed");
        close(sockfd);
        return false;
    }

    bool found = false;
    struct nlmsghdr *nh;
    // 遍历所有返回的netlink消息
    for (nh = (struct nlmsghdr *)resp_buf; NLMSG_OK(nh, resp_len); nh = NLMSG_NEXT(nh, resp_len)) {
        if (nh->nlmsg_type == NLMSG_DONE) {
            break;
        }
        if (nh->nlmsg_type != RTM_NEWROUTE) {
            continue;
        }

        struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh);
        // 检查是否是默认路由:目标地址掩码长度为0(对应0.0.0.0/0)
        if (rtm->rtm_dst_len != 0) {
            continue;
        }

        // 解析消息中的属性,找到接口索引
        struct rtattr *rta = RTM_RTA(rtm);
        int rta_len = RTM_PAYLOAD(nh);
        int if_index = -1;

        for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
            if (rta->rta_type == RTA_OIF) {
                if_index = *(int *)RTA_DATA(rta);
                break;
            }
        }

        // 用接口索引获取接口名称
        if (if_index != -1 && if_indextoname(if_index, if_name)) {
            found = true;
            break;
        }
    }

    close(sockfd);
    if (!found) {
        fprintf(stderr, "net_get_default_interface: no default route found\n");
    }
    return found;
}

// 用getifaddrs获取指定接口的MAC地址(无外部依赖)
bool net_get_mac_address(const char* interface_name, unsigned char* mac_buffer, size_t buffer_size) {
    if (interface_name == NULL || mac_buffer == NULL || buffer_size < 6) {
        fprintf(stderr, "net_get_mac_address: invalid parameters\n");
        return false;
    }

    struct ifaddrs *ifaddr = NULL, *ifa = NULL;
    bool success = false;

    if (getifaddrs(&ifaddr) == -1) {
        perror("net_get_mac_address: getifaddrs failed");
        return false;
    }

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        // 匹配接口名称,且是链路层地址(AF_PACKET)
        if (ifa->ifa_addr && strcmp(ifa->ifa_name, interface_name) == 0 &&
            ifa->ifa_addr->sa_family == AF_PACKET) {
            struct sockaddr_ll *s = (struct sockaddr_ll*)ifa->ifa_addr;
            if (s->sll_halen == 6) { // MAC地址固定6字节
                memcpy(mac_buffer, s->sll_addr, 6);
                success = true;
                break;
            }
        }
    }

    freeifaddrs(ifaddr);
    if (!success) {
        fprintf(stderr, "net_get_mac_address: failed to get MAC for interface %s\n", interface_name);
    }
    return success;
}

// 备选:用ioctl获取MAC地址的实现(纯系统API)
bool net_get_mac_address_ioctl(const char* interface_name, unsigned char* mac_buffer, size_t buffer_size) {
    if (interface_name == NULL || mac_buffer == NULL || buffer_size < 6) {
        fprintf(stderr, "net_get_mac_address_ioctl: invalid parameters\n");
        return false;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("net_get_mac_address_ioctl: socket creation failed");
        return false;
    }

    struct ifreq ifr = {0};
    strncpy(ifr.ifr_name, interface_name, IFNAMSIZ - 1);

    if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1) {
        perror("net_get_mac_address_ioctl: ioctl SIOCGIFHWADDR failed");
        close(sockfd);
        return false;
    }

    close(sockfd);
    memcpy(mac_buffer, ifr.ifr_hwaddr.sa_data, 6);
    return true;
}

int main() {
    char interface[IFNAMSIZ] = {0};
    if (!net_get_default_interface(interface, sizeof(interface))) {
        fprintf(stderr, "main: failed to get default interface\n");
        return 1;
    }
    printf("Default Interface: %s\n", interface);

    unsigned char mac[6] = {0};
    if (net_get_mac_address(interface, mac, sizeof(mac))) {
        printf("MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    } else {
        // 尝试备选的ioctl方式
        if (net_get_mac_address_ioctl(interface, mac, sizeof(mac))) {
            printf("MAC Address (ioctl): %02x:%02x:%02x:%02x:%02x:%02x\n",
                   mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        } else {
            fprintf(stderr, "main: failed to get MAC address\n");
            return 1;
        }
    }

    return 0;
}

编译运行与测试

编译命令

用GCC编译即可:

gcc -o get_default_if_mac get_default_if_mac.c
./get_default_if_mac

预期输出

和你原来的输出一致,比如:

Default Interface: enp4s0
MAC Address: b4:2e:99:f4:5e:d5

关键优势

  1. 完全无外部依赖:所有逻辑基于Linux系统原生API,避免了命令输出格式变化导致的解析失败
  2. 稳定性更强:rtnetlink是内核提供的标准接口,比解析命令输出可靠得多,能处理各种复杂路由场景
  3. 兼容性好:用到的API都是Linux系统长期支持的,在绝大多数发行版上都能正常运行

如果需要调整IPv6支持、错误处理逻辑或者其他细节,随时告诉我就行!

火山引擎 最新活动