[PATCH 3/4] lib/socket: retry generate local port in nl_connect on ADDRINUSE

Thomas Haller thaller at redhat.com
Wed Apr 9 03:08:52 PDT 2014


It can easily happen that the generated local netlink port is alrady in
use. In that case bind will fail with ADDRINUSE.

Users of libnl3 could workaround this, by managing the local ports
themselves, but sometimes these users are libraries too and they also
don't know which ports might be used by other components.

This patch changes that nl_socket_alloc() no longer initilizes the local
port id immediately. Instead it will be initialized only after the user calls
nl_socket_get_local_port() the first time and thereby shows interest in
the value.

If bind() fails due to ADDRINUSE, we check if the user ever cared about
the local port, i.e. whether the local port is still unset. In that case
we assume that libnl should choose a suitable port and we retry until we
find an unused port.

Signed-off-by: Thomas Haller <thaller at redhat.com>
---
 include/Makefile.am              |  1 +
 include/netlink-private/socket.h | 31 +++++++++++++++++++++
 lib/nl.c                         | 52 ++++++++++++++++++++++++++++++-----
 lib/socket.c                     | 58 ++++++++++++++++++++++++++++++++++++++--
 libnl.sym.in                     |  5 ++++
 5 files changed, 138 insertions(+), 9 deletions(-)
 create mode 100644 include/netlink-private/socket.h

diff --git a/include/Makefile.am b/include/Makefile.am
index 1b13f5a..8effe06 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -138,6 +138,7 @@ noinst_HEADERS = \
 	linux/tc_ematch/tc_em_meta.h \
 	netlink-private/genl.h \
 	netlink-private/netlink.h \
+	netlink-private/socket.h \
 	netlink-private/tc.h \
 	netlink-private/types.h \
 	netlink-private/cache-api.h \
diff --git a/include/netlink-private/socket.h b/include/netlink-private/socket.h
new file mode 100644
index 0000000..d33d064
--- /dev/null
+++ b/include/netlink-private/socket.h
@@ -0,0 +1,31 @@
+/*
+ * netlink-private/socket.h		Private declarations for socket
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Thomas Graf <tgraf at suug.ch>
+ */
+
+#ifndef NETLINK_SOCKET_PRIV_H_
+#define NETLINK_SOCKET_PRIV_H_
+
+#include <netlink-private/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int _nl_socket_is_local_port_unspecified (struct nl_sock *sk);
+uint32_t _nl_socket_generate_local_port_no_release(struct nl_sock *sk);
+
+void _nl_socket_used_ports_release_all(const uint8_t *used_ports);
+void _nl_socket_used_ports_set(uint8_t *used_ports, uint32_t port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/nl.c b/lib/nl.c
index 4692490..87d1968 100644
--- a/lib/nl.c
+++ b/lib/nl.c
@@ -26,6 +26,7 @@
  */
 
 #include <netlink-private/netlink.h>
+#include <netlink-private/socket.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/handlers.h>
@@ -85,6 +86,7 @@
 int nl_connect(struct nl_sock *sk, int protocol)
 {
 	int err, flags = 0;
+	int errsv;
 	socklen_t addrlen;
 
 #ifdef SOCK_CLOEXEC
@@ -96,7 +98,9 @@ int nl_connect(struct nl_sock *sk, int protocol)
 
 	sk->s_fd = socket(AF_NETLINK, SOCK_RAW | flags, protocol);
 	if (sk->s_fd < 0) {
-		err = -nl_syserr2nlerr(errno);
+		errsv = errno;
+		NL_DBG(4, "nl_connect(%p): socket() failed with %d\n", sk, errsv);
+		err = -nl_syserr2nlerr(errsv);
 		goto errout;
 	}
 
@@ -106,11 +110,45 @@ int nl_connect(struct nl_sock *sk, int protocol)
 			goto errout;
 	}
 
-	err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
-		   sizeof(sk->s_local));
-	if (err < 0) {
-		err = -nl_syserr2nlerr(errno);
-		goto errout;
+	if (_nl_socket_is_local_port_unspecified (sk)) {
+		uint32_t port;
+		uint8_t used_ports[32] = { 0 };
+
+		while (1) {
+			port = _nl_socket_generate_local_port_no_release(sk);
+
+			if (port == UINT32_MAX) {
+				NL_DBG(4, "nl_connect(%p): no more unused local ports.\n", sk);
+				_nl_socket_used_ports_release_all(used_ports);
+				err = -NLE_EXIST;
+				goto errout;
+			}
+			err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
+				   sizeof(sk->s_local));
+			if (err == 0)
+				break;
+
+			errsv = errno;
+			if (errsv == EADDRINUSE) {
+				NL_DBG(4, "nl_connect(%p): local port %u already in use. Retry.\n", sk, (unsigned) port);
+				_nl_socket_used_ports_set(used_ports, port);
+			} else {
+				NL_DBG(4, "nl_connect(%p): bind() for port %u failed with %d\n", sk, (unsigned) port, errsv);
+				_nl_socket_used_ports_release_all(used_ports);
+				err = -nl_syserr2nlerr(errsv);
+				goto errout;
+			}
+		}
+		_nl_socket_used_ports_release_all(used_ports);
+	} else {
+		err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
+			   sizeof(sk->s_local));
+		if (err != 0) {
+			errsv = errno;
+			NL_DBG(4, "nl_connect(%p): bind() failed with %d\n", sk, errsv);
+			err = -nl_syserr2nlerr(errsv);
+			goto errout;
+		}
 	}
 
 	addrlen = sizeof(sk->s_local);
