Skip to content

Instantly share code, notes, and snippets.

@danny-source
Forked from userid/netlink1.c
Created August 4, 2019 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danny-source/314cf0a322b7904c12b9ce41596ccf44 to your computer and use it in GitHub Desktop.
Save danny-source/314cf0a322b7904c12b9ce41596ccf44 to your computer and use it in GitHub Desktop.
使用rtnetlink监听和判断网卡的状态变化
/**
http://guochongxin.github.io/c/c/c++/linux/netlink/rj45/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/%E7%BD%91%E5%8D%A1/2014/12/05/tong_guo_netlink_jian_ce_wang_xian_cha_ba
最近有个需求需要检测RJ45网卡的网线有没有接上,而最近正在了解Netlink相关资料,刚好也看下通过Netlink可以进行检测,故在此做下粗略笔记:
1.首先要创建一个Netlink Socket,在用户层使用如下参数来调用socket()函数:
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
上面这个函数第一个参数必须是AF_NETLINK或PF_NETLINK,这两个标志在Linux下是一样的,第二个参数可以是SOCK_RAW或SOCK_DGRAM(对应用到TCP或UDP协议),而最后一个参数NETLINK_ROUTE为“路由守护进程”,用于接收来自内核的路由通知事件。
2.将上面创建的Socket绑定
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTNLGRP_LINK; //指定接收路由多播组消息
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
上面将创建的socket与相应的协议族和组进行绑定,接下来通过读fd这个socket来获得相应的消息数据struct nlmsghdr,再对该结构体数据进行判断来获得网线是接上或是拔掉,相应的源码如下:
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <string.h>
#define BUFLEN 20480
int main(int argc, char *argv[])
{
int fd, retval;
char buf[BUFLEN] = {0};
int len = BUFLEN;
struct sockaddr_nl addr;
struct nlmsghdr *nh;
struct ifinfomsg *ifinfo;
struct rtattr *attr;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTNLGRP_LINK;
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
while ((retval = read(fd, buf, BUFLEN)) > 0)
{
for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, retval); nh = NLMSG_NEXT(nh, retval))
{
if (nh->nlmsg_type == NLMSG_DONE)
break;
else if (nh->nlmsg_type == NLMSG_ERROR)
return;
else if (nh->nlmsg_type != RTM_NEWLINK)
continue;
ifinfo = NLMSG_DATA(nh);
printf("%u: %s", ifinfo->ifi_index,
(ifinfo->ifi_flags & IFF_LOWER_UP) ? "up" : "down" );
attr = (struct rtattr*)(((char*)nh) + NLMSG_SPACE(sizeof(*ifinfo)));
len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
{
if (attr->rta_type == IFLA_IFNAME)
{
printf(" %s", (char*)RTA_DATA(attr));
break;
}
}
printf("\n");
}
}
return 0;
}
/**
https://stackoverflow.com/questions/7225888/how-can-i-monitor-the-nic-statusup-down-in-a-c-program-without-polling-the-ker
https://stackoverflow.com/questions/10340145/get-event-for-nic-ethernet-card-link-status-on-linux
After doing a little research/reading on the web, I managed to cook up a working code to monitor NIC status.
*/
#include <asm/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
int
read_event (int sockint)
{
int status;
int ret = 0;
char buf[4096];
struct iovec iov = { buf, sizeof buf };
struct sockaddr_nl snl;
struct msghdr msg = { (void *) &snl, sizeof snl, &iov, 1, NULL, 0, 0 };
struct nlmsghdr *h;
struct ifinfomsg *ifi;
status = recvmsg (sockint, &msg, 0);
if (status < 0)
{
/* Socket non-blocking so bail out once we have read everything */
if (errno == EWOULDBLOCK || errno == EAGAIN)
return ret;
/* Anything else is an error */
printf ("read_netlink: Error recvmsg: %d\n", status);
perror ("read_netlink: Error: ");
return status;
}
if (status == 0)
{
printf ("read_netlink: EOF\n");
}
// We need to handle more than one message per 'recvmsg'
for (h = (struct nlmsghdr *) buf; NLMSG_OK (h, (unsigned int) status);
h = NLMSG_NEXT (h, status))
{
//Finish reading
if (h->nlmsg_type == NLMSG_DONE)
return ret;
// Message is some kind of error
if (h->nlmsg_type == NLMSG_ERROR)
{
printf ("read_netlink: Message is an error - decode TBD\n");
return -1; // Error
}
if (h->nlmsg_type == RTM_NEWLINK)
{
ifi = NLMSG_DATA (h);
printf ("NETLINK::%s\n", (ifi->ifi_flags & IFF_RUNNING) ? "Up" : "Down");
}
}
return ret;
}
int
main (int argc, char *argv[])
{
fd_set rfds, wfds;
struct timeval tv;
int retval;
struct sockaddr_nl addr;
int nl_socket = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (nl_socket < 0)
{
printf ("Socket Open Error!");
exit (1);
}
memset ((void *) &addr, 0, sizeof (addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid ();
addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
// addr.nl_groups = RTMGRP_LINK;
if (bind (nl_socket, (struct sockaddr *) &addr, sizeof (addr)) < 0)
{
printf ("Socket bind failed!");
exit (1);
}
while (1)
{
FD_ZERO (&rfds);
FD_CLR (nl_socket, &rfds);
FD_SET (nl_socket, &rfds);
tv.tv_sec = 10;
tv.tv_usec = 0;
retval = select (FD_SETSIZE, &rfds, NULL, NULL, &tv);
if (retval == -1)
printf ("Error select() \n");
else if (retval)
{
printf ("Event recieved >> ");
read_event (nl_socket);
}
else
printf ("## Select TimedOut ## \n");
}
return 0;
}
/**
https://blog.csdn.net/sourthstar/article/details/7975999
http://www.cpplive.com/html/1542.html
之前有一篇文章《Netlink实现Linux内核与用户空间通信》专门介绍了Netlink相比其他内核交互方式的优点以及Netlink的调用方法,并以NETLINK_KOBJECT_UEVENT(内核事件向用户态通知)为例演示了U盘热插拔信息的捕捉,衍生出另一篇文章《Linux下自动检测USB热插拔》,今天尝试用Netlink来捕捉一下网络接口信息,实现的主要功能是实时打印发生变化的网络接口的序列号、上下线状态和接口名称。
为了创建一个 netlink socket,用户需要使用如下参数调用 socket():
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM,第三个参数指定netlink协议类型,NETLINK_ROUTE意为“路由守护进程”,绑定该协议创建的fd可以接收到来自内核的路由通知事件(如网路接口eth0上线)。
函数 bind() 用于把一个打开的 netlink socket 与 netlink 源 socket 地址绑定在一起。netlink socket 的地址初始化及绑定如下:
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTNLGRP_LINK; //指定接收路由多播组消息
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <string.h>
#define BUFLEN 20480
int main(int argc, char *argv[])
{
int fd, retval;
char buf[BUFLEN] = {0};
int len = BUFLEN;
struct sockaddr_nl addr;
struct nlmsghdr *nh;
struct ifinfomsg *ifinfo;
struct rtattr *attr;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTNLGRP_LINK;
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
while ((retval = read(fd, buf, BUFLEN)) > 0)
{
for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, retval); nh = NLMSG_NEXT(nh, retval))
{
if (nh->nlmsg_type == NLMSG_DONE)
break;
else if (nh->nlmsg_type == NLMSG_ERROR)
return;
else if (nh->nlmsg_type != RTM_NEWLINK)
continue;
ifinfo = NLMSG_DATA(nh);
printf("%u: %s", ifinfo->ifi_index,
(ifinfo->ifi_flags & IFF_LOWER_UP) ? "up" : "down" );
attr = (struct rtattr*)(((char*)nh) + NLMSG_SPACE(sizeof(*ifinfo)));
len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
{
if (attr->rta_type == IFLA_IFNAME)
{
printf(" %s", (char*)RTA_DATA(attr));
break;
}
}
printf("\n");
}
}
return 0;
}
/**
https://stackoverflow.com/questions/40788161/how-to-receive-kernel-uevents-with-netlink-socket
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define NL_MAX_PAYLOAD 8192
int main () {
int nl_socket;
struct sockaddr_nl src_addr;
char msg[NL_MAX_PAYLOAD];
int ret;
// Prepare source address
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups = -1; // ==> add this so can receive from kernel broadcast
nl_socket = socket(AF_NETLINK, (SOCK_DGRAM | SOCK_CLOEXEC), NETLINK_KOBJECT_UEVENT);
if (nl_socket < 0) {
printf("Failed to create socket for DeviceFinder");
exit(1);
}
ret = bind(nl_socket, (struct sockaddr*) &src_addr, sizeof(src_addr));
if (ret) {
printf("Failed to bind netlink socket..");
close(nl_socket);
return 1;
}
printf("Waiting for events now...\n");
while (1) {
int r = recv(nl_socket, msg, sizeof(msg), MSG_DONTWAIT);
if (r == -1)
continue;
if (r < 0) {
perror("");
continue;
}
printf("length:%i\n msg:%s", r, msg);
}
}
/**
https://stackoverflow.com/questions/42264101/does-libevent-support-netlink-socket
使用libevent
Yes, libevent supports netlink socket.
There is https://github.com/libevent/libevent/blob/master/sample/hello-world.c, it is modified below to listen to netlink socket.
The basic example listens to Linux network interface creation/deletion and can be executed with sudo to gain privilege needed.
It listens to same events as ip monitor link.
Another example of listening to RAW sockets with libevent is here
https://github.com/bodgit/libevent-natpmp/blob/master/natpmp.c.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/route.h>
#include <fcntl.h>
#include <unistd.h>
#include <event.h>
#include <string.h>
#include <errno.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#define BUF_SIZE 10240
static void link_recvmsg(int fd, short event, void *arg)
{
char buf[NLMSG_SPACE(BUF_SIZE)] = {0};
socklen_t socklen;
struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf)};
struct sockaddr addr;
memset(&addr, 0, sizeof(struct sockaddr));
if (!fd || -1 == fd)
return;
int status = getsockname(fd, &addr, &socklen);
if(-1 == status)
return;
struct msghdr mh = {.msg_name = NULL, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1,
.msg_flags = 0, .msg_name = &addr, .msg_namelen = sizeof(struct sockaddr)};
status = recvmsg(fd, &mh, 0);
if ((-1 == status) && ((EINTR == errno) || (EAGAIN == errno)))
return;
if(-1 == status)
return;
if ((mh.msg_flags & MSG_TRUNC) == MSG_TRUNC)
return;
if ((mh.msg_flags & MSG_CTRUNC) == MSG_CTRUNC)
return;
for (const struct nlmsghdr *h = (struct nlmsghdr *)buf; NLMSG_OK(h, status); h = NLMSG_NEXT(h, status)) {
switch (h->nlmsg_type) {
case RTM_NEWLINK:
fprintf(stderr, "got RTM_NEWLINK\n");
break;
case RTM_DELLINK:
fprintf(stderr, "got RTM_DELLINK\n");
break;
default:
fprintf(stderr, "unexpected case in swtch statement\n");
break;
}
}
}
int main(int argc, char **argv)
{
/* some init code here */
/* NETLINK socket */
int status;
int buf_size = BUF_SIZE;
struct sockaddr_nl src_addr;
struct event_base *base;
base = event_base_new();
int socket_nl = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE);
if(-1 == socket_nl) return -1;
memset(&src_addr, 0, sizeof(struct sockaddr_nl));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups |= RTNLGRP_LINK;
status = setsockopt(socket_nl, SOL_SOCKET, SO_RCVBUF,
&buf_size, sizeof(buf_size));
if(-1 == status) return -1;
status = bind(socket_nl, (struct sockaddr *)&src_addr, sizeof(struct sockaddr_nl));
if(status < 0) return -1;
static struct event nl_ev;
event_set(&nl_ev, socket_nl, EV_READ|EV_PERSIST, link_recvmsg,
NULL);
if (base) {
event_base_set(base, &nl_ev);
}
event_add(&nl_ev, NULL);
/* some other code, dispatch event and deinit */
event_base_dispatch(base);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment