42、网络接口查询:getifaddrs()获取本机IP地址、网络接口信息

说实话,网络编程里最基础也最容易踩坑的一个需求,就是获取本机的IP地址。很多新手上来就想着读 /proc/net 或者解析 ifconfig 的输出,嗯,我当年也干过这种事。后来在做一个跨平台监控工具的时候,发现这些方法既不优雅也不可靠,直到遇到了 getifaddrs() 这个函数——它才是 POSIX 系统下获取网络接口信息的正统方式。

getifaddrs() 是什么?

简单说,这个函数能一次性把系统里所有网络接口的信息都捞出来。包括接口名、IP地址(IPv4和IPv6都能拿)、子网掩码、广播地址、甚至MAC地址。它返回的是一个链表,每个节点对应一个网络接口的地址信息。

我个人习惯把它叫做“网络接口的户口本”——你想查哪个接口的什么信息,翻翻这个链表就行了。

核心函数原型:

#include <sys/types.h>
#include <ifaddrs.h>

int getifaddrs(struct ifaddrs **ifap);
void freeifaddrs(struct ifaddrs *ifa);

调用一次 getifaddrs(),系统会动态分配内存,把接口信息填进 struct ifaddrs 结构体链表里。用完记得调用 freeifaddrs() 释放,不然内存泄漏等着你。

struct ifaddrs 结构体详解

这个结构体是理解整个函数的关键。我把它拆开来讲:

成员 类型 说明
ifa_next struct ifaddrs * 指向下一个接口节点,链表结构
ifa_name char * 接口名称,比如 "eth0"、"lo"、"wlan0"
ifa_flags unsigned int 接口标志位,比如 IFF_UP、IFF_RUNNING、IFF_LOOPBACK
ifa_addr struct sockaddr * 接口的IP地址(可能是IPv4或IPv6)
ifa_netmask struct sockaddr * 子网掩码
ifa_broadaddr struct sockaddr * 广播地址(仅对IPv4有效)
ifa_data void * 协议族特定的附加数据,一般用不到

这里有个坑要注意:ifa_addrstruct sockaddr * 类型,你不能直接拿来用。得根据 sa_family 判断是 AF_INET(IPv4)还是 AF_INET6(IPv6),然后强转成对应的结构体。我曾经在项目里忘了做这个判断,结果打印出来的IP地址全是乱码……排查了半天。

核心逻辑流程图

下面这张图展示了用 getifaddrs() 获取本机IP的完整流程:

getifaddrs() 获取本机IP地址流程 调用 getifaddrs(&ifap) 检查返回值(成功/失败) 返回 -1 遍历链表:for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) 判断 ifa->ifa_addr->sa_family AF_INET (IPv4) AF_INET6 (IPv6) inet_ntop() 转换 inet_ntop() 转换 freeifaddrs(ifap) 注意:必须释放内存,否则会造成泄漏

完整代码示例:获取本机所有IP地址

下面这个例子,是我在实际项目中提炼出来的。它能打印出所有非回环接口的IPv4和IPv6地址:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <arpa/inet.h>

int main() {
    struct ifaddrs *ifap, *ifa;
    struct sockaddr_in *sa;
    struct sockaddr_in6 *sa6;
    char addr_buf[INET6_ADDRSTRLEN];

    // 调用 getifaddrs 获取接口信息
    if (getifaddrs(&ifap) == -1) {
        perror("getifaddrs");
        return 1;
    }

    printf("本机网络接口信息:\n");
    printf("==================\n");

    // 遍历链表
    for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
        // 跳过没有地址的接口
        if (ifa->ifa_addr == NULL) continue;

        // 判断地址族
        if (ifa->ifa_addr->sa_family == AF_INET) {
            // IPv4 地址
            sa = (struct sockaddr_in *)ifa->ifa_addr;
            inet_ntop(AF_INET, &sa->sin_addr, addr_buf, sizeof(addr_buf));
            printf("接口: %-8s IPv4: %s", ifa->ifa_name, addr_buf);

            // 顺便打印子网掩码
            if (ifa->ifa_netmask != NULL) {
                sa = (struct sockaddr_in *)ifa->ifa_netmask;
                inet_ntop(AF_INET, &sa->sin_addr, addr_buf, sizeof(addr_buf));
                printf(" 掩码: %s", addr_buf);
            }
            printf("\n");

        } else if (ifa->ifa_addr->sa_family == AF_INET6) {
            // IPv6 地址
            sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
            inet_ntop(AF_INET6, &sa6->sin6_addr, addr_buf, sizeof(addr_buf));
            printf("接口: %-8s IPv6: %s\n", ifa->ifa_name, addr_buf);
        }
    }

    // 释放内存
    freeifaddrs(ifap);
    return 0;
}

编译与运行

编译的时候不需要链接额外的库,直接 gcc 就行:

gcc -o getifaddr getifaddr.c
./getifaddr

输出效果大概是这样:

本机网络接口信息:
==================
接口: lo       IPv4: 127.0.0.1  掩码: 255.0.0.0
接口: lo       IPv6: ::1
接口: eth0     IPv4: 192.168.1.100  掩码: 255.255.255.0
接口: eth0     IPv6: fe80::211:22ff:fe33:4455
接口: wlan0    IPv4: 10.0.0.5  掩码: 255.255.255.0

小技巧: 如果你想只获取某个特定接口(比如 eth0)的IP,可以在遍历时加个 strcmp(ifa->ifa_name, "eth0") == 0 的判断。我在写网络配置工具时经常这么干。

避坑指南

嗯,这里我要说几个我踩过的坑:

  • 内存泄漏: 调用 getifaddrs() 后,一定要记得 freeifaddrs()。我刚开始用的时候漏过一次,程序跑了三天,内存涨了200多MB……
  • 地址族判断: 别想当然地认为 ifa_addr 一定是 AF_INET。现在IPv6越来越普及,不判断 sa_family 直接强转,轻则打印乱码,重则段错误。
  • 回环接口: 如果你只想获取“真正”的网卡IP,记得用 ifa_flags & IFF_LOOPBACK 过滤掉 lo 接口。我曾经在写服务端程序时没过滤,结果客户端连上来的是127.0.0.1……
  • 线程安全: getifaddrs() 本身是线程安全的,但返回的链表是动态分配的,多线程环境下要注意同步释放。

特别注意: 在容器或虚拟化环境下,getifaddrs() 返回的接口信息可能和宿主机不一致。比如 Docker 容器里可能只有 eth0 和 lo,没有物理网卡的信息。做运维工具时一定要考虑到这种情况。

进阶用法:获取MAC地址

你可能想问:那MAC地址怎么拿?其实 getifaddrs() 本身不直接提供MAC地址。但有个取巧的办法——当 ifa_addr->sa_family == AF_PACKET 时,ifa_addr 实际上是一个 struct sockaddr_ll,里面包含了硬件地址信息。不过这个用法比较底层,我一般建议用 ioctl(SIOCGIFHWADDR) 来拿MAC,更直接。

好了,关于 getifaddrs() 的核心用法就这些。说白了,它就是一把万能钥匙,能打开系统网络接口信息的大门。你只要记住“调用-遍历-判断-释放”这四个步骤,基本不会出大问题。

公众号:蓝海资料掘金营,微信deep3321