summary refs log tree commit diff
path: root/src/platform/nm-netlink.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-06-04 00:07:45 +0200
committerMichael Biebl <biebl@debian.org>2018-06-04 00:07:45 +0200
commit04bc9e1cd3544445d883ad29ea108c1645c8e7b7 (patch)
treed10c354b1b980ca8a7b9e48ec9019e8ed88bde2b /src/platform/nm-netlink.c
parentee9c73a923909e23a649407be77e25235d769e25 (diff)
New upstream version 1.11.4 upstream/1.11.4
Diffstat (limited to 'src/platform/nm-netlink.c')
-rw-r--r--src/platform/nm-netlink.c1491
1 files changed, 1491 insertions, 0 deletions
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
new file mode 100644
index 00000000..4cb19780
--- /dev/null
+++ b/src/platform/nm-netlink.c
@@ -0,0 +1,1491 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * 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, 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.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-netlink.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+/*****************************************************************************/
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+/*****************************************************************************/
+
+#define NL_SOCK_PASSCRED        (1<<1)
+#define NL_MSG_PEEK             (1<<3)
+#define NL_MSG_PEEK_EXPLICIT    (1<<4)
+#define NL_NO_AUTO_ACK          (1<<5)
+
+#ifndef NETLINK_EXT_ACK
+#define NETLINK_EXT_ACK         11
+#endif
+
+#define NL_MSG_CRED_PRESENT 1
+
+struct nl_msg {
+	int                     nm_protocol;
+	int                     nm_flags;
+	struct sockaddr_nl      nm_src;
+	struct sockaddr_nl      nm_dst;
+	struct ucred            nm_creds;
+	struct nlmsghdr *       nm_nlh;
+	size_t                  nm_size;
+	int                     nm_refcnt;
+};
+
+struct nl_sock {
+	struct sockaddr_nl      s_local;
+	struct sockaddr_nl      s_peer;
+	int                     s_fd;
+	int                     s_proto;
+	unsigned int            s_seq_next;
+	unsigned int            s_seq_expect;
+	int                     s_flags;
+	size_t                  s_bufsize;
+};
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, int,
+	NM_UTILS_LOOKUP_DEFAULT (NULL),
+	NM_UTILS_LOOKUP_ITEM (NLE_UNSPEC,          "NLE_UNSPEC"),
+	NM_UTILS_LOOKUP_ITEM (NLE_BUG,             "NLE_BUG"),
+	NM_UTILS_LOOKUP_ITEM (NLE_NATIVE_ERRNO,    "NLE_NATIVE_ERRNO"),
+
+	NM_UTILS_LOOKUP_ITEM (NLE_ATTRSIZE,        "NLE_ATTRSIZE"),
+	NM_UTILS_LOOKUP_ITEM (NLE_BAD_SOCK,        "NLE_BAD_SOCK"),
+	NM_UTILS_LOOKUP_ITEM (NLE_DUMP_INTR,       "NLE_DUMP_INTR"),
+	NM_UTILS_LOOKUP_ITEM (NLE_MSG_OVERFLOW,    "NLE_MSG_OVERFLOW"),
+	NM_UTILS_LOOKUP_ITEM (NLE_MSG_TOOSHORT,    "NLE_MSG_TOOSHORT"),
+	NM_UTILS_LOOKUP_ITEM (NLE_MSG_TRUNC,       "NLE_MSG_TRUNC"),
+	NM_UTILS_LOOKUP_ITEM (NLE_SEQ_MISMATCH,    "NLE_SEQ_MISMATCH"),
+)
+
+const char *
+nl_geterror (int err)
+{
+	const char *s;
+
+	err = nl_errno (err);
+
+	if (err >= _NLE_BASE) {
+		s = _geterror (err);
+		if (s)
+			return s;
+	}
+	return g_strerror (err);
+}
+
+/*****************************************************************************/
+
+NM_UTILS_ENUM2STR_DEFINE (nl_nlmsgtype2str, int,
+	NM_UTILS_ENUM2STR (NLMSG_NOOP,    "NOOP"),
+	NM_UTILS_ENUM2STR (NLMSG_ERROR,   "ERROR"),
+	NM_UTILS_ENUM2STR (NLMSG_DONE,    "DONE"),
+	NM_UTILS_ENUM2STR (NLMSG_OVERRUN, "OVERRUN"),
+);
+
+NM_UTILS_FLAGS2STR_DEFINE (nl_nlmsg_flags2str, int,
+	NM_UTILS_FLAGS2STR (NLM_F_REQUEST, "REQUEST"),
+	NM_UTILS_FLAGS2STR (NLM_F_MULTI,   "MULTI"),
+	NM_UTILS_FLAGS2STR (NLM_F_ACK,     "ACK"),
+	NM_UTILS_FLAGS2STR (NLM_F_ECHO,    "ECHO"),
+	NM_UTILS_FLAGS2STR (NLM_F_ROOT,    "ROOT"),
+	NM_UTILS_FLAGS2STR (NLM_F_MATCH,   "MATCH"),
+	NM_UTILS_FLAGS2STR (NLM_F_ATOMIC,  "ATOMIC"),
+	NM_UTILS_FLAGS2STR (NLM_F_REPLACE, "REPLACE"),
+	NM_UTILS_FLAGS2STR (NLM_F_EXCL,    "EXCL"),
+	NM_UTILS_FLAGS2STR (NLM_F_CREATE,  "CREATE"),
+	NM_UTILS_FLAGS2STR (NLM_F_APPEND,  "APPEND"),
+);
+
+/*****************************************************************************/
+
+const char *
+nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
+{
+	const char *b;
+	const char *s;
+	guint flags, flags_before;
+	const char *prefix;
+
+	if (!nm_utils_to_string_buffer_init_null (hdr, &buf, &len))
+		return buf;
+
+	b = buf;
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:    s = "RTM_NEWLINK";  break;
+	case RTM_DELLINK:    s = "RTM_DELLINK";  break;
+	case RTM_NEWADDR:    s = "RTM_NEWADDR";  break;
+	case RTM_DELADDR:    s = "RTM_DELADDR";  break;
+	case RTM_NEWROUTE:   s = "RTM_NEWROUTE"; break;
+	case RTM_DELROUTE:   s = "RTM_DELROUTE"; break;
+	case RTM_NEWQDISC:   s = "RTM_NEWQDISC"; break;
+	case RTM_DELQDISC:   s = "RTM_DELQDISC"; break;
+	case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break;
+	case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break;
+	case NLMSG_NOOP:     s = "NLMSG_NOOP"; break;
+	case NLMSG_ERROR:    s = "NLMSG_ERROR"; break;
+	case NLMSG_DONE:     s = "NLMSG_DONE"; break;
+	case NLMSG_OVERRUN:  s = "NLMSG_OVERRUN"; break;
+	default:             s = NULL;       break;
+	}
+
+	if (s)
+		nm_utils_strbuf_append_str (&buf, &len, s);
+	else
+		nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
+
+	flags = hdr->nlmsg_flags;
+
+	if (!flags) {
+		nm_utils_strbuf_append_str (&buf, &len, ", flags 0");
+		goto flags_done;
+	}
+
+#define _F(f, n) \
+	G_STMT_START { \
+		if (NM_FLAGS_ALL (flags, f)) { \
+			flags &= ~(f); \
+			nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \
+			if (!flags) \
+				goto flags_done; \
+			prefix = ","; \
+		} \
+	} G_STMT_END
+
+	prefix = ", flags ";
+	flags_before = flags;
+	_F (NLM_F_REQUEST, "request");
+	_F (NLM_F_MULTI, "multi");
+	_F (NLM_F_ACK, "ack");
+	_F (NLM_F_ECHO, "echo");
+	_F (NLM_F_DUMP_INTR, "dump_intr");
+	_F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
+
+	if (flags_before != flags)
+		prefix = ";";
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_NEWADDR:
+	case RTM_NEWROUTE:
+	case RTM_NEWQDISC:
+	case RTM_NEWTFILTER:
+		_F (NLM_F_REPLACE, "replace");
+		_F (NLM_F_EXCL, "excl");
+		_F (NLM_F_CREATE, "create");
+		_F (NLM_F_APPEND, "append");
+		break;
+	case RTM_GETLINK:
+	case RTM_GETADDR:
+	case RTM_GETROUTE:
+	case RTM_DELQDISC:
+	case RTM_DELTFILTER:
+		_F (NLM_F_DUMP, "dump");
+		_F (NLM_F_ROOT, "root");
+		_F (NLM_F_MATCH, "match");
+		_F (NLM_F_ATOMIC, "atomic");
+		break;
+	}
+
+#undef _F
+
+	if (flags_before != flags)
+		prefix = ";";
+	nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags);
+
+flags_done:
+
+	nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
+
+	return b;
+}
+
+/*****************************************************************************/
+
+struct nlmsghdr *
+nlmsg_hdr (struct nl_msg *n)
+{
+	return n->nm_nlh;
+}
+
+void *
+nlmsg_reserve (struct nl_msg *n, size_t len, int pad)
+{
+	char *buf = (char *) n->nm_nlh;
+	size_t nlmsg_len = n->nm_nlh->nlmsg_len;
+	size_t tlen;
+
+	if (len > n->nm_size)
+		return NULL;
+
+	tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len;
+
+	if ((tlen + nlmsg_len) > n->nm_size)
+		return NULL;
+
+	buf += nlmsg_len;
+	n->nm_nlh->nlmsg_len += tlen;
+
+	if (tlen > len)
+		memset(buf + len, 0, tlen - len);
+
+	return buf;
+}
+
+/*****************************************************************************/
+
+static int
+ get_default_page_size (void)
+{
+	static int val = 0;
+	int v;
+
+	if (G_UNLIKELY (val == 0)) {
+		v = getpagesize ();
+		g_assert (v > 0);
+		val = v;
+	}
+	return val;
+}
+
+struct nlattr *
+nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
+{
+	struct nlattr *nla;
+	int tlen;
+
+	if (attrlen < 0)
+		return NULL;
+
+	tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
+
+	if (tlen > msg->nm_size)
+		return NULL;
+
+	nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+	nla->nla_type = attrtype;
+	nla->nla_len = nla_attr_size(attrlen);
+
+	if (attrlen)
+		memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
+	msg->nm_nlh->nlmsg_len = tlen;
+
+	return nla;
+}
+
+struct nl_msg *
+nlmsg_alloc_size (size_t len)
+{
+	struct nl_msg *nm;
+
+	if (len < sizeof (struct nlmsghdr))
+		len = sizeof (struct nlmsghdr);
+
+	nm = g_slice_new0 (struct nl_msg);
+
+	nm->nm_refcnt = 1;
+	nm->nm_protocol = -1;
+	nm->nm_size = len;
+	nm->nm_nlh = g_malloc0 (len);
+	nm->nm_nlh->nlmsg_len = nlmsg_total_size (0);
+	return nm;
+}
+
+/**
+ * Allocate a new netlink message with the default maximum payload size.
+ *
+ * Allocates a new netlink message without any further payload. The
+ * maximum payload size defaults to PAGESIZE or as otherwise specified
+ * with nlmsg_set_default_size().
+ *
+ * @return Newly allocated netlink message or NULL.
+ */
+struct nl_msg *
+nlmsg_alloc (void)
+{
+	return nlmsg_alloc_size (get_default_page_size ());
+}
+
+/**
+ * Allocate a new netlink message with maximum payload size specified.
+ */
+struct nl_msg *
+nlmsg_alloc_inherit (struct nlmsghdr *hdr)
+{
+	struct nl_msg *nm;
+
+	nm = nlmsg_alloc ();
+	if (hdr) {
+		struct nlmsghdr *new = nm->nm_nlh;
+
+		new->nlmsg_type = hdr->nlmsg_type;
+		new->nlmsg_flags = hdr->nlmsg_flags;
+		new->nlmsg_seq = hdr->nlmsg_seq;
+		new->nlmsg_pid = hdr->nlmsg_pid;
+	}
+
+	return nm;
+}
+
+struct nl_msg *
+nlmsg_alloc_convert (struct nlmsghdr *hdr)
+{
+	struct nl_msg *nm;
+
+	nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len));
+	memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
+	return nm;
+}
+
+struct nl_msg *
+nlmsg_alloc_simple (int nlmsgtype, int flags)
+{
+	struct nlmsghdr nlh = {
+		.nlmsg_type = nlmsgtype,
+		.nlmsg_flags = flags,
+	};
+
+	return nlmsg_alloc_inherit (&nlh);
+}
+
+int
+nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
+{
+	void *tmp;
+
+	tmp = nlmsg_reserve (n, len, pad);
+	if (tmp == NULL)
+		return -ENOMEM;
+
+	memcpy(tmp, data, len);
+	return 0;
+}
+
+int
+nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+             int maxtype, const struct nla_policy *policy)
+{
+	if (!nlmsg_valid_hdr(nlh, hdrlen))
+		return -NLE_MSG_TOOSHORT;
+
+	return nla_parse (tb, maxtype, nlmsg_attrdata(nlh, hdrlen),
+	                  nlmsg_attrlen(nlh, hdrlen), policy);
+}
+
+struct nlmsghdr *
+nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
+           int type, int payload, int flags)
+{
+	struct nlmsghdr *nlh;
+
+	if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN)
+		g_return_val_if_reached (NULL);
+
+	nlh = (struct nlmsghdr *) n->nm_nlh;
+	nlh->nlmsg_type = type;
+	nlh->nlmsg_flags = flags;
+	nlh->nlmsg_pid = pid;
+	nlh->nlmsg_seq = seq;
+
+	if (payload > 0 &&
+	    nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL)
+		return NULL;
+
+	return nlh;
+}
+
+uint64_t
+nla_get_u64 (const struct nlattr *nla)
+{
+	uint64_t tmp = 0;
+
+	if (nla && nla_len(nla) >= sizeof (tmp))
+		memcpy(&tmp, nla_data(nla), sizeof (tmp));
+
+	return tmp;
+}
+
+size_t
+nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize)
+{
+	size_t srclen = nla_len(nla);
+	const char *src = nla_data(nla);
+
+	if (srclen > 0 && src[srclen - 1] == '\0')
+		srclen--;
+
+	if (dstsize > 0) {
+		size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen;
+
+		memset(dst, 0, dstsize);
+		memcpy(dst, src, len);
+	}
+
+	return srclen;
+}
+
+int
+nla_memcpy (void *dest, const struct nlattr *src, int count)
+{
+	int minlen;
+
+	if (!src)
+		return 0;
+
+	minlen = NM_MIN (count, (int) nla_len (src));
+	memcpy(dest, nla_data(src), minlen);
+
+	return minlen;
+}
+
+int
+nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data)
+{
+	struct nlattr *nla;
+
+	nla = nla_reserve(msg, attrtype, datalen);
+	if (!nla) {
+		if (datalen < 0)
+			g_return_val_if_reached (-NLE_BUG);
+
+		return -ENOMEM;
+	}
+
+	if (datalen > 0)
+		memcpy (nla_data(nla), data, datalen);
+
+	return 0;
+}
+
+struct nlattr *
+nla_find (const struct nlattr *head, int len, int attrtype)
+{
+	const struct nlattr *nla;
+	int rem;
+
+	nla_for_each_attr (nla, head, len, rem) {
+		if (nla_type (nla) == attrtype)
+			return (struct nlattr*)nla;
+	}
+
+	return NULL;
+}
+
+void
+nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr)
+{
+	ssize_t len;
+
+	len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr;
+	if (len < 0)
+		g_return_if_reached ();
+	else if (len > 0) {
+		msg->nm_nlh->nlmsg_len -= len;
+		memset(nlmsg_tail(msg->nm_nlh), 0, len);
+	}
+}
+
+struct nlattr *
+nla_nest_start (struct nl_msg *msg, int attrtype)
+{
+	struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+
+	if (nla_put(msg, attrtype, 0, NULL) < 0)
+		return NULL;
+
+	return start;
+}
+
+static int
+_nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
+{
+	size_t pad, len;
+
+	len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
+
+	if (   len > USHRT_MAX
+	    || (!keep_empty && len == NLA_HDRLEN)) {
+		/*
+		 * Max nlattr size exceeded or empty nested attribute, trim the
+		 * attribute header again
+		 */
+		nla_nest_cancel(msg, start);
+
+		/* Return error only if nlattr size was exceeded */
+		return (len == NLA_HDRLEN) ? 0 : -NLE_ATTRSIZE;
+	}
+
+	start->nla_len = len;
+
+	pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
+	if (pad > 0) {
+		/*
+		 * Data inside attribute does not end at a alignment boundry.
+		 * Pad accordingly and accoun for the additional space in
+		 * the message. nlmsg_reserve() may never fail in this situation,
+		 * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
+		 */
+		if (!nlmsg_reserve(msg, pad, 0))
+			g_return_val_if_reached (-NLE_BUG);
+	}
+
+	return 0;
+}
+
+int
+nla_nest_end (struct nl_msg *msg, struct nlattr *start)
+{
+	return _nest_end (msg, start, 0);
+}
+
+static const uint16_t nla_attr_minlen[NLA_TYPE_MAX+1] = {
+	[NLA_U8]        = sizeof (uint8_t),
+	[NLA_U16]       = sizeof (uint16_t),
+	[NLA_U32]       = sizeof (uint32_t),
+	[NLA_U64]       = sizeof (uint64_t),
+	[NLA_STRING]    = 1,
+	[NLA_FLAG]      = 0,
+};
+
+static int
+validate_nla (const struct nlattr *nla, int maxtype,
+              const struct nla_policy *policy)
+{
+	const struct nla_policy *pt;
+	unsigned int minlen = 0;
+	int type = nla_type(nla);
+
+	if (type < 0 || type > maxtype)
+		return 0;
+
+	pt = &policy[type];
+
+	if (pt->type > NLA_TYPE_MAX)
+		g_return_val_if_reached (-NLE_BUG);
+
+	if (pt->minlen)
+		minlen = pt->minlen;
+	else if (pt->type != NLA_UNSPEC)
+		minlen = nla_attr_minlen[pt->type];
+
+	if (nla_len(nla) < minlen)
+		return -NLE_UNSPEC;
+
+	if (pt->maxlen && nla_len(nla) > pt->maxlen)
+		return -NLE_UNSPEC;
+
+	if (pt->type == NLA_STRING) {
+		const char *data = nla_data(nla);
+		if (data[nla_len(nla) - 1] != '\0')
+			return -NLE_UNSPEC;
+	}
+
+	return 0;
+}
+
+int
+nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+           const struct nla_policy *policy)
+{
+	struct nlattr *nla;
+	int rem, err;
+
+	memset(tb, 0, sizeof (struct nlattr *) * (maxtype + 1));
+
+	nla_for_each_attr(nla, head, len, rem) {
+		int type = nla_type(nla);
+
+		if (type > maxtype)
+			continue;
+
+		if (policy) {
+			err = validate_nla(nla, maxtype, policy);
+			if (err < 0)
+				goto errout;
+		}
+
+		tb[type] = nla;
+	}
+
+	err = 0;
+errout:
+	return err;
+}
+
+/*****************************************************************************/
+
+void nlmsg_free (struct nl_msg *msg)
+{
+	if (!msg)
+		return;
+
+	if (msg->nm_refcnt < 1)
+		g_return_if_reached ();
+
+	msg->nm_refcnt--;
+
+	if (msg->nm_refcnt <= 0) {
+		g_free (msg->nm_nlh);
+		g_slice_free (struct nl_msg, msg);
+	}
+}
+
+int
+nlmsg_get_proto (struct nl_msg *msg)
+{
+	return msg->nm_protocol;
+}
+
+void
+nlmsg_set_proto (struct nl_msg *msg, int protocol)
+{
+	msg->nm_protocol = protocol;
+}
+
+void
+nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr)
+{
+	memcpy (&msg->nm_src, addr, sizeof (*addr));
+}
+
+struct ucred *
+nlmsg_get_creds (struct nl_msg *msg)
+{
+	if (msg->nm_flags & NL_MSG_CRED_PRESENT)
+		return &msg->nm_creds;
+	return NULL;
+}
+
+void
+nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds)
+{
+	memcpy (&msg->nm_creds, creds, sizeof (*creds));
+	msg->nm_flags |= NL_MSG_CRED_PRESENT;
+}
+
+/*****************************************************************************/
+
+void *
+genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family,
+             int hdrlen, int flags, uint8_t cmd, uint8_t version)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr hdr = {
+		.cmd = cmd,
+		.version = version,
+	};
+
+	nlh = nlmsg_put (msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
+	if (nlh == NULL)
+		return NULL;
+
+	memcpy (nlmsg_data (nlh), &hdr, sizeof (hdr));
+
+	return (char *) nlmsg_data (nlh) + GENL_HDRLEN;
+}
+
+void *
+genlmsg_data (const struct genlmsghdr *gnlh)
+{
+	return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+void *
+genlmsg_user_hdr (const struct genlmsghdr *gnlh)
+{
+	return genlmsg_data (gnlh);
+}
+
+struct genlmsghdr *
+genlmsg_hdr (struct nlmsghdr *nlh)
+{
+	return nlmsg_data (nlh);
+}
+
+void *
+genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen)
+{
+	return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen);
+}
+
+struct nlattr *
+genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_user_data (gnlh, hdrlen);
+}
+
+int
+genlmsg_len (const struct genlmsghdr *gnlh)
+{
+	const struct nlmsghdr *nlh;
+
+	nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
+	return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
+}
+
+int
+genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen);
+}
+
+int
+genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen)
+{
+	struct genlmsghdr *ghdr;
+
+	if (!nlmsg_valid_hdr (nlh, GENL_HDRLEN))
+		return 0;
+
+	ghdr = nlmsg_data (nlh);
+	if (genlmsg_len (ghdr) < NLMSG_ALIGN (hdrlen))
+		return 0;
+
+	return 1;
+}
+
+int
+genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+               int maxtype, const struct nla_policy *policy)
+{
+	struct genlmsghdr *ghdr;
+
+	if (!genlmsg_valid_hdr (nlh, hdrlen))
+		return -NLE_MSG_TOOSHORT;
+
+	ghdr = nlmsg_data (nlh);
+	return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen),
+	                  genlmsg_attrlen (ghdr, hdrlen), policy);
+}
+
+static int
+_genl_parse_getfamily (struct nl_msg *msg, void *arg)
+{
+	static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
+		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
+		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
+		                            .maxlen = GENL_NAMSIZ },
+		[CTRL_ATTR_VERSION]      = { .type = NLA_U32 },
+		[CTRL_ATTR_HDRSIZE]      = { .type = NLA_U32 },
+		[CTRL_ATTR_MAXATTR]      = { .type = NLA_U32 },
+		[CTRL_ATTR_OPS]          = { .type = NLA_NESTED },
+		[CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED },
+	};
+	struct nlattr *tb[CTRL_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr (msg);
+	gint32 *response_data = arg;
+
+	if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
+		return NL_SKIP;
+
+	if (tb[CTRL_ATTR_FAMILY_ID])
+		*response_data = nla_get_u16 (tb[CTRL_ATTR_FAMILY_ID]);
+
+	return NL_STOP;
+}
+
+int
+genl_ctrl_resolve (struct nl_sock *sk, const char *name)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int result = -ENOMEM;
+	gint32 response_data = -1;
+	const struct nl_cb cb = {
+		.valid_cb = _genl_parse_getfamily,
+		.valid_arg = &response_data,
+	};
+
+	msg = nlmsg_alloc ();
+
+	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
+	                  0, 0, CTRL_CMD_GETFAMILY, 1))
+		goto out;
+
+	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
+		goto out;
+
+	result = nl_send_auto (sk, msg);
+	if (result < 0)
+		goto out;
+
+	result = nl_recvmsgs (sk, &cb);
+	if (result < 0)
+		goto out;
+
+	/* If search was successful, request may be ACKed after data */
+	result = nl_wait_for_ack (sk, NULL);
+	if (result < 0)
+		goto out;
+
+	if (response_data > 0)
+		result = response_data;
+	else
+		result = -ENOENT;
+
+out:
+	return result;
+}
+
+/*****************************************************************************/
+
+struct nl_sock *
+nl_socket_alloc (void)
+{
+	struct nl_sock *sk;
+
+	sk = g_slice_new0 (struct nl_sock);
+
+	sk->s_fd = -1;
+	sk->s_local.nl_family = AF_NETLINK;
+	sk->s_peer.nl_family = AF_NETLINK;
+	sk->s_seq_expect = sk->s_seq_next = time(NULL);
+
+	return sk;
+}
+
+void
+nl_socket_free (struct nl_sock *sk)
+{
+	if (!sk)
+		return;
+
+	if (sk->s_fd >= 0)
+		nm_close (sk->s_fd);
+	g_slice_free (struct nl_sock, sk);
+}
+
+int
+nl_socket_get_fd (const struct nl_sock *sk)
+{
+	return sk->s_fd;
+}
+
+uint32_t
+nl_socket_get_local_port (const struct nl_sock *sk)
+{
+	return sk->s_local.nl_pid;
+}
+
+size_t
+nl_socket_get_msg_buf_size (struct nl_sock *sk)
+{
+	return sk->s_bufsize;
+}
+
+int
+nl_socket_set_passcred (struct nl_sock *sk, int state)
+{
+	int err;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_PASSCRED,
+	                  &state, sizeof (state));
+	if (err < 0)
+		return -nl_syserr2nlerr (errno);
+
+	if (state)
+		sk->s_flags |= NL_SOCK_PASSCRED;
+	else
+		sk->s_flags &= ~NL_SOCK_PASSCRED;
+
+	return 0;
+}
+
+int
+nl_socket_set_msg_buf_size (struct nl_sock *sk, size_t bufsize)
+{
+	sk->s_bufsize = bufsize;
+
+	return 0;
+}
+
+struct sockaddr_nl *
+nlmsg_get_dst (struct nl_msg *msg)
+{
+	return &msg->nm_dst;
+}
+
+int
+nl_socket_set_nonblocking (const struct nl_sock *sk)
+{
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
+		return -nl_syserr2nlerr (errno);
+
+	return 0;
+}
+
+int
+nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf)
+{
+	int err;
+
+	if (rxbuf <= 0)
+		rxbuf = 32768;
+
+	if (txbuf <= 0)
+		txbuf = 32768;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_SNDBUF,
+	                  &txbuf, sizeof (txbuf));
+	if (err < 0) {
+		return -nl_syserr2nlerr (errno);
+	}
+
+	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_RCVBUF,
+	                  &rxbuf, sizeof (rxbuf));
+	if (err < 0) {
+		return -nl_syserr2nlerr (errno);
+	}
+
+	return 0;
+}
+
+int
+nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
+{
+	int err;
+	va_list ap;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	va_start(ap, group);
+
+	while (group != 0) {
+		if (group < 0) {
+			va_end(ap);
+			g_return_val_if_reached (-NLE_BUG);
+		}
+
+		err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+		                  &group, sizeof (group));
+		if (err < 0) {
+			va_end(ap);
+			return -nl_syserr2nlerr (errno);
+		}
+
+		group = va_arg(ap, int);
+	}
+
+	va_end(ap);
+
+	return 0;
+}
+
+int
+nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable)
+{
+	int err, val;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	val = !!enable;
+	err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof (val));
+	if (err < 0)
+		return -nl_syserr2nlerr (errno);
+
+	return 0;
+}
+
+void nl_socket_disable_msg_peek (struct nl_sock *sk)
+{
+	sk->s_flags |= NL_MSG_PEEK_EXPLICIT;
+	sk->s_flags &= ~NL_MSG_PEEK;
+}
+
+int
+nl_connect (struct nl_sock *sk, int protocol)
+{
+	int err;
+	socklen_t addrlen;
+	struct sockaddr_nl local = { 0 };
+
+	if (sk->s_fd != -1)
+		return -NLE_BAD_SOCK;
+
+	sk->s_fd = socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+	if (sk->s_fd < 0) {
+		err = -nl_syserr2nlerr (errno);
+		goto errout;
+	}
+
+	err = nl_socket_set_buffer_size(sk, 0, 0);
+	if (err < 0)
+		goto errout;
+
+	nm_assert (sk->s_local.nl_pid == 0);
+
+	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 (local);
+	err = getsockname (sk->s_fd, (struct sockaddr *) &local,
+	                   &addrlen);
+	if (err < 0) {
+		err = -nl_syserr2nlerr (errno);
+		goto errout;
+	}
+
+	if (addrlen != sizeof (local)) {
+		err = -NLE_UNSPEC;
+		goto errout;
+	}
+
+	if (local.nl_family != AF_NETLINK) {
+		err = -NLE_UNSPEC;
+		goto errout;
+	}
+
+	sk->s_local = local;
+	sk->s_proto = protocol;
+
+	return 0;
+
+errout:
+	if (sk->s_fd != -1) {
+		close(sk->s_fd);
+		sk->s_fd = -1;
+	}
+	return err;
+}
+
+/*****************************************************************************/
+
+static void
+_cb_init (struct nl_cb *dst, const struct nl_cb *src)
+{
+	nm_assert (dst);
+
+	if (src)
+		*dst = *src;
+	else
+		memset (dst, 0, sizeof (*dst));
+}
+
+static int ack_wait_handler(struct nl_msg *msg, void *arg)
+{
+	return NL_STOP;
+}
+
+int
+nl_wait_for_ack (struct nl_sock *sk,
+                 const struct nl_cb *cb)
+{
+	struct nl_cb cb2;
+
+	_cb_init (&cb2, cb);
+	cb2.ack_cb = ack_wait_handler;
+	return nl_recvmsgs (sk, &cb2);
+}
+
+#define NL_CB_CALL(cb, type, msg) \
+do { \
+	const struct nl_cb *_cb = (cb); \
+	\
+	if (_cb->type##_cb) { \
+		err = _cb->type##_cb ((msg), _cb->type##_arg); \
+		switch (err) { \
+		case NL_OK: \
+			err = 0; \
+			break; \
+		case NL_SKIP: \
+			goto skip; \
+		case NL_STOP: \
+			goto stop; \
+		default: \
+			goto out; \
+		} \
+	} \
+} while (0)
+
+int
+nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb)
+{
+	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	gs_free unsigned char *buf = NULL;
+	struct nlmsghdr *hdr;
+	struct sockaddr_nl nla = { 0 };
+	gs_free struct ucred *creds = NULL;
+
+continue_reading:
+	n = nl_recv (sk, &nla, &buf, &creds);
+	if (n <= 0)
+		return n;
+
+	hdr = (struct nlmsghdr *) buf;
+	while (nlmsg_ok (hdr, n)) {
+		nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+		msg = nlmsg_alloc_convert (hdr);
+
+		nlmsg_set_proto (msg, sk->s_proto);
+		nlmsg_set_src (msg, &nla);
+		if (creds)
+			nlmsg_set_creds (msg, creds);
+
+		nrecv++;
+
+		/* Only do sequence checking if auto-ack mode is enabled */
+		if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+			if (hdr->nlmsg_seq != sk->s_seq_expect) {
+				err = -NLE_SEQ_MISMATCH;
+				goto out;
+			}
+		}
+
+		if (hdr->nlmsg_type == NLMSG_DONE ||
+		    hdr->nlmsg_type == NLMSG_ERROR ||
+		    hdr->nlmsg_type == NLMSG_NOOP ||
+		    hdr->nlmsg_type == NLMSG_OVERRUN) {
+			/* We can't check for !NLM_F_MULTI since some netlink
+			 * users in the kernel are broken. */
+			sk->s_seq_expect++;
+		}
+
+		if (hdr->nlmsg_flags & NLM_F_MULTI)
+			multipart = 1;
+
+		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+			/*
+			 * We have to continue reading to clear
+			 * all messages until a NLMSG_DONE is
+			 * received and report the inconsistency.
+			 */
+			interrupted = 1;
+		}
+
+		/* messages terminates a multipart message, this is
+		 * usually the end of a message and therefore we slip
+		 * out of the loop by default. the user may overrule
+		 * this action by skipping this packet. */
+		if (hdr->nlmsg_type == NLMSG_DONE) {
+			multipart = 0;
+			NL_CB_CALL(cb, finish, msg);
+		}
+
+		/* Message to be ignored, the default action is to
+		 * skip this message if no callback is specified. The
+		 * user may overrule this action by returning
+		 * NL_PROCEED. */
+		else if (hdr->nlmsg_type == NLMSG_NOOP)
+			goto skip;
+
+		/* Data got lost, report back to user. The default action is to
+		 * quit parsing. The user may overrule this action by retuning
+		 * NL_SKIP or NL_PROCEED (dangerous) */
+		else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+			err = -NLE_MSG_OVERFLOW;
+			goto out;
+		}
+
+		/* Message carries a nlmsgerr */
+		else if (hdr->nlmsg_type == NLMSG_ERROR) {
+			struct nlmsgerr *e = nlmsg_data(hdr);
+
+			if (hdr->nlmsg_len < nlmsg_size(sizeof (*e))) {
+				/* Truncated error message, the default action
+				 * is to stop parsing. The user may overrule
+				 * this action by returning NL_SKIP or
+				 * NL_PROCEED (dangerous) */
+				err = -NLE_MSG_TRUNC;
+				goto out;
+			}
+			if (e->error) {
+				/* Error message reported back from kernel. */
+				if (cb->err_cb) {
+					err = cb->err_cb (&nla, e,
+					                  cb->err_arg);
+					if (err < 0)
+						goto out;
+					else if (err == NL_SKIP)
+						goto skip;
+					else if (err == NL_STOP) {
+						err = -e->error;
+						goto out;
+					}
+				} else {
+					err = -e->error;
+					goto out;
+				}
+			} else
+				NL_CB_CALL(cb, ack, msg);
+		} else {
+			/* Valid message (not checking for MULTIPART bit to
+			 * get along with broken kernels. NL_SKIP has no
+			 * effect on this.  */
+			NL_CB_CALL(cb, valid, msg);
+		}
+skip:
+		err = 0;
+		hdr = nlmsg_next(hdr, &n);
+	}
+
+	if (multipart) {
+		/* Multipart message not yet complete, continue reading */
+		nm_clear_g_free (&creds);
+		nm_clear_g_free (&buf);
+
+		goto continue_reading;
+	}
+
+stop:
+	err = 0;
+
+out:
+	if (interrupted)
+		err = -NLE_DUMP_INTR;
+
+	return err ?: nrecv;
+}
+
+int
+nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
+{
+	int ret;
+
+	if (sk->s_fd < 0)
+		return -NLE_BAD_SOCK;
+
+	nlmsg_set_src (msg, &sk->s_local);
+
+	ret = sendmsg(sk->s_fd, hdr, 0);
+	if (ret < 0)
+		return -nl_syserr2nlerr (errno);
+
+	return ret;
+}
+
+int
+nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
+{
+	struct sockaddr_nl *dst;
+	struct ucred *creds;
+	struct msghdr hdr = {
+		.msg_name = (void *) &sk->s_peer,
+		.msg_namelen = sizeof (struct sockaddr_nl),
+		.msg_iov = iov,
+		.msg_iovlen = iovlen,
+	};
+	char buf[CMSG_SPACE(sizeof (struct ucred))];
+
+	/* Overwrite destination if specified in the message itself, defaults
+	 * to the peer address of the socket.
+	 */
+	dst = nlmsg_get_dst(msg);
+	if (dst->nl_family == AF_NETLINK)
+		hdr.msg_name = dst;
+
+	/* Add credentials if present. */
+	creds = nlmsg_get_creds(msg);
+	if (creds != NULL) {
+		struct cmsghdr *cmsg;
+
+		hdr.msg_control = buf;
+		hdr.msg_controllen = sizeof (buf);
+
+		cmsg = CMSG_FIRSTHDR(&hdr);
+		cmsg->cmsg_level = SOL_SOCKET;
+		cmsg->cmsg_type = SCM_CREDENTIALS;
+		cmsg->cmsg_len = CMSG_LEN(sizeof (struct ucred));
+		memcpy(CMSG_DATA(cmsg), creds, sizeof (struct ucred));
+	}
+
+	return nl_sendmsg(sk, msg, &hdr);
+}
+
+void
+nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nlmsg_hdr(msg);
+	if (nlh->nlmsg_pid == NL_AUTO_PORT)
+		nlh->nlmsg_pid = nl_socket_get_local_port(sk);
+
+	if (nlh->nlmsg_seq == NL_AUTO_SEQ)
+		nlh->nlmsg_seq = sk->s_seq_next++;
+
+	if (msg->nm_protocol == -1)
+		msg->nm_protocol = sk->s_proto;
+
+	nlh->nlmsg_flags |= NLM_F_REQUEST;
+
+	if (!(sk->s_flags & NL_NO_AUTO_ACK))
+		nlh->nlmsg_flags |= NLM_F_ACK;
+}
+
+int
+nl_send (struct nl_sock *sk, struct nl_msg *msg)
+{
+	struct iovec iov = {
+		.iov_base = (void *) nlmsg_hdr(msg),
+		.iov_len = nlmsg_hdr(msg)->nlmsg_len,
+	};
+
+	return nl_send_iovec(sk, msg, &iov, 1);
+}
+
+int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
+{
+	nl_complete_msg(sk, msg);
+
+	return nl_send(sk, msg);
+}
+
+int
+nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
+         unsigned char **buf, struct ucred **creds)
+{
+	ssize_t n;
+	int flags = 0;
+	static int page_size = 0;
+	struct iovec iov;
+	struct msghdr msg = {
+		.msg_name = (void *) nla,
+		.msg_namelen = sizeof (struct sockaddr_nl),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	gs_free struct ucred* tmpcreds = NULL;
+	int retval;
+
+	nm_assert (nla);
+	nm_assert (buf && !*buf);
+	nm_assert (!creds || !*creds);
+
+	if (   (sk->s_flags & NL_MSG_PEEK)
+	    || (   !(sk->s_flags & NL_MSG_PEEK_EXPLICIT)
+	        && sk->s_bufsize == 0))
+		flags |= MSG_PEEK | MSG_TRUNC;
+
+	if (page_size == 0)
+		page_size = getpagesize() * 4;
+
+	iov.iov_len = sk->s_bufsize ? : page_size;
+	iov.iov_base = g_malloc (iov.iov_len);
+
+	if (   creds
+	    && (sk->s_flags & NL_SOCK_PASSCRED)) {
+		msg.msg_controllen = CMSG_SPACE (sizeof (struct ucred));
+		msg.msg_control = g_malloc (msg.msg_controllen);
+	}
+
+retry:
+	n = recvmsg(sk->s_fd, &msg, flags);
+	if (!n) {
+		retval = 0;
+		goto abort;
+	}
+
+	if (n < 0) {
+		if (errno == EINTR)
+			goto retry;
+
+		retval = -nl_syserr2nlerr (errno);
+		goto abort;
+	}
+
+	if (msg.msg_flags & MSG_CTRUNC) {
+		if (msg.msg_controllen == 0) {
+			retval = -NLE_MSG_TRUNC;
+			goto abort;
+		}
+
+		msg.msg_controllen *= 2;
+		msg.msg_control = g_realloc (msg.msg_control, msg.msg_controllen);
+		goto retry;
+	}
+
+	if (   iov.iov_len < n
+	    || (msg.msg_flags & MSG_TRUNC)) {
+		/* respond with error to an incomplete message */
+		if (flags == 0) {
+			retval = -NLE_MSG_TRUNC;
+			goto abort;
+		}
+
+		/* Provided buffer is not long enough, enlarge it
+		 * to size of n (which should be total length of the message)
+		 * and try again. */
+		iov.iov_base = g_realloc (iov.iov_base, n);
+		iov.iov_len = n;
+		flags = 0;
+		goto retry;
+	}
+
+	if (flags != 0) {
+		/* Buffer is big enough, do the actual reading */
+		flags = 0;
+		goto retry;
+	}
+
+	if (msg.msg_namelen != sizeof (struct sockaddr_nl)) {
+		retval =  -NLE_UNSPEC;
+		goto abort;
+	}
+
+	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+		struct cmsghdr *cmsg;
+
+		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+			if (cmsg->cmsg_level != SOL_SOCKET)
+				continue;
+			if (cmsg->cmsg_type != SCM_CREDENTIALS)
+				continue;
+			tmpcreds = g_memdup (CMSG_DATA(cmsg), sizeof (*tmpcreds));
+			break;
+		}
+	}
+
+	retval = n;
+
+abort:
+	g_free (msg.msg_control);
+
+	if (retval <= 0) {
+		g_free (iov.iov_base);
+		return retval;
+	}
+
+	*buf = iov.iov_base;
+	NM_SET_OUT (creds, g_steal_pointer (&tmpcreds));
+	return retval;
+}