In my networking application I need to get the MAC address of the default gateway. One way to do this is parsing the relevant files in /proc/net, but this is not an elegant solution.

Instead, I use the network configuration interface to the linux kernel: rtnetlink. To get familiar with the API before integrating the feature in my application, I wrote a quick sample application:

rtnl.c download
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/ether.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUFSIZE 8192

struct gw_info {
    uint32_t ip;
    char mac[ETH_ALEN];
};

int send_req(int sock, char *buf, size_t nlseq, size_t req_type)
{
    struct nlmsghdr *nlmsg;

    memset(buf, 0, BUFSIZE);
    nlmsg = reinterpret_cast<struct nlmsghdr *>(buf);

    nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
    nlmsg->nlmsg_type = req_type;
    nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
    nlmsg->nlmsg_seq = nlseq++;
    nlmsg->nlmsg_pid = getpid();

    if (send(sock, buf, nlmsg->nlmsg_len, 0) < 0)
        return -1;

    return nlseq;
}

int read_res(int sock, char *buf, size_t nlseq)
{
    struct nlmsghdr *nlmsg;
    int len;
    size_t total_len = 0;

    do {
        len = recv(sock, buf, BUFSIZE - total_len, 0);

        if (len < 0)
            return -1;

        nlmsg = reinterpret_cast<struct nlmsghdr *>(buf);

        if (NLMSG_OK(nlmsg, len) == 0)
            return -1;

        if (nlmsg->nlmsg_type == NLMSG_ERROR)
            return -1;

        if (nlmsg->nlmsg_type == NLMSG_DONE)
            break;

        buf += len;
        total_len += len;

        if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
            break;

    } while (nlmsg->nlmsg_seq != nlseq || nlmsg->nlmsg_pid != getpid());

    return total_len;
}

int print_gw(struct gw_info *gw)
{
    char buf[INET_ADDRSTRLEN];

    if (inet_ntop(AF_INET, &gw->ip, buf, INET_ADDRSTRLEN) == NULL)
        return -1;

    printf("gateway ip:  %s\n", buf);
    printf("gateway mac: ");
    for (size_t i = 0; i < ETH_ALEN - 1; ++i)
        printf("%02hhx:", gw->mac[i]);
    printf("%02hhx\n", gw->mac[ETH_ALEN - 1]);

    return 0;
}

void parse_route(struct nlmsghdr *nlmsg, void *gw)
{
    struct rtmsg *rtmsg;
    struct rtattr *attr;
    uint32_t gw_tmp;
    size_t len;
    struct gw_info *info;

    info = reinterpret_cast<struct gw_info *>(gw);
    rtmsg = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(nlmsg));

    if (rtmsg->rtm_family != AF_INET || rtmsg->rtm_table != RT_TABLE_MAIN)
        return;

    attr = reinterpret_cast<struct rtattr *>(RTM_RTA(rtmsg));
    len = RTM_PAYLOAD(nlmsg);

    for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
        if (attr->rta_type != RTA_GATEWAY)
            continue;

        info->ip = *reinterpret_cast<uint32_t *>(RTA_DATA(attr));
        break;
    }
}

void parse_neigh(struct nlmsghdr *nlmsg, void *gw)
{
    struct ndmsg *ndmsg;
    struct rtattr *attr;
    size_t len;
    char mac[ETH_ALEN];
    uint32_t ip = 0;
    struct gw_info *info;

    info = reinterpret_cast<struct gw_info *>(gw);
    ndmsg = reinterpret_cast<struct ndmsg *>(NLMSG_DATA(nlmsg));

    if (ndmsg->ndm_family != AF_INET)
        return;

    attr = reinterpret_cast<struct rtattr *>(RTM_RTA(ndmsg));
    len = RTM_PAYLOAD(nlmsg);

    for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
        if (attr->rta_type == NDA_LLADDR)
            memcpy(mac, RTA_DATA(attr), ETH_ALEN);

        if (attr->rta_type == NDA_DST)
            ip = *reinterpret_cast<uint32_t *>(RTA_DATA(attr));
    }

    if (ip && ip == info->ip)
        memcpy(info->mac, mac, ETH_ALEN);
}

void parse_response(char *buf, size_t len, void (cb)(struct nlmsghdr *, void *),
                    void *arg)
{
    struct nlmsghdr *nlmsg;

    nlmsg = reinterpret_cast<struct nlmsghdr *>(buf);

    for (; NLMSG_OK(nlmsg, len); nlmsg = NLMSG_NEXT(nlmsg, len))
        cb(nlmsg, arg);
}

int main(int argc, char **argv)
{
    int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
    char buf[BUFSIZE];
    size_t nlseq = 0;
    size_t msg_len;
    struct gw_info gw;

    if (sock <= 0)
        return -1;

    nlseq = send_req(sock, buf, nlseq, RTM_GETROUTE);
    msg_len = read_res(sock, buf, nlseq);

    if (msg_len <= 0)
        return -1;

    parse_response(buf, msg_len, &parse_route, &gw);

    nlseq = send_req(sock, buf, nlseq, RTM_GETNEIGH);
    msg_len = read_res(sock, buf, nlseq);

    if (msg_len <= 0)
        return -1;

    parse_response(buf, msg_len, &parse_neigh, &gw);
    print_gw(&gw);

    return 0;

}

The code is inspired from this blog entry, which led me on the track.