コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

NETLINKでネットワークインタフェースの状態変化を検知する

LinuxでNETLINKを使ったネットワークインタフェースの状態変化(up/down, IPアドレスの追加/削除)を検知してみる。

NETLINKはネットワークインタフェースの状態変化をカーネルからユーザランドのアプリケーションに対してソケットインタフェース経由で通知してくれる仕組み。
詳細についてはMan page of NETLINKを参照。


ソースコード

netlink.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <errno.h>

#define EPOLL_SIZE (1)
#define CTRLNL_OK  (0)
#define CTRLNL_NG  (-1)

struct rtnl_handle
{
    int         fd;
    struct sockaddr_nl  local;
    struct sockaddr_nl  peer;
    __u32           seq;
    __u32           dump;
};

static int ctrlnl_open(struct rtnl_handle* rth, unsigned subscriptions, int protocol)
{
    int ret = CTRLNL_NG;
    int rc = 0;
    int addr_len = 0;

    memset(rth, 0, sizeof(rth));

    do {
        rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
        if (rth->fd < 0) {
            perror("Cannot open netlink socket");
            break;
        }

        memset(&rth->local, 0, sizeof(rth->local));
        rth->local.nl_family = AF_NETLINK;
        rth->local.nl_groups = subscriptions;

        if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) {
            perror("Cannot bind netlink socket");
            close(rth->fd);
            break;
        }
        
        addr_len = sizeof(rth->local);
        if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) {
            perror("Cannot getsockname");
            close(rth->fd);
            break;
        }
        
        if (addr_len != sizeof(rth->local)) {
            fprintf(stderr, "Wrong address length %d\n", addr_len);
            close(rth->fd);
            break;
        }
        
        if (rth->local.nl_family != AF_NETLINK) {
            fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family);
            close(rth->fd);
            break;
        }

        rth->seq = time(NULL);

        ret = CTRLNL_OK;

    } while(0);

    return ret;
}

static void ctrlnl_getifname(int ifindex, char* ifname, int length)
{
    int fd = -1;
    struct ifreq ifr = {
        .ifr_ifindex = ifindex,
    };

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
    } else {

        if (ioctl(fd, SIOCGIFNAME, &ifr) < 0) {
            perror("ioctl");
        } else {
            snprintf(ifname, length-1, "%s", ifr.ifr_name);
        }

        close(fd);
    }
}

static void ctrlnl_getrtaddr(struct rtattr** rtas, struct ifaddrmsg* ifa, int length)
{
    struct rtattr* rta = IFA_RTA(ifa);

    while(RTA_OK(rta, length)) {
        if (rta->rta_type <= IFA_MAX) {
            rtas[rta->rta_type] = rta;
        }

        rta = RTA_NEXT(rta, length);
    }
}

static int ctrlnl_showinfo(struct nlmsghdr* h)
{
    int ret = CTRLNL_OK;

    char  ifname[64] = {0};
    struct ifaddrmsg* ifa = NLMSG_DATA(h);
    struct rtattr*  rtas[IFA_MAX+1];

    printf("######### Recv Netlink Message ##########\n");

    ctrlnl_getifname(ifa->ifa_index, ifname, sizeof(ifname));
    printf("ifname = %s\n", ifname);
    printf("nlmsg_type = %#x\n", h->nlmsg_type);

    switch(h->nlmsg_type) {
    case RTM_NEWLINK:
    case RTM_DELLINK:
        break;
    case RTM_NEWADDR:
    case RTM_DELADDR:
        {
            ctrlnl_getrtaddr(rtas, ifa, (h->nlmsg_len - NLMSG_SPACE(sizeof(*ifa))));

            printf("ifa_family = %#x\n", ifa->ifa_family);

            char addr[128] = {0};
            if (rtas[IFA_LOCAL]) {
                inet_ntop(ifa->ifa_family, RTA_DATA(rtas[IFA_LOCAL]), addr, sizeof(addr));
            }

            if (rtas[IFA_ADDRESS]) {
                inet_ntop(ifa->ifa_family, RTA_DATA(rtas[IFA_ADDRESS]), addr, sizeof(addr));
            }

            printf("address = %s\n", addr);
        }
        break;
    default:
        fprintf(stderr, "illegal message type.\n");
        ret = CTRLNL_NG;
        break;
    }

    printf("\n");

    return ret;
}

