tcpconns/linux: Use netlink instead of parsing /proc/net/tcp{,6}
authorMichael Stapelberg <michael@stapelberg.de>
Tue, 7 Aug 2012 10:41:38 +0000 (12:41 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Tue, 7 Aug 2012 10:41:38 +0000 (12:41 +0200)
src/tcpconns.c

index 3c8fc72..0bcdf89 100644 (file)
 #endif
 
 #if KERNEL_LINUX
+# include <asm/types.h>
+/* sys/socket.h is necessary to compile when using netlink on older systems. */
+# include <sys/socket.h>
+# include <linux/netlink.h>
+# include <linux/inet_diag.h>
+# include <sys/socket.h>
+# include <arpa/inet.h>
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_SYSCTLBYNAME
@@ -419,6 +426,116 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t
 } /* int conn_handle_ports */
 
 #if KERNEL_LINUX
+static int conn_read_netlink (void)
+{
+  int fd;
+  struct sockaddr_nl nladdr;
+  struct {
+    struct nlmsghdr nlh;
+    struct inet_diag_req r;
+  } req;
+  struct msghdr msg;
+  struct iovec iov;
+  struct inet_diag_msg *r;
+  char buf[8192];
+  static uint32_t sequence_number = 0;
+
+  if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG)) < 0)
+    return (0);
+
+  memset(&nladdr, 0, sizeof(nladdr));
+  nladdr.nl_family = AF_NETLINK;
+
+  req.nlh.nlmsg_len = sizeof(req);
+  req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
+  /* NLM_F_ROOT: return the complete table instead of a single entry. */
+  req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
+  req.nlh.nlmsg_pid = 0;
+  /* The sequence_number is used to track our messages. Since netlink is not
+   * reliable, we don't want to end up with a corrupt or incomplete old
+   * message in case the system is/was out of memory. */
+  req.nlh.nlmsg_seq = ++sequence_number;
+  memset(&req.r, 0, sizeof(req.r));
+  req.r.idiag_family = AF_INET;
+  req.r.idiag_states = 0xfff;
+  req.r.idiag_ext = 0;
+
+  iov.iov_base = &req;
+  iov.iov_len = sizeof(req);
+
+  msg = (struct msghdr) {
+    .msg_name = (void*)&nladdr,
+    .msg_namelen = sizeof(nladdr),
+    .msg_iov = &iov,
+    .msg_iovlen = 1,
+  };
+
+  if (sendmsg (fd, &msg, 0) < 0)
+  {
+    close (fd);
+    return (0);
+  }
+
+  iov.iov_base = buf;
+  iov.iov_len = sizeof(buf);
+
+  while (1)
+  {
+    int status;
+    struct nlmsghdr *h;
+
+    msg = (struct msghdr) {
+      (void*)&nladdr, sizeof(nladdr),
+      &iov, 1,
+      NULL, 0,
+      0
+    };
+
+    status = recvmsg(fd, &msg, 0);
+    if (status < 0)
+    {
+      if (errno == EINTR)
+       continue;
+      close (fd);
+      return (0);
+    }
+
+    if (status == 0)
+    {
+      close (fd);
+      return (1);
+    }
+
+    h = (struct nlmsghdr*)buf;
+    while (NLMSG_OK(h, status))
+    {
+      if (h->nlmsg_seq == sequence_number)
+      {
+        if (h->nlmsg_type == NLMSG_DONE)
+        {
+          close (fd);
+          return (1);
+        }
+        else if (h->nlmsg_type == NLMSG_ERROR)
+        {
+          close (fd);
+          return (0);
+        }
+
+        r = NLMSG_DATA(h);
+
+        /* This code does not (need to) distinguish between IPv4 and IPv6. */
+        conn_handle_ports (ntohs(r->id.idiag_sport),
+            ntohs(r->id.idiag_dport),
+            r->idiag_state);
+      }
+      h = NLMSG_NEXT(h, status);
+    }
+  }
+
+  return (1);
+} /* int conn_read_netlink */
+
 static int conn_handle_line (char *buffer)
 {
   char *fields[32];
@@ -557,10 +674,15 @@ static int conn_read (void)
 
   conn_reset_port_entry ();
 
-  if (conn_read_file ("/proc/net/tcp") != 0)
-    errors_num++;
-  if (conn_read_file ("/proc/net/tcp6") != 0)
-    errors_num++;
+  /* Try to use netlink for getting this data, it is _much_ faster on systems
+   * with a large amount of connections. */
+  if (!conn_read_netlink ())
+  {
+    if (conn_read_file ("/proc/net/tcp") != 0)
+      errors_num++;
+    if (conn_read_file ("/proc/net/tcp6") != 0)
+      errors_num++;
+  }
 
   if (errors_num < 2)
   {
@@ -569,7 +691,7 @@ static int conn_read (void)
   else
   {
     ERROR ("tcpconns plugin: Neither /proc/net/tcp nor /proc/net/tcp6 "
-       "coult be read.");
+       "could be read.");
     return (-1);
   }