From: Michael Stapelberg Date: Tue, 7 Aug 2012 10:41:38 +0000 (+0200) Subject: tcpconns/linux: Use netlink instead of parsing /proc/net/tcp{,6} X-Git-Tag: collectd-5.2.0~76^2~2 X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=d94a7f0dac9c46b94a4681940809ae13cd1aa5dd;p=collectd.git tcpconns/linux: Use netlink instead of parsing /proc/net/tcp{,6} --- diff --git a/src/tcpconns.c b/src/tcpconns.c index 3c8fc728..0bcdf89e 100644 --- a/src/tcpconns.c +++ b/src/tcpconns.c @@ -70,6 +70,13 @@ #endif #if KERNEL_LINUX +# include +/* sys/socket.h is necessary to compile when using netlink on older systems. */ +# include +# include +# include +# include +# include /* #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); }