NETLINKでネットワークインタフェースの状態変化を検知する
LinuxでNETLINKを使ったネットワークインタフェースの状態変化(up/down, IPアドレスの追加/削除)を検知してみる。
NETLINKはネットワークインタフェースの状態変化をカーネルからユーザランドのアプリケーションに対してソケットインタフェース経由で通知してくれる仕組み。
詳細についてはMan page of NETLINKを参照。
[rtoc_mokuji title=”” title_display=”” heading=”h3″ list_h2_type=”” list_h3_type=”” display=”” frame_design=”” animation=””]
目次
ソースコード
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のソースを見ればもう少し理解が深まりそう。
コメント