Integrate libmnl (minimal netlink) library with libosmocore select loop

This adds an easy way to listen to netlink events form the Linux kernel
from within libosmocore applications.

The new dependency can be disabled via the "--disable-lbimnl" configure flag.

Change-Id: I4f787ee68f0d6d04f0a5655eb57d55b3b326a42f
diff --git a/src/mnl.c b/src/mnl.c
new file mode 100644
index 0000000..13d9c9e
--- /dev/null
+++ b/src/mnl.c
@@ -0,0 +1,116 @@
+/*! \file mnl.c
+ *
+ * This code integrates libmnl (minimal netlink library) into the osmocom select
+ * loop abstraction.  It allows other osmocom libraries or application code to
+ * create netlink sockets and subscribe to netlink events via libmnl.  The completion
+ * handler / callbacks are dispatched via libosmocore select loop handling.
+ */
+
+/*
+ * (C) 2020 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserverd.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301, USA.
+ */
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/mnl.h>
+
+#include <libmnl/libmnl.h>
+
+#include <errno.h>
+#include <string.h>
+
+/* osmo_fd call-back for when RTNL socket is readable */
+static int osmo_mnl_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	uint8_t buf[MNL_SOCKET_BUFFER_SIZE];
+	struct osmo_mnl *omnl = ofd->data;
+	int rc;
+
+	if (!(what & OSMO_FD_READ))
+		return 0;
+
+	rc = mnl_socket_recvfrom(omnl->mnls, buf, sizeof(buf));
+	if (rc <= 0) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "Error in mnl_socket_recvfrom(): %s\n",
+			strerror(errno));
+		return -EIO;
+	}
+
+	return mnl_cb_run(buf, rc, 0, 0, omnl->mnl_cb, omnl);
+}
+
+/*! create an osmocom-wrapped limnl netlink socket.
+ *  \parma[in] ctx talloc context from which to allocate
+ *  \param[in] bus netlink socket bus ID (see NETLINK_* constants)
+ *  \param[in] groups groups of messages to bind/subscribe to
+ *  \param[in] mnl_cb callback function called for each incoming message
+ *  \param[in] priv opaque private user data
+ *  \returns newly-allocated osmo_mnl or NULL in case of error. */
+struct osmo_mnl *osmo_mnl_init(void *ctx, int bus, unsigned int groups, mnl_cb_t mnl_cb, void *priv)
+{
+	struct osmo_mnl *olm = talloc_zero(ctx, struct osmo_mnl);
+
+	if (!olm)
+		return NULL;
+
+	olm->priv = priv;
+	olm->mnl_cb = mnl_cb;
+	olm->mnls = mnl_socket_open2(bus, SOCK_CLOEXEC);
+	if (!olm->mnls) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "Error creating netlink socket for bus %d: %s\n",
+			bus, strerror(errno));
+		goto out_free;
+	}
+
+	if (mnl_socket_bind(olm->mnls, groups, MNL_SOCKET_AUTOPID) < 0) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "Error binding netlink socket for bus %d to groups 0x%x: %s\n",
+			bus, groups, strerror(errno));
+		goto out_close;
+	}
+
+	osmo_fd_setup(&olm->ofd, mnl_socket_get_fd(olm->mnls), OSMO_FD_READ, osmo_mnl_fd_cb, olm, 0);
+
+	if (osmo_fd_register(&olm->ofd)) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "Error registering netlinks socket\n");
+		goto out_close;
+	}
+
+	return olm;
+
+out_close:
+	mnl_socket_close(olm->mnls);
+out_free:
+	talloc_free(olm);
+	return NULL;
+}
+
+/*! destroy an existing osmocom-wrapped mnl netlink socket: Unregister + close + free.
+ *  \param[in] omnl osmo_mnl socket previously returned by osmo_mnl_init() */
+void osmo_mnl_destroy(struct osmo_mnl *omnl)
+{
+	if (!omnl)
+		return;
+
+	osmo_fd_unregister(&omnl->ofd);
+	mnl_socket_close(omnl->mnls);
+	talloc_free(omnl);
+}