Posted by: Airtower | 2010-07-16

First steps with libnl

My GLib based program needs to get a list of the IP addresses configured on an interface in my Linux system. I knew that the preferred way to get this kind of information is via netlink socket communication and that libnl provides an abstraction layer for that, so I had to figure out how to use the libnl API. (If you don’t know what a netlink socket is, read “Why and How to Use Netlink Socket“.) The libnl on-line API documentation is for the current development tree and does not match the version 1.1 I have installed, but that could be solved by generating the API doc from the sources. The only tutorial style example I found was “Adventures in Netlink“, which was really helpful, especially because the use case is quite similar.

Connecting to netlink

struct nl_handle *handle = nl_handle_alloc();
nl_connect(handle, NETLINK_ROUTE);
struct nl_cache *link_cache = rtnl_link_alloc_cache(handle);
struct nl_cache *addr_cache = rtnl_addr_alloc_cache(handle);

The first step is establishing the netlink connection to use. The code above allocates the socket, connects and uses the socket to fill caches for link and address data with data from the kernel. The NETLINK_ROUTE parameter to nl_connect lets connects a routing family socket, which can be used to perform many operations related to link, address and route management.

Get the relevant addresses

struct rtnl_addr *filter = rtnl_addr_alloc();
rtnl_addr_set_ifindex(filter, rtnl_link_name2i(link_cache, name));

It is normal for one interface to have multiple IPv6 addresses. While this is unusual for IPv4, it is possible. The filter structure is used to select which of these I want to read. Basically, it is an address structure in which only some fields are set, and addresses that match those fields are selected. The first thing I set is the interface. name is a char * that contains something like eth0, rtnl_link_name2i() gets the matching interface index.

GPtrArray *a = g_ptr_array_new();
g_ptr_array_set_free_func(a, g_object_unref);

GPtrArray is a dynamically sized array that contains pointers to arbitrary objects – really useful! This will be used to store the addresses.

rtnl_addr_set_family(filter, AF_INET6);
nl_cache_foreach_filter(addr_cache, (struct nl_object *) filter,
                        add_addr_to_array, a);
rtnl_addr_set_family(filter, AF_INET);
nl_cache_foreach_filter(addr_cache, (struct nl_object *) filter,
                        add_addr_to_array, a);

These are the actual filter calls. nl_cache_foreach_filter() calls the function specified as second argument for each matching address with the address and the third argument (the array) as parameters. Note that I change the filter’s address family attribute to get both IPv6 and IPv4 addresses.

The callback function

void add_addr_to_array(struct nl_object *obj, void *data)
{
    struct nl_addr *naddr =
        rtnl_addr_get_local((struct rtnl_addr *) obj);
    gint family = nl_addr_get_family(naddr);
    GInetAddress *addr = NULL;
    if (family == AF_INET6)
        addr = g_inet_address_new_from_bytes(
                nl_addr_get_binary_addr(naddr),
                G_SOCKET_FAMILY_IPV6);
    else if (family == AF_INET)
        addr = g_inet_address_new_from_bytes(
                nl_addr_get_binary_addr(naddr),
                G_SOCKET_FAMILY_IPV4);
    else
        g_assert_not_reached();
    g_ptr_array_add((GPtrArray *) data, addr);
}

This is actually quite simple:

  1. Get the netlink address object.
  2. Get the address family (only IPv6 or IPv4 because that’s what I’m filtering for).
  3. Depending on the family, create a GInetAddress with the right socket family.
  4. Add the address to the array and return.

After both calls to nl_cache_foreach_filter() are done, the array will contain GInetAddress objects for all IP addresses on the selected interface, just what I need. 🙂

Cleaning up

nl_cache_free(addr_cache);
nl_cache_free(link_cache);
rtnl_addr_put(filter);
nl_handle_destroy(handle);

Some of the libnl cleanup functions have unusual names, to I wanted to mention them here. Also remember that this simple example does no error handling, so be careful. 😉

Advertisements

