如何在 linux 上使用 libnl3(netlink 版本 3)获取接口的 ipv4 地址?

How to get ipv4 address of an interface using libnl3 (netlink version 3) on linux?

我正在学习 netlink 库版本 3,我想知道如何获取指定网络接口的 ipv4 地址。我可以获得 mac 地址,甚至可以从 link 数据结构中重新查询接口名称,但我不知道如何使用 libnl 和 libnl-route 库获取 IP 地址。我确实找到了一些使用 libnl-cli 库获取 ip 地址的代码,但那是为了将结果转储到文件描述符(想想 stdout)。我已将邮件发送到该图书馆的邮件列表,但尚未收到回复。

这是我的代码: https://gist.github.com/netskink/4f554ed6657954b17ab255ad5bc6d1f0

这是我的结果:

./stats 
Returned link name is enp6s0
Returned link addr is a0:36:9f:66:93:13

我已经看到使用 ioctls 检索 ip 地址的机制,但是由于 netlink lib 可以 return 使用 cli 子库的 ip 地址我认为它可以完成但我无法确定出一条路。

接口可以有多个地址(ipv4 和 ipv6 地址 - 代码示例给了我一个 ipv4 和一个 ipv6),所以没有 returns 接口一个地址的功能。如果只有你有具体的本地地址,你就可以打电话给 rtnl_addr_get。相反,您可以迭代地址。

#include <libnl3/netlink/cache.h>

void addr_cb(struct nl_object *o, void *data)
{
    int ifindex = (int)(intptr_t)data;
    struct rtnl_addr *addr = (rtnl_addr *)o;
    if (NULL == addr) {
        /* error */
        printf("addr is NULL %d\n", errno);
        return;
    }

    int cur_ifindex = rtnl_addr_get_ifindex(addr);
    if(cur_ifindex != ifindex)
        return;

    const struct nl_addr *local = rtnl_addr_get_local(addr);
    if (NULL == local) {
        /* error */
        printf("rtnl_addr_get failed\n");
        return;
    }

    char addr_str[ADDR_STR_BUF_SIZE];
    const char *addr_s = nl_addr2str(local, addr_str, sizeof(addr_str));
    if (NULL == addr_s) {
        /* error */
        printf("nl_addr2str failed\n");
        return;
    }
    fprintf(stdout, "\naddr is: %s\n", addr_s);
}

您可以从缓存中迭代地址并查看它们是否包含所需的地址(查看 ifindex)。请查看https://www.infradead.org/~tgr/libnl/doc/api/cache_8c_source.html以了解有用的功能(有一些过滤功能)。

int ifindex = rtnl_link_get_ifindex(p_rtnl_link);
printf("ifindex: %d\n", ifindex);

bool empty = nl_cache_is_empty(addr_cache);
printf("empty: %d\n", empty);

nl_cache_foreach(addr_cache,
        addr_cb, (void*)(intptr_t)ifindex);

并检查 ip 版本使用 rtnl_addr_get_family.

基于 user2518959 的回答。

rtnl_addr_alloc_cache和rtnl_link_alloc_cache都return一个nl_cacheobject/structure。即使这两个结果属于同一类型,它们也有不同的例程可以用于每个。

从 rtnl_addr_alloc_cache 编辑的 nl_cache return 可用于获取 rtnl_addr object/structures。反过来可以用来调用 rtnl_addr_get_local 来获取 ipv4 或 ipv6 地址。

相比之下,nl_cache returned from rtnl_link_alloc_cache 可用于获取接口名称(eth0,enp6s0,...)和 mac地址。套路分别是rtnl_link_get_by_name和rtnl_link_get_addr。

在任何一种情况下,两者之间的共同 link 是例程 rtnl_addr_get_index 和 rtnl_link_get_index 其中 return 一个接口索引,可用于关联任一条目来自每个缓存。 IE。来自 nl_cache 地址版本的接口 1 和来自 link nl_cache 的接口 1 是相同的接口。一个给出 ip 地址,另一个给出 mac 地址和名称。

最后,隧道将有 IP 地址但没有 mac,因此它不会有 link 名称或 mac 地址。

这里有一些代码显示了 user25185959 的方法和一个明确显示关系的替代方法。 user2518959将接口号传入回调过滤接口

#include <libnl3/netlink/netlink.h>
#include <libnl3/netlink/route/link.h>
#include <libnl3/netlink/route/addr.h>
#include <libnl3/netlink/cache.h>
#include <libnl3/netlink/route/addr.h>

#include <errno.h>



/*
gcc ipchange.c -o ipchange $(pkg-config --cflags --libs libnl-3.0 libnl-route-3.0 libnl-cli-3.0)
*/

