获取接口地址 getifaddrs() 函数:

在 man 3 getifaddrs 中,有一段示例代码,我直接抄过来了:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/if_link.h>

int main()
{
    struct ifaddrs *ifaddr, *ifa;
    int family, s, n;
    char host[NI_MAXHOST];

    if (getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        exit(EXIT_FAILURE);
    }

    /* Walk through linked list, maintaining head pointer so we
       can free list later */

    for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
        if (ifa->ifa_addr == NULL)
            continue;

        family = ifa->ifa_addr->sa_family;

        /* Display interface name and family (including symbolic
           form of the latter for the common families) */

        printf("%-8s %s (%d)\n",
                ifa->ifa_name,
                (family == AF_PACKET) ? "AF_PACKET" :
                (family == AF_INET) ? "AF_INET" :
                (family == AF_INET6) ? "AF_INET6" : "???",
                family);

        /* For an AF_INET* interface address, display the address */

        if (family == AF_INET || family == AF_INET6) {
            s = getnameinfo(ifa->ifa_addr,
                            (family == AF_INET) ? sizeof(struct sockaddr_in) :
                                             sizeof(struct sockaddr_in6),
                            host, NI_MAXHOST,
                            NULL, 0, NI_NUMERICHOST);
            if (s != 0) {
                printf("getnameinfo() failed: %s\n", gai_strerror(s));
                    exit(EXIT_FAILURE);
            }

            printf("\t\taddress: <%s>\n", host);

        } else if (family == AF_PACKET && ifa->ifa_data != NULL) {
            struct rtnl_link_stats *stats = ifa->ifa_data;

            printf("\t\ttx_packets = %10u; rx_packets = %10u\n"
                   "\t\ttx_bytes   = %10u; rx_bytes   = %10u\n",
                   stats->tx_packets, stats->rx_packets,
                   stats->tx_bytes, stats->rx_bytes);
        }
    }

    freeifaddrs(ifaddr);
    exit(EXIT_SUCCESS);
}

其中,NI_MAXHOST 是定义在 netdb.h 里的宏,输出效果大概是这样的:

lo       AF_PACKET (17)
                    tx_packets =      34796; rx_packets =      34796
                    tx_bytes   =   24606813; rx_bytes   =   24606813
enp8s0   AF_PACKET (17)
                    tx_packets =          0; rx_packets =          0
                    tx_bytes   =          0; rx_bytes   =          0
wlp9s0   AF_PACKET (17)
                    tx_packets =      37529; rx_packets =      36399
                    tx_bytes   =    6396242; rx_bytes   =   23919476
lo       AF_INET (2)
                    address: <127.0.0.1>
wlp9s0   AF_INET (2)
                    address: <192.168.1.115>
lo       AF_INET6 (10)
                    address: <::1>
wlp9s0   AF_INET6 (10)
                    address: <fe80::e733:949:93bc:ea49%wlp9s0>

该函数最早出现在 BSD 系统中,而 Linux 直到 glibc 2.3 才出现该函数,而且 2.3.3 之前只支持 IPv4,所以,UNP 中完全没提到这个,却用 ioctl 实现了一个类似功能的 get_ifi_info() 函数。

该函数功能强大,可以得到很多信息,但是使用也很简单,唯一需要注意的是:

The addresses returned on Linux will usually be the IPv4 and IPv6 addresses assigned to the interface, but also one AF_PACKET address per interface containing lower-level details about the interface and its physical layer. In this case, the ifa_data field may contain a pointer to a struct rtnl_link_stats, defined in <linux/if_link.h> (in Linux 2.4 and earlier, struct net_device_stats, defined in <linux/netdevice.h>), which contains various interface attributes and statistics.

接口索引 if_nameindex

接口索引是一个正值(从不为 0),是一个唯一识别的编号,并与实际上的唯一的逻辑接口相关。RFC 3493 表明它是为了 IPv6 才引入的,不过依然使用与 IPv4。RFC 1213 中也有很多叙述。

接口索引通常可以代替接口名字。这里就是要介绍一下转换接口名字和索引的几个函数。

首先是一个结构体:

struct if_nameindex {
    unsigned int if_index; /* 1, 2, ... */
    char *if_name;         /* "eth0", "lo",... */
}

虽然我的系统(ubuntu 16.04)上这个结构体只有两个元素,但实际上在 man 3 if_nameindex 中,明确指出,这个结构体最少要包含这两项,所以其他系统上可能不同。

if_nameindex() 函数用于获取所有的接口名字和索引值,并保存在 if_nameindex 结构体中,而这个结构体是函数中动态分配的,所以还有一个 if_freenameindex() 函数用于释放内存:

struct if_nameindex *if_nameindex(void);
void if_freenameindex(struct if_nameindex *);

大概是这么用的:

#include <net/if.h>   /* 这是使用 if_nameindex 的必要头文件 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    struct if_nameindex *ifni, *i;

    ifni = if_nameindex();
    if (ifni == NULL) {
        perror("if_nameindex error");
        exit(EXIT_FAILURE);
    }
    if (i = ifni; ! (i->if_index == 0 || i->if_name == NULL); ++i)
        printf("%u %s\n", i->if_index, i->if_name);
    if_freenameindex(ifni);

    return 0;
}

我的输出:

1 lo
2 enp8s0
3 wlp9s0

然后是 if_nametoindex() 和 if_indextoname() 的接口名与索引互转:

unsigned int if_nametoindex(const char *ifname);
char *if_indextoname(unsigned int ifindex, char *ifname);

非常简单,一看就会,所以不做过多解释。但是!if_indextoname() 的第二个参数,是要保存接口名字的大小,所以要足够大,而这个最小值也定义在 net/if.h 里,是 IF_NAMESIZE,在我电脑上是 16。

我简单 Google 了一下,见到索引值的地方只有 Cisco 的教程,说到 SNMP 网络需要这个,所以,更多的还是参见 RFC 吧。