Adventures in Netlink
By andy.grover on Dec 03, 2008
Ever spent hours writing code and then found out it's not needed? I just had the pleasure. I wrote some code using netlink (via libnl) that takes an interface name ("eth0") and returns the ipv4 addresses associated with it, but the bug I thought I was fixing was not there, so I'm making a blog entry out of it :)
Libnl has documentation. Autogenerated documentation -- which is better than nothing, but it's really an API reference, not a tutorial. Of course these days everything is in a git repo so I was able to muddle through by looking at the source, but of course this shouldn't be needed, in an ideal world...
First, include some headers. This assumes you have libnl-devel RPM installed, and don't forget to link against libnl with "-lnl":
Here's the main function:
static uint32_t parse_iface(char *ptr)
struct nl_handle *sock;
struct nl_cache *link_cache;
struct nl_cache *addr_cache;
uint32_t result = 0;
struct rtnl_addr *addr;
sock = nl_handle_alloc();
link_cache = rtnl_link_alloc_cache(sock);
addr_cache = rtnl_addr_alloc_cache(sock);
ifindex = rtnl_link_name2i(link_cache, ptr);
addr = rtnl_addr_alloc();
nl_cache_foreach_filter(addr_cache, (struct nl_object *)addr, nl_cache_callback, &result);
This code is connecting to the netlink socket to receive route-related information. The
rtnl_*_alloc_cache() calls basically fill those caches with all the information from the kernel related to links (aka net interfaces) and addresses (all of them, whether ipv4, ipv6, appletalk, or whatever). So now all the info we need is in our address space, and we just need to sift through it!
Luckily, we can cross-reference the two separate caches because ifindex refers to the same interface in either cache. The code then gets the ifindex for the passed-in name (e.g. if ptr was "eth0").
The next part was kind of weird, due to being so flexible that simple things like what I wanted to do become nonobvious. The way you filter a cache is by creating an instance of the thing in the cache, and then only filling in the fields that you want to match the results. That's what the next few lines do, alloc an addr and fill in the limiters I wanted: ifindex (so I just get addresses for the one interface) and the family (limit to ipv4 addresses only).
libnl is designed to handle multiple returns for everything. This isn't dumb -- an interface can still have more than 1 ipv4 addresses that will get returned, so it has a callback interface. You give it a function and it calls it once for each result. I named my function
nl_cache_callback, let's take a look at it:
static void nl_cache_callback(struct nl_object *obj, void *arg)
struct nl_addr *addr;
uint32_t *ipv4_addr = arg;
/* only get addr in 1st callback */
addr = rtnl_addr_get_local((struct rtnl_addr *)obj);
*ipv4_addr = *(uint32_t *)nl_addr_get_binary_addr(addr);
Like I said, this could be called more than once but I really just want the first one, so I am passing an initially-zero variable in the context argument, and if it's already set I bail out. The last wrinkle is that a
struct rtnl_addr can hold a variety of different address types, so if I didn't already know I'd asked for only 32-bit ipv4 addresses, I'd have to use the accessor methods to find out the address's length and type. But since I do, I just copy the addr's 4 bytes into my 4 bytes, and that's it. Back in the first function, it's a quick
ntohl(ipv4_addr) and Bob's your uncle!
 libnl also has a helper function that fills in a sockaddr directly, pretty nice
 I actually have an uncle named Bob! Your results may vary.