#include <stdbool.h>

#define ADDR_STR_BUF_SIZE 80

void addr_cb(struct nl_object *p_nl_object, void *data) {

    int ifindex = (int) (intptr_t) data;  // this is the link index passed as a parm
    struct rtnl_addr *p_rtnl_addr;
    p_rtnl_addr = (struct rtnl_addr *) p_nl_object;
    int result;

    if (NULL == p_rtnl_addr) {
        /* error */
        printf("addr is NULL %d\n", errno);
        return;
    }

    // This routine is not mentioned in the doxygen help.  
    // It is listed under Attributes, but no descriptive text.
    // this routine just returns p_rtnl_addr->a_ifindex
    int cur_ifindex = rtnl_addr_get_ifindex(p_rtnl_addr);
    if(cur_ifindex != ifindex) {
        // skip interaces where the index differs.
        return;
    }

    // Adding this to see if I can filter on ipv4 addr
    // this routine just returns p_rtnl_addr->a_family
    // this is not the one to use
    // ./linux/netfilter.h:    NFPROTO_IPV6   = 10,
    // ./linux/netfilter.h:    NFPROTO_IPV4   =  2,
    // this is the one to use
    // x86_64-linux-gnu/bits/socket.h
    // defines AF_INET6 = PF_INET6 = 10
    // defines AF_INET  = PF_INET  = 2
    result = rtnl_addr_get_family(p_rtnl_addr);
    // printf( "family is %d\n",result);
    if (AF_INET6 == result) {
    // early exit, I don't care about IPV6
    return;
    }

    // This routine just returns p_rtnl_addr->a_local
    const struct nl_addr *p_nl_addr_local = rtnl_addr_get_local(p_rtnl_addr);
    if (NULL == p_nl_addr_local) {
        /* error */
        printf("rtnl_addr_get failed\n");
        return;
    }

    char addr_str[ADDR_STR_BUF_SIZE];
    const char *addr_s = nl_addr2str(p_nl_addr_local, addr_str, sizeof(addr_str));
    if (NULL == addr_s) {
        /* error */
        printf("nl_addr2str failed\n");
        return;
    }
    fprintf(stdout, "\naddr is: %s\n", addr_s);

}