static int ctrlnl_recv(int fd)
{
    int ret = CTRLNL_NG;
    struct nlmsghdr* h;
    char buf[4096] = {0};
    ssize_t rs = 0;

    rs = recv(fd, buf, sizeof(buf), 0);
    if (rs < 0) {
        perror("recv");
    } else {
        for (h = (struct nlmsghdr*)buf; NLMSG_OK(h, rs); h = NLMSG_NEXT(h, rs)) {
            ret = ctrlnl_showinfo(h);
            if (CTRLNL_OK != ret) {
                fprintf(stderr, "ctrlnl_showinfo(%d) failed.\n", ret);
                break;
            }
        }
    }

    return ret;
}

static int ctrlnl_close(int fd)
{
    int ret = CTRLNL_OK;
    int rc = 0;

    rc = close(fd);
    if (rc < 0) {
        perror("close");
        ret = CTRLNL_NG;
    }

    return ret;
}

static int ctrlnl_poll(int fd)
{
    int rc = 0;
    int ret = CTRLNL_NG;
    struct sockaddr_in addr;
    int epfd = -1;
    int nfds = -1;
    struct epoll_event events[EPOLL_SIZE];
    struct epoll_event evt = {
        .events = EPOLLIN,
        .data.fd = fd,
    };

    epfd = epoll_create(EPOLL_SIZE);
    if (epfd < 0) {
        perror("epoll_create");
        goto error_end;
    }

    rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt);
    if (rc < 0) {
        perror("epoll_ctl");
        close(epfd);
        goto error_end;
    }

    while(1) {

        nfds = epoll_wait(epfd, events, EPOLL_SIZE, -1);
        if (nfds < -1) {
            perror("epoll_wait");
            close(epfd);
            goto error_end;
        }

        int i;
        for (i = 0; i < nfds; i++) {

            if (events[i].data.fd == fd) {

                ret = ctrlnl_recv(fd);
                if (CTRLNL_OK != ret) {
                    fprintf(stderr, "ctrlnl_recv(%d) failed.\n", ret);
                }
            }
        }
    }

 error_end:

    return ret;
}

int main(void)
{
    int ret = -1;
    struct rtnl_handle  rth;

    do {
        ret = ctrlnl_open(&rth,
                          RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR,
                          NETLINK_ROUTE);
        if (CTRLNL_OK != ret) {
            fprintf(stderr, "ctrlnl_open(%d) failed.\n", ret);
            break;
        }

        ret = ctrlnl_poll(rth.fd);
        if (CTRLNL_OK != ret) {
            fprintf(stderr, "ctrlnl_poll(%d) failed.\n", ret);
        }

        ret = ctrlnl_close(rth.fd);
        if (CTRLNL_OK != ret) {
            fprintf(stderr, "ctrlnl_close(%d) failed.\n", ret);
        }

    } while(0);

    return 0;
}

実行結果

ビルドしたバイナリを実行後にdown/up, IPv4付与をやってみる

user@user-VirtualBox ~ $ sudo ifconfig enp0s9
enp0s9    Link encap:イーサネット  ハードウェアアドレス 08:00:27:7c:40:f4
          UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
          RXパケット:0 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:3635 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:1000
          RXバイト:0 (0.0 B)  TXバイト:601190 (601.1 KB)

user@user-VirtualBox ~ $ 
user@user-VirtualBox ~ $ sudo ifconfig enp0s9 down
user@user-VirtualBox ~ $ sudo ifconfig enp0s9 up
user@user-VirtualBox ~ $ sudo ifconfig enp0s9 172.16.1.1


表示結果

user@user-VirtualBox ~ $ gcc -o netlink netlink.c
user@user-VirtualBox ~ $ ./netlink
######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x10

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x15
ifa_family = 0xa
address = fe80::5e20:28f4:aaab:aade

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x10

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x10

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x10

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x10

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x14
ifa_family = 0xa
address = fe80::5e20:28f4:aaab:aade

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x14
ifa_family = 0xa
address = fe80::5e20:28f4:aaab:aade

######### Recv Netlink Message ##########
ifname = enp0s9
nlmsg_type = 0x14
ifa_family = 0x2
address = 172.16.1.1

適当に参考になりそうなソースコードを集めて作ってみたが何とか動いた。
コマンド操作に対して通知回数が多い気がするのはIPv6のRAが動いているからか?(よく分かってない)

ifconfig,ipコマンドとかNet-SNMPのソースを見ればもう少し理解が深まりそう。