Responses

  1. getifaddrs would have been slightly easier and more portable.

    • Could you give an example? 😉

      • /*
        gcc -g -o getifaddrs getifaddrs.c
        ./getifaddrs
        */
        #include
        #include
        #include
        #include // qsort
        #include // getifaddrs
        #include // offsetof
        #include // if_nametoindex
        #include // af_packet

        #include

        #define ADDROFFSET(x) \
        (x) ? \
        ((((struct sockaddr *)(x))->sa_family == AF_INET) ? \
        ((void *)(x) + offsetof(struct sockaddr_in, sin_addr)) : \
        (((struct sockaddr *)(x))->sa_family == AF_INET6) ? \
        ((void *)(x) + offsetof(struct sockaddr_in6, sin6_addr)) : \
        NULL) : \
        NULL

        static int cmp_ifaddrs_by_ifa_name(const void *p1, const void *p2)
        {
        return strcmp((*(struct ifaddrs **)p1)->ifa_name, (*(struct ifaddrs **)p2)->ifa_name);
        }

        int main()
        {
        struct ifaddrs *iface, *head;
        if( getifaddrs(&head) >= 0 )
        {
        int count=0;
        for( iface=head; iface != NULL; iface=iface->ifa_next )
        count++;

        struct ifaddrs *ifaces[count];
        memset(ifaces, 0, count*sizeof(struct ifaces *));

        for( count=0,iface=head; iface != NULL; iface=iface->ifa_next )
        ifaces[count++] = iface;

        qsort(ifaces, count, sizeof(struct ifaddrs *), cmp_ifaddrs_by_ifa_name);

        int i=0;

        char *old_ifa_name = “”;
        for( iface=ifaces[0]; i ifa_addr == NULL || (iface->ifa_addr->sa_family != AF_INET && iface->ifa_addr->sa_family != AF_INET6 && iface->ifa_addr->sa_family != AF_PACKET ))
        continue;

        if(strcmp(old_ifa_name, iface->ifa_name) != 0 )
        {
        old_ifa_name = iface->ifa_name;
        printf(“%i %s\n”, i, iface->ifa_name);
        }

        printf(“\t%s (%i)\n”, (iface->ifa_addr->sa_family == AF_INET) ? “IPv4” : ((iface->ifa_addr->sa_family == AF_INET6) ? “IPv6” : “unknown AF”),
        iface->ifa_addr->sa_family);

        char ip_string[INET6_ADDRSTRLEN+1] = “”;
        void *offset = ADDROFFSET(iface->ifa_addr);
        if(offset)
        {
        inet_ntop(iface->ifa_addr->sa_family, offset, ip_string, INET6_ADDRSTRLEN);
        printf(“\t\tAddress: %s\n”, ip_string);
        }else
        if( iface->ifa_addr->sa_family == AF_PACKET )
        {
        struct sockaddr_ll *lladdr = (struct sockaddr_ll *)iface->ifa_addr;

        int len = lladdr->sll_halen;
        char *data = (char *)lladdr->sll_addr;
        char *ptr = ip_string;
        int j;
        for( j = 0; j ifa_netmask);
        if(!offset)
        goto end_this_addr;
        inet_ntop(iface->ifa_addr->sa_family, offset, ip_string, INET6_ADDRSTRLEN);
        printf(“\t\tNetmask: %s\n”, ip_string);

        if(iface->ifa_addr->sa_family == AF_INET6)
        printf(“\t\tScopeID: %i\n”, if_nametoindex(iface->ifa_name));

        if(iface->ifa_flags & IFF_BROADCAST)
        {
        offset = ADDROFFSET(iface->ifa_ifu.ifu_broadaddr);
        if(!offset)
        goto end_this_addr;
        inet_ntop(iface->ifa_addr->sa_family, offset, ip_string, INET6_ADDRSTRLEN);
        printf(“\t\tBroadcast: %s\n”, ip_string);

        }else
        if(iface->ifa_flags & IFF_POINTOPOINT)
        {
        offset = ADDROFFSET(iface->ifa_ifu.ifu_dstaddr);
        if(!offset)
        goto end_this_addr;
        inet_ntop(iface->ifa_addr->sa_family, offset, ip_string, INET6_ADDRSTRLEN);
        printf(“\t\tPoint-To-Point: %s\n”, ip_string);
        }

        end_this_addr:
        printf(“\n”);
        }
        freeifaddrs(head);
        }
        return 0;
        }


Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: