The Linux kernel netlink interface can be used to learn about and manage network interfaces. Netlink covers a lot of territory and is quite sophisticated, check out the userspace documentation for libnl for a good description.

I recently wrote an application that, among other things, needs to know the names of 802.11 interfaces on the system (ex: wlan0) so that it can then do things with those interfaces. I was curious how iw does this and dug around, so here are some quick notes.

API

I built an application that links against the generic netlink library libnl-genl as well as libnl itself. This example will connect via netlink, send a command asking to dump all 802.11 network interfaces, and then exit.

The netlink API needed is:

#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/ctrl.h>

The ABI contract, nl80211.h, can be taken from the iw source tree. It provides the 802.11-specific netlink commands and attributes that we use when talking to the kernel:

#include "nl80211.h"

And finally we need standard IO and errno.h for error codes:

#include <stdio.h>
#include <errno.h>

Initialization

Netlink provides a socket wrapper of type struct nl_sock and methods to allocate and free that. There is also a unique ID used as a handle. We can keep some data structure around for these:

static struct {
    struct nl_sock *nls;
    int nl80211_id;
} wifi;

We need one of these sockets in order to talk to the kernel, the buffer sizes are taken from the iw implementation:

wifi.nls = nl_socket_alloc();
if (!wifi.nls) {
        fprintf(stderr, "Failed to allocate netlink socket.\n");
        return -ENOMEM;
}

nl_socket_set_buffer_size(wifi.nls, 8192, 8192);

We can then use the generic netlink to connect to the nl80211 control channel:

if (genl_connect(wifi.nls)) {
        fprintf(stderr, "Failed to connect to generic netlink.\n");
        nl_socket_free(wifi.nls);
        return -ENOLINK;
}

wifi.nl80211_id = genl_ctrl_resolve(wifi.nls, "nl80211");
if (wifi.nl80211_id < 0) {
        fprintf(stderr, "nl80211 not found.\n");
        nl_socket_free(wifi.nls);
        return -ENOENT;
}

We now have a connected socket and an ID for the nl80211 channel, so we can set things up to send commands and receive responses.

Messages and callbacks

We allocate a netlink message that we will later send:

struct nl_msg *msg = nlmsg_alloc();
if (!msg) {
        fprintf(stderr, "Failed to allocate netlink message.\n");
        return -ENOMEM;
}

We also allocate a callback for which we can register various handlers:

struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
if (!cb) {
        fprintf(stderr, "Failed to allocate netlink callback.\n");
    nlmsg_free(msg);
        return -ENOMEM;
}

This callback will actually list 802.11 interfaces if things work out:

nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, list_interface_handler, NULL);

This sets up the generic netlink message:

genlmsg_put(msg, 0, 0, wifi.nl80211_id, 0,
                NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0);

See nl80211.h (and its comments) for a list of commands (NL80211_CMD_) and flags. We use the NLM_F_DUMP flag because we want a dump of all interfaces (and our callback may be called multiple times), iw will do this for certain commands or for example when printing information about a specified interface or phy, will instead not set this flag but specify the interface or phy index.

This kicks off the message:

nl_send_auto_complete(wifi.nls, msg);

And finally, borrowing from iw, this lets you know that the dump is finished:

int err = 1;

nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);

while (err > 0)
    nl_recvmsgs(wifi.nls, cb);

We will have listed all of the interfaces with calls to list_interface_handler and then we will have been told that we are done by a call to finish_handler when the while loop exits by having err cleared.

The finish_handler looks like this:

static int finish_handler(struct nl_msg *msg, void *arg)
{
    int *ret = arg;
    *ret = 0;
    return NL_SKIP;
}

Here is a very simple list_interface_handler implementation, see interface.c in iw for the complete implementation and more ideas:

static int list_interface_handler(struct nl_msg *msg, void *arg)
{
    struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
    struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

    nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
            genlmsg_attrlen(gnlh, 0), NULL);

    if (tb_msg[NL80211_ATTR_IFNAME])
        printf("Interface: %s\n", nla_get_string(tb_msg[NL80211_ATTR_IFNAME]));

    return NL_SKIP;
}

Cleanup

We should free up the callback, message, and netlink socket:

nlmsg_free(msg);
nl_cb_put(cb);

nl_socket_free(wifi.nls);