int main(int argc, char **argv, char **envp) {

    int err;

    struct nl_sock *p_nl_sock;
    struct nl_cache *link_cache;
    struct nl_cache *addr_cache;

    struct rtnl_addr *p_rtnl_addr;
    struct nl_addr *p_nl_addr;
    struct nl_link *p_nl_link;

    struct rtnl_link *p_rtnl_link;


    char addr_str[ADDR_STR_BUF_SIZE];

    char *pchLinkName;
    char *pchLinkAddr;
    char *pchIPAddr;
    char *interface;
    interface = "enp6s0";
    pchLinkAddr = malloc(40);
    pchIPAddr = malloc(40);
    strcpy(pchLinkAddr,"11:22:33:44:55:66");
    strcpy(pchIPAddr,"123.456.789.abc");





    p_nl_sock = nl_socket_alloc();
    if (!p_nl_sock) {
        fprintf(stderr, "Could not allocate netlink socket.\n");
        exit(ENOMEM);
    }



    // Connect to socket
    if(err = nl_connect(p_nl_sock, NETLINK_ROUTE)) {
        fprintf(stderr, "netlink error: %s\n", nl_geterror(err));
        p_nl_sock = NULL;
        exit(err);
    }


    // Either choice, the result below is a mac address
    err = rtnl_link_alloc_cache(p_nl_sock, AF_UNSPEC, &link_cache);
    //err = rtnl_link_alloc_cache(p_nl_sock, AF_INET, &link_cache);
    //err = rtnl_link_alloc_cache(p_nl_sock, IFA_LOCAL, &link_cache);
    if (0 != err) {
        /* error */
    printf("rtnl_link_alloc_cache failed: %s\n", nl_geterror(err));
    return(EXIT_FAILURE);
    }

    err = rtnl_addr_alloc_cache(p_nl_sock, &addr_cache);
    if (0 != err) {
        /* error */
    printf("rtnl_addr_alloc_cache failed: %s\n", nl_geterror(err));
    return(EXIT_FAILURE);
    }



    p_rtnl_link = rtnl_link_get_by_name(link_cache, "enp6s0");
    if (NULL == p_rtnl_link) {
        /* error */
    printf("rtnl_link_get_by_name failed\n");
    return(EXIT_FAILURE);
    }


    pchLinkName = rtnl_link_get_name(p_rtnl_link);
    if (NULL == pchLinkName) {
        /* error */
    printf("rtnl_link_get_name failed\n");
    return(EXIT_FAILURE);
    }
    printf("Returned link name is %s\n",pchLinkName);


    ////////////////////////////////// mac address  
    p_nl_addr = rtnl_link_get_addr(p_rtnl_link);
    if (NULL == p_nl_addr) {
        /* error */
    printf("rtnl_link_get_addr failed\n");
    return(EXIT_FAILURE);
    }


    pchLinkAddr = nl_addr2str(p_nl_addr, pchLinkAddr, 40);
    if (NULL == pchLinkAddr) {
        /* error */
    printf("rtnl_link_get_name failed\n");
    return(EXIT_FAILURE);
    }
    printf("Returned link addr is %s\n",pchLinkAddr);



    ////////////////////////////////// ip address  
    // How to get ip address for a specified interface?








    //
    // The way she showed me.
    //


    // Return interface index of link object
    int ifindex = rtnl_link_get_ifindex(p_rtnl_link);
    printf("ifindex: %d\n", ifindex);

    // She gave me this but its not necessary
    // Returns true if the cache is empty. True if the cache is empty.
    // bool empty = nl_cache_is_empty(addr_cache);
    // printf("empty: %d\n", empty);

    // Call a callback on each element of the cache.  The
    // arg is passed on the callback function.
    // addr_cache is the cache to iterate on
    // addr_cb is the callback function
    // ifindex is the argument passed to the callback function
    // 
    nl_cache_foreach(addr_cache, addr_cb, (void*)(intptr_t)ifindex);



    // This shows that the link index returned from rtnl_addr_get_index
    // and rtnl_link_get_index are equivalent when using the rtnl_addr
    // and rtnl_link from the two respective caches.


   // Another way...
   // This will iterate through the cache of ip's
   printf("Getting the list of interfaces by ip addr cache\n");
   int count = nl_cache_nitems(addr_cache);
   printf("addr_cache has %d items\n",count);
   struct nl_object *p_nl_object;
   p_nl_object = nl_cache_get_first(addr_cache); 
   p_rtnl_addr = (struct rtnl_addr *) p_nl_object;
   for (int i=0; i<count; i++) {
    // This routine just returns p_rtnl_addr->a_local
    const struct nl_addr *p_nl_addr_local = rtnl_addr_get_local(p_rtnl_addr);
    if (NULL == p_nl_addr_local) {
        /* error */
        printf("rtnl_addr_get failed\n");
        return(EXIT_FAILURE);
    }



    int cur_ifindex = rtnl_addr_get_ifindex(p_rtnl_addr);
    printf("This is index %d\n",cur_ifindex);

    const char *addr_s = nl_addr2str(p_nl_addr_local, addr_str, sizeof(addr_str));
    if (NULL == addr_s) {
        /* error */
        printf("nl_addr2str failed\n");
        return(EXIT_FAILURE);
    }
    fprintf(stdout, "\naddr is: %s\n", addr_s);

    //       
    printf("%d\n",i);
    p_nl_object = nl_cache_get_next(p_nl_object); 
        p_rtnl_addr = (struct rtnl_addr *) p_nl_object;

    // Just for grins


    }



   // Another way...
   // This will iterate through the cache of LLC
   printf("Getting the list of interfaces by mac cache\n");
   count = nl_cache_nitems(link_cache);
   printf("addr_cache has %d items\n",count);
   p_nl_object = nl_cache_get_first(link_cache); 
   p_rtnl_link = (struct rtnl_link *) p_nl_object;
   for (int i=0; i<count; i++) {
    // This routine just returns p_rtnl_addr->a_local
    const struct nl_addr *p_nl_addr_mac = rtnl_link_get_addr(p_rtnl_link);
    if (NULL == p_nl_addr_mac) {
        /* error */
        printf("rtnl_addr_get failed\n");
        return(EXIT_FAILURE);
    }

    int cur_ifindex = rtnl_link_get_ifindex(p_rtnl_link);
    printf("This is index %d\n",cur_ifindex);

    const char *addr_s = nl_addr2str(p_nl_addr_mac, addr_str, sizeof(addr_str));
    if (NULL == addr_s) {
        /* error */
        printf("nl_addr2str failed\n");
        return(EXIT_FAILURE);
    }
    fprintf(stdout, "\naddr is: %s\n", addr_s);

    //       
    printf("%d\n",i);
    p_nl_object = nl_cache_get_next(p_nl_object); 
        p_rtnl_link = (struct rtnl_link *) p_nl_object;

    }


    return(EXIT_SUCCESS);
}