2

I'm learning the netlink library version 3 and I want to know how to get the ipv4 address of a specified network interface. I can get the mac address and even requery the interface name from a link data structure, but I can not figure out how to get the ip address using the libnl and libnl-route libs. I did find some code to get the ip address using the libnl-cli lib but that is for dumping the results to a file descriptor (think stdout). I have sent mail to the mailing list for this library but I have not gotten a response.

Here is my code: https://gist.github.com/netskink/4f554ed6657954b17ab255ad5bc6d1f0

Here are my results:

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

Ive seen the mechanism to retrieve the ip address using ioctls, but since netlink lib can return the ip address using the cli sublibrary I figure it can be done but I can not figure out a way.

netskink
  • 4,033
  • 2
  • 34
  • 46
  • Possibly the source to `nl-addr-list` is of interest? https://github.com/thom311/libnl/blob/master/src/nl-addr-list.c – larsks Feb 17 '17 at 21:58
  • 1
    That code uses the cli sublibrary. That is not what I am interested in. See my original question. – netskink Feb 17 '17 at 22:04

2 Answers2

4

Interface can have multiple addresses (ipv4 and ipv6 addresses - code sample gave me one ipv4 and one ipv6), so there is no such function that returns one address for interface. If only you had specific local address, you could have called rtnl_addr_get. Instead you can iterate addresses.

#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);
}

You can iterate addresses from cache and see if they contain needed address (looking at ifindex). Please take a look at https://www.infradead.org/~tgr/libnl/doc/api/cache_8c_source.html for useful functions (there is some filter function).

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);

And to check ip version use rtnl_addr_get_family.

galka
  • 186
  • 7
  • Thank you. I will attempt to use your solution tomorrow. Many thanks. – netskink Feb 19 '17 at 17:21
  • That indeed works. Many thanks. I've marked your solution as the answer and up voted it for visibility. I appreciate your help a lot. – netskink Feb 20 '17 at 15:39
3

Building upon user2518959's answer.

The rtnl_addr_alloc_cache and rtnl_link_alloc_cache both return a nl_cache object/structure. Even those these two results are of the same type, they have different routines which can be used on each.

The nl_cache returned from rtnl_addr_alloc_cache can be used to get rtnl_addr object/structures. Which are in turn can be used to call rtnl_addr_get_local to get the ipv4 or ipv6 address.

In contrast, the nl_cache returned from rtnl_link_alloc_cache can be used to get the interface name (eth0, enp6s0, ...) and the mac address. The routines are rtnl_link_get_by_name and rtnl_link_get_addr respectively.

In either case, the common link between the two is routine rtnl_addr_get_index and rtnl_link_get_index which return an interface index which can be used to relate either entry from each cache. ie. interface 1 from the addr version of nl_cache and interface 1 from the link nl_cache are the same interface. One gives the ip address and the other gives the mac address and name.

Lastly, a tunnel will have an ip address but no mac so it will not have a link name or mac address.

Here is some code which shows user25185959 approach and an alternate method which shows the relationship explictly. User2518959 passed the interface number into the callback to filter out interfaces.

#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);
}
netskink
  • 4,033
  • 2
  • 34
  • 46