@@ -405,7 +443,7 @@ void nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg)
 
 	nlh = nlmsg_hdr(msg);
 	if (nlh->nlmsg_pid == NL_AUTO_PORT)
-		nlh->nlmsg_pid = sk->s_local.nl_pid;
+		nlh->nlmsg_pid = nl_socket_get_local_port(sk);
 
 	if (nlh->nlmsg_seq == NL_AUTO_SEQ)
 		nlh->nlmsg_seq = sk->s_seq_next++;
diff --git a/lib/socket.c b/lib/socket.c
index f94ee7f..cf0dba1 100644
--- a/lib/socket.c
+++ b/lib/socket.c
@@ -30,6 +30,7 @@
 #include "defs.h"
 
 #include <netlink-private/netlink.h>
+#include <netlink-private/socket.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/handlers.h>
@@ -97,7 +98,7 @@ static void release_local_port(uint32_t port)
 {
 	int nr;
 
-	if (port == UINT32_MAX)
+	if (port == UINT32_MAX || port == 0)
 		return;
 	
 	nr = port >> 22;
@@ -107,6 +108,35 @@ static void release_local_port(uint32_t port)
 	nl_write_unlock(&port_map_lock);
 }
 
+void _nl_socket_used_ports_release_all(const uint8_t *used_ports)
+{
+	int i;
+
+	for (i = 0; i < 32; i++) {
+		if (used_ports[i] != 0) {
+			nl_write_lock(&port_map_lock);
+			for (; i < 32; i++) {
+				BUG_ON((used_ports_map[i] & used_ports[i]) != used_ports[i]);
+				used_ports_map[i] &= ~(used_ports[i]);
+			}
+			nl_write_unlock(&port_map_lock);
+			return;
+		}
+	}
+}
+
+void _nl_socket_used_ports_set(uint8_t *used_ports, uint32_t port)
+{
+	int nr;
+
+	nr = port >> 22;
+
+	BUG_ON(port == UINT32_MAX || port == 0 || (getpid() & 0x3FFFFF) != (port & 0x3FFFFF));
+	BUG_ON(used_ports[nr / 32] & (1 << (nr % 32)));
+
+	used_ports[nr / 32] |= (1 << (nr % 32));
+}
+
 /**
  * @name Allocation
  * @{
@@ -125,7 +155,7 @@ static struct nl_sock *__alloc_socket(struct nl_cb *cb)
 	sk->s_local.nl_family = AF_NETLINK;
 	sk->s_peer.nl_family = AF_NETLINK;
 	sk->s_seq_expect = sk->s_seq_next = time(0);
-	sk->s_local.nl_pid = generate_local_port();
+	sk->s_local.nl_pid = 0;
 
 	return sk;
 }
@@ -261,6 +291,11 @@ void nl_socket_enable_auto_ack(struct nl_sock *sk)
 
 /** @} */
 
+int _nl_socket_is_local_port_unspecified(struct nl_sock *sk)
+{
+	return (sk->s_local.nl_pid == 0);
+}
+
 /**
  * @name Source Idenficiation
  * @{
@@ -268,9 +303,28 @@ void nl_socket_enable_auto_ack(struct nl_sock *sk)
 
 uint32_t nl_socket_get_local_port(const struct nl_sock *sk)
 {
+	if (sk->s_local.nl_pid == 0) {
+		/* modify the const argument sk. This is justified, because
+		 * nobody ever saw the local_port from externally. So, we
+		 * initilize it on first use. */
+		nl_socket_set_local_port ((struct nl_sock *) sk, 0);
+	}
 	return sk->s_local.nl_pid;
 }
 
+uint32_t _nl_socket_generate_local_port_no_release(struct nl_sock *sk)
+{
+	uint32_t port;
+
+	/* similar to nl_socket_set_local_port(sk, 0), but do not release
+	 * the previously generated port. */
+
+	port = generate_local_port();
+	sk->s_flags &= ~NL_OWN_PORT;
+	sk->s_local.nl_pid = port;
+	return port;
+}
+
 /**
  * Set local port of socket
  * @arg sk		Netlink socket.
diff --git a/libnl.sym.in b/libnl.sym.in
index e8f6c53..df8888c 100644
--- a/libnl.sym.in
+++ b/libnl.sym.in
@@ -1,4 +1,9 @@
 libnl_ at MAJ_VERSION@ {
 global:
 	*;
+local:
+	_nl_socket_generate_local_port_no_release;
+	_nl_socket_is_local_port_unspecified;
+	_nl_socket_used_ports_release_all;
+	_nl_socket_used_ports_set;
 };
-- 
1.9.0




More information about the libnl mailing list