如何在不轮询内核的情况下监控 C 程序中的 NIC 状态(启动/关闭)?

Posted

技术标签:

【中文标题】如何在不轮询内核的情况下监控 C 程序中的 NIC 状态(启动/关闭)?【英文标题】:How can I monitor the NIC status(up/down) in a C program without polling the kernel? 【发布时间】:2011-11-05 18:07:17 【问题描述】:

现在我需要实时获取 NIC 的状态(启动或关闭)。这意味着当 NIC 在阻塞循环中启动或关闭时,我必须捕获内核中断。

我的第一个愚蠢的方法是检查 /sys/class/net/eth0/operstate 或使用 ioctl 在循环中每 100 毫秒获取一次 ifflag。但是 100 毫秒对于应用程序重新路由流量来说太长了,而且每 100 毫秒轮询一次内核也不是一个好主意。

我注意到 inotify 功能可以以块模式监视文件。但不幸的是,它无法监控 /sys/class/net/eth0/operstate 文件,因为 /sys 位于 RAM 中而不是磁盘中。

那么,除了写一个内核模块来用块模式捕获C程序中的网卡中断(上/下)之外,还有其他方法吗?

【问题讨论】:

Is there a notification mechanism for when getifaddrs() results change? 的可能重复项 相关:get notified with netlist and RTMGRP_LINK signal 【参考方案1】:

是的,打开一个 netlink 套接字并监听 RTMGRP_LINK(网络接口创建/删除/启动/关闭事件)多播组。

netlink 手册页here 有一个具体的例子来做到这一点。

【讨论】:

不客气!如果您认为我的答案解决了您的问题,习惯上将其标记为正确答案(点击问题左侧的 V) 我已经尝试将 RTMGET_LINK 作为消息类型并从内核获取信息,设备信息是在 ifinfomsg struct 中构造的。该过程类似于用户->内核和内核->用户。但我想要的是在用户空间运行一个循环,当网卡状态发生变化时,内核将自动与用户空间通信,而无需用户空间发送请求。你能用 RTMGRP_LINK 做一个简短的介绍吗? 好的!我找出!再次感谢“V”! 您能否分享您的解决方案..只是检测界面上少数事件的基本代码。【参考方案2】:

在网上做了一些研究/阅读后,我设法编写了一个工作代码来监控 NIC 状态。

#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;

【讨论】:

您可能错过了问题中要求“不进行轮询”的部分,这就是您的解决方案似乎要做的。 这与@victor 接受的解决方案相同,并且此代码在启动接口时通过套接字获取通知。【参考方案3】:

您是否尝试过使用selectpoll 函数监控/sys/class/net/eth0/operstate 文件?据我所知,sysfs 文件在轮询方面的行为应该与常规文件相同:每当发生更改时,您应该在文件句柄上收到通知,说明某些内容已更改,并且您应该能够做出相应的回应。

【讨论】:

我不知道selectpoll是否会在文件内容被修改时通知用户空间。 /sys/class/net/eth0/operstate 是来自内核的结果,用于指示 NIC 启动或关闭。 它是一个文件句柄:它应该可以正常工作。 bugzilla.redhat.com/show_bug.cgi?id=604887 是一个旧的 RHEL 错误,它显示了在 sysfs 文件中使用了 select:似乎有理由期望它能够工作。 并非所有 sysfs 文件都实现poll 支持。不幸的是,operstate 是其中之一。

以上是关于如何在不轮询内核的情况下监控 C 程序中的 NIC 状态(启动/关闭)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不轮询的情况下监视页面的更改?

有没有办法在不轮询 REST API 的情况下通知 Google AI Platform 训练作业的状态变化?

如何让 GraphQL 在不进行轮询的情况下从数据库中获取实时/新数据?

轮询或不轮询(在 Web 服务上下文中)

如何在不阻塞的情况下使用 mpd.idle() 从 GTK 轮询 MPD

linux下进程结束时如何得到通知?