File openslp.use-TCPDIAG-for-checking-listeners of Package openslp

From: Jeff Mahoney <jeffm@suse.com>
Subject: openslp: Use TCPDIAG for checking listeners
References: bnc#601002

 The use of /proc/net/tcp is deprecated and can cause performance issues on
 large systems. The issue is that there are a great many locks that must
 be claimed and released in order to produce the contents of the proc file.

 The replacement mechanism is to use the INETDIAG/TCPDIAG interface to
 get the results. This has the advantage of using in-kernel filtering as
 well as a binary interface so that the parsing of the proc file is
 unnecessary.

 Support is limited to TCP so the use of /proc/net/udp is still required.

 If for whatever reason the netlink connection is lost and can't be
 re-established, we fall back to reading /proc/net/tcp until the daemon
 is restarted.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
---
 slpd/slpd_database.c |  179 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 176 insertions(+), 3 deletions(-)

--- a/slpd/slpd_database.c
+++ b/slpd/slpd_database.c
@@ -76,6 +76,9 @@ FILE *regfileFP;
 /* standard header files                                                   */
 /*=========================================================================*/
 #include <dirent.h>
+#include <linux/netlink.h>
+#include <linux/inet_diag.h>
+#include <sched.h>
 
 /*=========================================================================*/
 SLPDDatabase G_SlpdDatabase;
@@ -919,11 +922,176 @@ static void SLPDDatabaseWatcher_fd(int f
     }
 }
 
