summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 5e58643)
raw | patch | inline | side by side (parent: 5e58643)
author | Michael Stapelberg <michael@stapelberg.de> | |
Tue, 7 Aug 2012 10:41:38 +0000 (12:41 +0200) | ||
committer | Michael Stapelberg <michael@stapelberg.de> | |
Tue, 7 Aug 2012 10:41:38 +0000 (12:41 +0200) |
src/tcpconns.c | patch | blob | history |
diff --git a/src/tcpconns.c b/src/tcpconns.c
index 3c8fc7285e2621c57fa5f3779d08df70e8ed7179..0bcdf89ef8f7cc7e535f428cd855703733007684 100644 (file)
--- a/src/tcpconns.c
+++ b/src/tcpconns.c
#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];
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)
{
else
{
ERROR ("tcpconns plugin: Neither /proc/net/tcp nor /proc/net/tcp6 "
- "coult be read.");
+ "could be read.");
return (-1);
}