[PATCH 3/3] core: retry generate local port in nl_connect on ADDRINUSE

Thomas Haller thaller at redhat.com
Tue Apr 8 05:06:26 PDT 2014


It is a common error, 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
themselfes, 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 a matching port.

Signed-off-by: Thomas Haller <thaller at redhat.com>
---
 include/Makefile.am              |  1 +
 include/netlink-private/socket.h | 29 ++++++++++++++++++++++++++
 lib/nl.c                         | 43 +++++++++++++++++++++++++++++++++-----
 lib/socket.c                     | 45 ++++++++++++++++++++++++++++++++++++++--
 libnl.sym.in                     |  4 ++++
 5 files changed, 115 insertions(+), 7 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..913545c
--- /dev/null
+++ b/include/netlink-private/socket.h
@@ -0,0 +1,29 @@
+/*
+ * 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);
+void _nl_socket_release_used_ports(const uint8_t *used_ports);
+void _nl_socket_set_used_ports(uint8_t *used_ports, uint32_t port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/nl.c b/lib/nl.c
index 4692490..455be07 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>
@@ -106,11 +107,43 @@ 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)) {
+		int errsv;
+		uint32_t port;
+		uint8_t used_ports[32] = { 0 };
+
+		while (1) {
+			nl_socket_set_local_port(sk, 0);
+
+			port = nl_socket_get_local_port(sk);
+			if (port == UINT_MAX) {
+				/* no more ports available. */
+				_nl_socket_release_used_ports(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_socket_set_used_ports(used_ports, port);
+			else {
+				_nl_socket_release_used_ports(used_ports);
+				err = -nl_syserr2nlerr(errsv);
+				goto errout;
+			}
+		}
+		_nl_socket_release_used_ports(used_ports);
+	} else {
+		err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
+			   sizeof(sk->s_local));
+		if (err != 0) {
+			err = -nl_syserr2nlerr(errno);
+			goto errout;
+		}
 	}
 
 	addrlen = sizeof(sk->s_local);
diff --git a/lib/socket.c b/lib/socket.c
index f94ee7f..94d45b8 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_release_used_ports(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++)
+				used_ports_map[i] &= ~(used_ports[i]);
+			nl_write_unlock(&port_map_lock);
+			return;
+		}
+	}
+}
+
+void _nl_socket_set_used_ports(uint8_t *used_ports, uint32_t port)
+{
+	int nr;
+
+	if (port == UINT32_MAX || port == 0 ||
+	    (getpid() & 0x3FFFFF) != (port & 0x3FFFFF)) {
+		BUG();
+		return;
+	}
+
+	nr = port >> 22;
+	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,6 +303,12 @@ 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;
 }
 
diff --git a/libnl.sym.in b/libnl.sym.in
index e8f6c53..f684206 100644
--- a/libnl.sym.in
+++ b/libnl.sym.in
@@ -1,4 +1,8 @@
 libnl_ at MAJ_VERSION@ {
 global:
 	*;
+local:
+	_nl_socket_is_local_port_unspecified;
+	_nl_socket_release_used_ports;
+	_nl_socket_set_used_ports;
 };
-- 
1.9.0




More information about the libnl mailing list