+enum {
+        SS_UNKNOWN,
+        SS_ESTABLISHED,
+        SS_SYN_SENT,
+        SS_SYN_RECV,
+        SS_FIN_WAIT1,
+        SS_FIN_WAIT2,
+        SS_TIME_WAIT,
+        SS_CLOSE,
+        SS_CLOSE_WAIT,
+        SS_LAST_ACK,
+        SS_LISTEN,
+        SS_CLOSING,
+        SS_MAX
+};
+
+#define SS_ALL ((1<<SS_MAX)-1)
+
+static int reconnect_nl(int *fd)
+{
+    int new_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
+
+    close (*fd);
+
+    if (new_fd < 0)
+	return errno;
+
+    *fd = new_fd;
+    return 0;
+}
+
+static void SLPDDatabaseWatcher_nl(int *fd, int flag, unsigned char *porthash)
+{
+    char buf[8192];
+    int port, status = 0;
+    SLPDatabaseHandle dh;
+
+    struct sockaddr_nl nladdr = {
+	.nl_family = AF_NETLINK
+    };
+
+    struct {
+	struct nlmsghdr nlh;
+	struct inet_diag_req r;
+    } req = {
+	.nlh = {
+	    .nlmsg_len = sizeof(req),
+	    .nlmsg_type = TCPDIAG_GETSOCK,
+	    .nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST,
+	    .nlmsg_pid = 0,
+	    .nlmsg_seq = 123456,
+	},
+	.r = {
+	    .idiag_family = AF_INET,
+	    .idiag_states = 1 << SS_LISTEN,
+	    .idiag_ext = ((1 << (INET_DIAG_INFO - 1)) |
+		          (1 << (INET_DIAG_VEGASINFO - 1)) |
+			  (1 << (INET_DIAG_CONG - 1))),
+	}
+    };
+
+    struct iovec iov = {
+	.iov_base = &req,
+	.iov_len = sizeof(req),
+    };
+
+    struct msghdr msg = {
+	.msg_name	= (void *)&nladdr,
+	.msg_namelen	= sizeof(nladdr),
+	.msg_iov	= &iov,
+	.msg_iovlen	= 1,
+    };
+    struct in_addr ipv4_loopback = { htonl(INADDR_LOOPBACK) };
+    struct in6_addr ipv6_loopback = IN6ADDR_LOOPBACK_INIT;
+    int retries;
+
+    /* If the socket shuts down for whatever reason, we need to
+     * reopen it. Since we can't listen to a socket for which we have
+     * made a request, we reissue the request and listen again. */
+retry_sendmsg:
+    retries = 2;
+    while (retries-- > 0) {
+	if (sendmsg(*fd, &msg, 0) >= 0)
+	    break;
+
+	if (reconnect_nl(fd)) {
+	    SLPDLog("Lost TCPDIAG netlink connection and attempts to "
+		    "re-establish have failed. Falling back to /proc/net/tcp "
+		    "for dead/alive updates.\n");
+	    *fd = -1;
+	    return;
+	}
+	sched_yield();
+    }
+
+    iov.iov_base = buf;
+    iov.iov_len = sizeof(buf);
+
+    dh = SLPDatabaseOpen(&G_SlpdDatabase.database);
+    while (!status) {
+	struct nlmsghdr *h;
+
+	status = recvmsg(*fd, &msg, 0);
+	if (status < 0) {
+	    if (errno == EINTR)
+		continue;
+	    goto retry_sendmsg;
+	}
+
+	/* Socket has shut down */
+	if (status == 0)
+	    goto retry_sendmsg;
+
+	for (h = (struct nlmsghdr *) buf; NLMSG_OK(h, status);
+	     h = NLMSG_NEXT(h, status)) {
+	    SLPDatabaseEntry *entry;
+	    struct inet_diag_msg *r = NLMSG_DATA(h);
+
+	    if (h->nlmsg_seq != 123456)
+		continue;
+
+	    if (h->nlmsg_type == NLMSG_DONE)
+		goto close;
+
+	    if (h->nlmsg_type == NLMSG_ERROR) {
+		struct nlmsgerr *err = NLMSG_DATA(h);
+		if (h->nlmsg_len >= NLMSG_LENGTH(sizeof(*err)))
+		    status = EINVAL;
+		else
+		    status = -err->error;
+		break;
+	    }
+
+	    if (r->idiag_family != AF_INET && r->idiag_family != AF_INET6)
+		continue;
+
+	    if (r->idiag_family == AF_INET &&
+		ipv4_loopback.s_addr == r->id.idiag_src[0])
+		continue;
+
+	    if (r->idiag_family == AF_INET6 &&
+		!memcmp(ipv6_loopback.s6_addr32, r->id.idiag_src,
+			sizeof(ipv6_loopback)))
+		continue;
+
+	    port = ntohs(r->id.idiag_sport);
+	    if (!(porthash[(port / 8) & 255] & (1 << (port & 7))))
+		continue;
+
+	    SLPDatabaseRewind(dh);
+
+	    while ((entry = SLPDatabaseEnum(dh)) != 0) {
+		SLPSrvReg *srvreg = &(entry->msg->body.srvreg);
+		if (!(srvreg->watchflags & flag))
+		    continue;
+		if (port == srvreg->watchport)
+		    srvreg->watchflags &= ~SLP_REG_WATCH_CHECKING;
+	    }
+	}
+    }
+
+close:
+    SLPDatabaseClose(dh);
+}
+
 /*=========================================================================*/
 void SLPDDatabaseWatcher(void)
 {
     static int initialized = 0;
-    static int proctcp, procudp, proctcp6, procudp6;
+    static int proctcp, procudp, proctcp6, procudp6, inet_diag = -1;
     unsigned char porthash[256];
     int flags, port;
     SLPDatabaseHandle dh;
@@ -931,6 +1099,7 @@ void SLPDDatabaseWatcher(void)
     SLPSrvReg*          srvreg;
 
     if (!initialized) {
+	inet_diag = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
 	proctcp = open("/proc/net/tcp_listen", O_RDONLY);
 	if (proctcp == -1)
 	  proctcp = open("/proc/net/tcp", O_RDONLY);
@@ -955,8 +1124,12 @@ void SLPDDatabaseWatcher(void)
     }
     SLPDatabaseClose(dh);
     if ((flags & SLP_REG_WATCH_TCP) != 0) {
-	SLPDDatabaseWatcher_fd(proctcp, SLP_REG_WATCH_TCP, porthash);
-	SLPDDatabaseWatcher_fd(proctcp6, SLP_REG_WATCH_TCP, porthash);
+	if (inet_diag >= 0)
+		SLPDDatabaseWatcher_nl(&inet_diag, SLP_REG_WATCH_TCP, porthash);
+	if (inet_diag < 0) { /* Fallback if _nl fails */
+		SLPDDatabaseWatcher_fd(proctcp, SLP_REG_WATCH_TCP, porthash);
+		SLPDDatabaseWatcher_fd(proctcp6, SLP_REG_WATCH_TCP, porthash);
+	}
     }
     if ((flags & SLP_REG_WATCH_UDP) != 0) {
 	SLPDDatabaseWatcher_fd(procudp, SLP_REG_WATCH_UDP, porthash);