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 \
--- /dev/null
+/*
+ * 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@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 uint32_t *used_ports);
+void _nl_socket_used_ports_set(uint32_t *used_ports, uint32_t port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE = 3,
#define NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE
+ /**
+ * Indicate that the local port is unspecified until the user accesses
+ * it (via nl_socket_get_local_port()) or until nl_connect(). More importantly,
+ * if the port is left unspecified, nl_connect() will retry generating another
+ * port when bind() fails with ADDRINUSE.
+ */
+ NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE = 4,
+#define NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE
+
__NL_CAPABILITY_MAX
#define NL_CAPABILITY_MAX (__NL_CAPABILITY_MAX - 1)
};
*/
#include <netlink-private/netlink.h>
+#include <netlink-private/socket.h>
#include <netlink/netlink.h>
#include <netlink/utils.h>
#include <netlink/handlers.h>
* be closed automatically if any of the `exec` family functions succeed.
* This is essential for multi threaded programs.
*
+ * @note The local port (`nl_socket_get_local_port()`) is unspecified after
+ * creating a new socket. It only gets determined when accessing the
+ * port the first time or during `nl_connect()`. When nl_connect()
+ * fails during `bind()` due to `ADDRINUSE`, it will retry with
+ * different ports if the port is unspecified. Unless you want to enforce
+ * the use of a specific local port, don't access the local port (or
+ * reset it to `unspecified` by calling `nl_socket_set_local_port(sk, 0)`).
+ * This capability is indicated by
+ * `%NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE`.
+ *
* @see nl_socket_alloc()
* @see nl_close()
*
int nl_connect(struct nl_sock *sk, int protocol)
{
int err, flags = 0;
+ int errsv;
socklen_t addrlen;
#ifdef SOCK_CLOEXEC
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;
}
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;
+ uint32_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);
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++;
#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>
static void release_local_port(uint32_t port)
{
int nr;
+ uint32_t mask;
if (port == UINT32_MAX)
return;
-
+
+ BUG_ON(port == 0);
+
nr = port >> 22;
+ mask = 1UL << (nr % 32);
+ nr /= 32;
nl_write_lock(&port_map_lock);
- used_ports_map[nr / 32] &= ~(1 << (nr % 32));
+ BUG_ON((used_ports_map[nr] & mask) != mask);
+ used_ports_map[nr] &= ~mask;
nl_write_unlock(&port_map_lock);
}
+/** \cond skip */
+void _nl_socket_used_ports_release_all(const uint32_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(uint32_t *used_ports, uint32_t port)
+{
+ int nr;
+ int32_t mask;
+
+ nr = port >> 22;
+ mask = 1UL << (nr % 32);
+ nr /= 32;
+
+ /*
+ BUG_ON(port == UINT32_MAX || port == 0 || (getpid() & 0x3FFFFF) != (port & 0x3FFFFF));
+ BUG_ON(used_ports[nr] & mask);
+ */
+
+ used_ports[nr] |= mask;
+}
+/** \endcond */
+
/**
* @name Allocation
* @{
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();
+
+ /* the port is 0 (unspecified), meaning NL_OWN_PORT */
+ sk->s_flags = NL_OWN_PORT;
return sk;
}
/** @} */
+/** \cond skip */
+int _nl_socket_is_local_port_unspecified(struct nl_sock *sk)
+{
+ return (sk->s_local.nl_pid == 0);
+}
+
+uint32_t _nl_socket_generate_local_port_no_release(struct nl_sock *sk)
+{
+ uint32_t port;
+
+ /* reset the port to generate_local_port(), 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;
+}
+/** \endcond */
+
/**
* @name Source Idenficiation
* @{
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.
+ *
+ * Note that this also means that you cannot call this function
+ * from multiple threads without synchronization. But nl_sock
+ * is not automatically threadsafe anyway, so the user is not
+ * allowed to do that.
+ */
+ return _nl_socket_generate_local_port_no_release((struct nl_sock *) sk);
+ }
return sk->s_local.nl_pid;
}
* @arg sk Netlink socket.
* @arg port Local port identifier
*
- * Assigns a local port identifier to the socket. If port is 0
- * a unique port identifier will be generated automatically.
+ * Assigns a local port identifier to the socket.
+ *
+ * If port is 0, the port is reset to 'unspecified' as it is after newly
+ * calling nl_socket_alloc().
+ * Unspecified means, that the port will be generated automatically later
+ * on first use (either on nl_socket_get_local_port() or nl_connect()).
*/
void nl_socket_set_local_port(struct nl_sock *sk, uint32_t port)
{
- if (port == 0) {
- port = generate_local_port();
- /*
- * Release local port after generation of a new one to be
- * able to change local port using nl_socket_set_local_port(, 0)
- */
- if (!(sk->s_flags & NL_OWN_PORT))
- release_local_port(sk->s_local.nl_pid);
- else
- sk->s_flags &= ~NL_OWN_PORT;
- } else {
- if (!(sk->s_flags & NL_OWN_PORT))
- release_local_port(sk->s_local.nl_pid);
- sk->s_flags |= NL_OWN_PORT;
- }
-
+ if (!(sk->s_flags & NL_OWN_PORT))
+ release_local_port(sk->s_local.nl_pid);
+ sk->s_flags |= NL_OWN_PORT;
sk->s_local.nl_pid = port;
}
NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE,
NL_CAPABILITY_ROUTE_LINK_VETH_GET_PEER_OWN_REFERENCE,
NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE,
- 0,
+ NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE,
0,
0,
0,
libnl_@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;
};