summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/nm-fake-platform.c1
-rw-r--r--src/platform/nm-linux-platform.c779
-rw-r--r--src/platform/nm-netlink.c1491
-rw-r--r--src/platform/nm-netlink.h511
-rw-r--r--src/platform/nm-platform-utils.c3
-rw-r--r--src/platform/nm-platform.c644
-rw-r--r--src/platform/nm-platform.h117
-rw-r--r--src/platform/nmp-netns.c4
-rw-r--r--src/platform/nmp-object.c94
-rw-r--r--src/platform/nmp-object.h31
-rw-r--r--src/platform/tests/meson.build37
-rw-r--r--src/platform/tests/monitor.c3
-rw-r--r--src/platform/tests/test-address.c5
-rw-r--r--src/platform/tests/test-cleanup.c3
-rw-r--r--src/platform/tests/test-common.c116
-rw-r--r--src/platform/tests/test-common.h30
-rw-r--r--src/platform/tests/test-link.c135
-rw-r--r--src/platform/tests/test-nmp-object.c27
-rw-r--r--src/platform/tests/test-route.c3
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c372
-rw-r--r--src/platform/wifi/wifi-utils-private.h14
-rw-r--r--src/platform/wifi/wifi-utils-wext.c42
-rw-r--r--src/platform/wifi/wifi-utils.c61
-rw-r--r--src/platform/wifi/wifi-utils.h2
24 files changed, 1036 insertions, 3489 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 06dd7e13..be430152 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -938,6 +938,7 @@ wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 static void
 wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running)
 {
+	;
 }
 
 static guint32
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 0ed8fa06..e5961c7e 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -21,7 +21,6 @@
 
 #include "nm-linux-platform.h"
 
-#include <poll.h>
 #include <endian.h>
 #include <errno.h>
 #include <unistd.h>
@@ -37,14 +36,14 @@
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
-#include <linux/ip6_tunnel.h>
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
 #include <libudev.h>
 
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
-#include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
 #include "nmp-netns.h"
@@ -80,6 +79,10 @@ enum {
 
 #define VLAN_FLAG_MVRP 0x8
 
+/* nm-internal error codes for libnl. Make sure they don't overlap. */
+#define _NLE_NM_NOBUFS 500
+#define _NLE_MSG_TRUNC 501
+
 /*****************************************************************************/
 
 #define IFQDISCSIZ                      32
@@ -124,18 +127,6 @@ enum {
 #define IFLA_IPTUN_MAX                  (__IFLA_IPTUN_MAX - 1)
 #endif
 
-#define IFLA_TUN_UNSPEC                 0
-#define IFLA_TUN_OWNER                  1
-#define IFLA_TUN_GROUP                  2
-#define IFLA_TUN_TYPE                   3
-#define IFLA_TUN_PI                     4
-#define IFLA_TUN_VNET_HDR               5
-#define IFLA_TUN_PERSIST                6
-#define IFLA_TUN_MULTI_QUEUE            7
-#define IFLA_TUN_NUM_QUEUES             8
-#define IFLA_TUN_NUM_DISABLED_QUEUES    9
-#define __IFLA_TUN_MAX                  10
-#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1)
 
 static const gboolean RTA_PREF_SUPPORTED_AT_COMPILETIME = (RTA_MAX >= 20 /* RTA_PREF */);
 
@@ -292,7 +283,7 @@ typedef enum {
 
 #define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \
 	for ((iflags) = (DelayedActionType) 0x1LL; (iflags) <= DELAYED_ACTION_TYPE_MAX; (iflags) <<= 1) \
-		if (NM_FLAGS_ANY (flags_all, iflags))
+		if (NM_FLAGS_HAS (flags_all, iflags))
 
 typedef enum {
 	/* Negative values are errors from kernel. Add dummy member to
@@ -306,9 +297,13 @@ typedef enum {
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL,
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT,
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING,
-	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS,
 } WaitForNlResponseResult;
 
+typedef void (*WaitForNlResponseCallback) (NMPlatform *platform,
+                                           guint32 seq_number,
+                                           WaitForNlResponseResult seq_result,
+                                           gpointer user_data);
+
 static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
 static void do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name);
@@ -333,9 +328,7 @@ wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result)
 }
 
 static const char *
-wait_for_nl_response_to_string (WaitForNlResponseResult seq_result,
-                                const char *errmsg,
-                                char *buf, gsize buf_size)
+wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size)
 {
 	char *buf0 = buf;
 
@@ -350,13 +343,8 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result,
 		nm_utils_strbuf_append_str (&buf, &buf_size, "failure");
 		break;
 	default:
-		if (seq_result < 0) {
-			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s%s%s)",
-			                        -((int) seq_result),
-			                        g_strerror (-((int) seq_result)),
-			                        errmsg ? " - " : "",
-			                        errmsg ?: "");
-		}
+		if (seq_result < 0)
+			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s)", -((int) seq_result), g_strerror (-((int) seq_result)));
 		else
 			nm_utils_strbuf_append (&buf, &buf_size, "internal failure %d", (int) seq_result);
 		break;
@@ -550,7 +538,8 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
 	{ NM_LINK_TYPE_PPP,           "ppp",         NULL,          "ppp" },
 	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         NULL },
-	{ NM_LINK_TYPE_TUN,           "tun",         "tun",         NULL },
+	{ NM_LINK_TYPE_TAP,           "tap",         NULL,          NULL },
+	{ NM_LINK_TYPE_TUN,           "tun",         NULL,          NULL },
 	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
 	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
 	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
@@ -811,25 +800,36 @@ _linktype_get_type (NMPlatform *platform,
 		    && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
 		    && nm_streq (ifname, obj->link.name)
 		    && (   !kind
-		        || nm_streq0 (kind, obj->link.kind))) {
+		        || !g_strcmp0 (kind, obj->link.kind))) {
 			nm_assert (obj->link.kind == g_intern_string (obj->link.kind));
 			*out_kind = obj->link.kind;
 			return obj->link.type;
 		}
 	}
 
-	/* we intern kind to not require us to keep the pointer alive. Essentially
-	 * leaking it in a global cache. That should be safe enough, because the
-	 * kind comes only from kernel messages, which depend on the number of
-	 * available drivers. So, there is not the danger that we leak uncontrolled
-	 * many kinds. */
 	*out_kind = g_intern_string (kind);
 
 	if (kind) {
 		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-			if (nm_streq0 (kind, linktypes[i].rtnl_type)) {
+			if (g_strcmp0 (kind, linktypes[i].rtnl_type) == 0)
 				return linktypes[i].nm_type;
+		}
+
+		if (!strcmp (kind, "tun")) {
+			NMPlatformTunProperties props;
+
+			if (   platform
+			    && nm_platform_link_tun_get_properties (platform, ifindex, &props)) {
+				if (!g_strcmp0 (props.mode, "tap"))
+					return NM_LINK_TYPE_TAP;
+				if (!g_strcmp0 (props.mode, "tun"))
+					return NM_LINK_TYPE_TUN;
 			}
+
+			/* try guessing the type using the link flags instead... */
+			if (flags & IFF_POINTOPOINT)
+				return NM_LINK_TYPE_TUN;
+			return NM_LINK_TYPE_TAP;
 		}
 	}
 
@@ -951,6 +951,137 @@ _nl_addattr_l (struct nlmsghdr *n,
 	return TRUE;
 }
 
+static void
+_nm_auto_nl_msg_cleanup (void *ptr)
+{
+	nlmsg_free (*((struct nl_msg **) ptr));
+}
+#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
+
+static 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;
+
+	nm_utils_to_string_buffer_init (&buf, &len);
+	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;
+}
+
+static int
+_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+               const struct nla_policy *policy)
+{
+	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
+}
+#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
+
+static int
+_nl_nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+                 int maxtype, const struct nla_policy *policy)
+{
+	return nlmsg_parse (nlh, hdrlen, tb, maxtype, (struct nla_policy *) policy);
+}
+#define nlmsg_parse(...) _nl_nlmsg_parse(__VA_ARGS__)
+
+static int
+_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+                      const struct nla_policy *policy)
+{
+	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
+}
+#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
+
 /******************************************************************
  * NMPObject/netlink functions
  ******************************************************************/
@@ -1161,7 +1292,6 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 		[IFLA_IPTUN_ENCAP_LIMIT] = { .type = NLA_U8 },
 		[IFLA_IPTUN_FLOWINFO]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_PROTO]       = { .type = NLA_U8 },
-		[IFLA_IPTUN_FLAGS]       = { .type = NLA_U32 },
 	};
 	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
 	int err;
@@ -1196,8 +1326,6 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 	}
 	if (tb[IFLA_IPTUN_PROTO])
 		props->proto = nla_get_u8 (tb[IFLA_IPTUN_PROTO]);
-	if (tb[IFLA_IPTUN_FLAGS])
-		props->flags = nla_get_u32 (tb[IFLA_IPTUN_FLAGS]);
 
 	return obj;
 }
@@ -1377,60 +1505,6 @@ _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 
 /*****************************************************************************/
 
-static NMPObject *
-_parse_lnk_tun (const char *kind, struct nlattr *info_data)
-{
-	static const struct nla_policy policy[IFLA_TUN_MAX + 1] = {
-		[IFLA_TUN_OWNER]               = { .type = NLA_U32 },
-		[IFLA_TUN_GROUP]               = { .type = NLA_U32 },
-		[IFLA_TUN_TYPE]                = { .type = NLA_U8 },
-		[IFLA_TUN_PI]                  = { .type = NLA_U8 },
-		[IFLA_TUN_VNET_HDR]            = { .type = NLA_U8 },
-		[IFLA_TUN_PERSIST]             = { .type = NLA_U8 },
-		[IFLA_TUN_MULTI_QUEUE]         = { .type = NLA_U8 },
-		[IFLA_TUN_NUM_QUEUES]          = { .type = NLA_U32 },
-		[IFLA_TUN_NUM_DISABLED_QUEUES] = { .type = NLA_U32 },
-	};
-	struct nlattr *tb[IFLA_TUN_MAX + 1];
-	int err;
-	NMPObject *obj;
-	NMPlatformLnkTun *props;
-
-	if (!info_data || !nm_streq0 (kind, "tun"))
-		return NULL;
-
-	err = nla_parse_nested (tb, IFLA_TUN_MAX, info_data, policy);
-	if (err < 0)
-		return NULL;
-
-	if (!tb[IFLA_TUN_TYPE]) {
-		/* we require at least a type. */
-		return NULL;
-	}
-
-	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_TUN, NULL);
-	props = &obj->lnk_tun;
-
-	props->type = nla_get_u8 (tb[IFLA_TUN_TYPE]);
-
-	props->pi = !!nla_get_u8_cond (tb, IFLA_TUN_PI, FALSE);
-	props->vnet_hdr = !!nla_get_u8_cond (tb, IFLA_TUN_VNET_HDR, FALSE);
-	props->multi_queue = !!nla_get_u8_cond (tb, IFLA_TUN_MULTI_QUEUE, FALSE);
-	props->persist = !!nla_get_u8_cond (tb, IFLA_TUN_PERSIST, FALSE);
-
-	if (tb[IFLA_TUN_OWNER]) {
-		props->owner_valid = TRUE;
-		props->owner = nla_get_u32 (tb[IFLA_TUN_OWNER]);
-	}
-	if (tb[IFLA_TUN_GROUP]) {
-		props->group_valid = TRUE;
-		props->group = nla_get_u32 (tb[IFLA_TUN_GROUP]);
-	}
-	return obj;
-}
-
-/*****************************************************************************/
-
 static gboolean
 _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
                             const NMVlanQosMapping **out_map,
@@ -1862,9 +1936,6 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_SIT:
 		lnk_data = _parse_lnk_sit (nl_info_kind, nl_info_data);
 		break;
-	case NM_LINK_TYPE_TUN:
-		lnk_data = _parse_lnk_tun (nl_info_kind, nl_info_data);
-		break;
 	case NM_LINK_TYPE_VLAN:
 		lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data);
 		break;
@@ -2594,7 +2665,8 @@ _nl_msg_new_link (int nlmsg_type,
 
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK));
 
-	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!(msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags)))
+		g_return_val_if_reached (NULL);
 
 	if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2636,6 +2708,8 @@ _nl_msg_new_address (int nlmsg_type,
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWADDR, RTM_DELADDR));
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		g_return_val_if_reached (NULL);
 
 	if (scope == -1) {
 		/* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */
@@ -2748,6 +2822,8 @@ _nl_msg_new_route (int nlmsg_type,
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
 
 	msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags);
+	if (!msg)
+		g_return_val_if_reached (NULL);
 
 	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2845,6 +2921,8 @@ _nl_msg_new_qdisc (int nlmsg_type,
 	};
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		return NULL;
 
 	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2919,6 +2997,8 @@ _nl_msg_new_tfilter (int nlmsg_type,
 	};
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		return NULL;
 
 	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2960,7 +3040,6 @@ typedef struct {
 	DelayedActionWaitForNlResponseType response_type;
 	gint64 timeout_abs_ns;
 	WaitForNlResponseResult *out_seq_result;
-	char **out_errmsg;
 	union {
 		gint *out_refresh_all_in_progess;
 		NMPObject **out_route_get;
@@ -3421,7 +3500,7 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data
 			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
 			                        (int) data->response_type,
 			                        data->seq_result ? ", " : "",
-			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, NULL, b, sizeof (b)) : "");
+			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : "");
 		} else
 			nm_utils_strbuf_append_str (&buf, &buf_size, " (any)");
 		break;
@@ -3503,58 +3582,27 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 }
 
 static void
-delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform,
-                                                    WaitForNlResponseResult force_result,
-                                                    guint32 *out_next_seq_number,
-                                                    gint64 *out_next_timeout_abs_ns,
-                                                    gint64 *p_now_ns)
+delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
+                                                  WaitForNlResponseResult fallback_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	guint i;
-	guint32 next_seq_number = 0;
-	gint64 next_timeout_abs_ns = 0;
-	gint now_ns = 0;
-
-	for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
-		const DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
-
-		if (data->seq_result)
-			delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
-		else if (   p_now_ns
-		         && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) >= data->timeout_abs_ns)) {
-			/* the caller can optionally check for timeout by providing a p_now_ns argument. */
-			delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
-		} else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
-			delayed_action_wait_for_nl_response_complete (platform, i, force_result);
-		else {
-			if (   next_seq_number == 0
-			    || next_timeout_abs_ns > data->timeout_abs_ns) {
-				next_seq_number = data->seq_number;
-				next_timeout_abs_ns = data->timeout_abs_ns;
-			}
-			i++;
-		}
-	}
 
-	if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) {
-		nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-		nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
-	}
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		while (priv->delayed_action.list_wait_for_nl_response->len > 0) {
+			const DelayedActionWaitForNlResponseData *data;
+			guint idx = priv->delayed_action.list_wait_for_nl_response->len - 1;
+			WaitForNlResponseResult r;
 
-	NM_SET_OUT (out_next_seq_number, next_seq_number);
-	NM_SET_OUT (out_next_timeout_abs_ns, next_timeout_abs_ns);
-	NM_SET_OUT (p_now_ns, now_ns);
-}
+			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, idx);
 
-static void
-delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
-                                                  WaitForNlResponseResult fallback_result)
-{
-	delayed_action_wait_for_nl_response_complete_check (platform,
-	                                                    fallback_result,
-	                                                    NULL,
-	                                                    NULL,
-	                                                    NULL);
+			/* prefer the result that we already have. */
+			r = data->seq_result ? : fallback_result;
+
+			delayed_action_wait_for_nl_response_complete (platform, idx, r);
+		}
+	}
+	nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+	nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
 }
 
 /*****************************************************************************/
@@ -3737,7 +3785,6 @@ static void
 delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
                                               guint32 seq_number,
                                               WaitForNlResponseResult *out_seq_result,
-                                              char **out_errmsg,
                                               DelayedActionWaitForNlResponseType response_type,
                                               gpointer response_out_data)
 {
@@ -3745,7 +3792,6 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 		.seq_number = seq_number,
 		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
 		.out_seq_result = out_seq_result,
-		.out_errmsg = out_errmsg,
 		.response_type = response_type,
 		.response.out_data = response_out_data,
 	};
@@ -3930,46 +3976,40 @@ cache_on_change (NMPlatform *platform,
 		    && (obj_new && obj_new->_link.netlink.is_in_netlink)
 		    && (!obj_old || !obj_old->_link.netlink.is_in_netlink))
 		{
-			gboolean re_request_link = FALSE;
-			const NMPlatformLnkTun *lnk_tun;
-
-			if (   !obj_new->_link.netlink.lnk
-			    && NM_IN_SET (obj_new->link.type, NM_LINK_TYPE_GRE,
-			                                      NM_LINK_TYPE_IP6TNL,
-			                                      NM_LINK_TYPE_INFINIBAND,
-			                                      NM_LINK_TYPE_MACVLAN,
-			                                      NM_LINK_TYPE_MACVLAN,
-			                                      NM_LINK_TYPE_SIT,
-			                                      NM_LINK_TYPE_TUN,
-			                                      NM_LINK_TYPE_VLAN,
-			                                      NM_LINK_TYPE_VXLAN)) {
+			if (!obj_new->_link.netlink.lnk) {
 				/* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that
 				 * kernel didn't send this notification, thus when we first learn about a link
 				 * that lacks an lnk_data we re-request it again.
 				 *
 				 * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */
-				re_request_link = TRUE;
-			} else if (   obj_new->link.type == NM_LINK_TYPE_TUN
-			           && obj_new->_link.netlink.lnk
-			           && (lnk_tun = &(obj_new->_link.netlink.lnk)->lnk_tun)
-			           && !lnk_tun->persist
-			           && lnk_tun->pi
-			           && !lnk_tun->vnet_hdr
-			           && !lnk_tun->multi_queue
-			           && !lnk_tun->owner_valid
-			           && !lnk_tun->group_valid) {
-				/* kernel has/had a know issue that the first notification for TUN device would
-				 * be sent with invalid parameters. The message looks like that kind, so refetch
-				 * it. */
-				re_request_link = TRUE;
-			} else if (   obj_new->link.type == NM_LINK_TYPE_VETH
-			           && obj_new->link.parent == 0) {
+				switch (obj_new->link.type) {
+				case NM_LINK_TYPE_GRE:
+				case NM_LINK_TYPE_IP6TNL:
+				case NM_LINK_TYPE_INFINIBAND:
+				case NM_LINK_TYPE_MACVLAN:
+				case NM_LINK_TYPE_MACVTAP:
+				case NM_LINK_TYPE_SIT:
+				case NM_LINK_TYPE_VLAN:
+				case NM_LINK_TYPE_VXLAN:
+					delayed_action_schedule (platform,
+					                         DELAYED_ACTION_TYPE_REFRESH_LINK,
+					                         GINT_TO_POINTER (obj_new->link.ifindex));
+					break;
+				default:
+					break;
+				}
+			}
+			if (   obj_new->link.type == NM_LINK_TYPE_VETH
+			    && obj_new->link.parent == 0) {
 				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
 				 * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827).
 				 * Request it again. */
-				re_request_link = TRUE;
-			} else if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
-			           && obj_new->link.addr.len == 0) {
+				delayed_action_schedule (platform,
+				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
+				                         GINT_TO_POINTER (obj_new->link.ifindex));
+			}
+			if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
+			    && obj_new->link.addr.len == 0) {
 				/* Due to a kernel bug, we sometimes receive spurious NEWLINK
 				 * messages after a wifi interface has disappeared. Since the
 				 * link is not present anymore we can't determine its type and
@@ -3977,9 +4017,6 @@ cache_on_change (NMPlatform *platform,
 				 * specified.  Request the link again to check if it really
 				 * exists.  https://bugzilla.redhat.com/show_bug.cgi?id=1302037
 				 */
-				re_request_link = TRUE;
-			}
-			if (re_request_link) {
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
 				                         GINT_TO_POINTER (obj_new->link.ifindex));
@@ -4030,10 +4067,8 @@ cache_on_change (NMPlatform *platform,
 static guint32
 _nlh_seq_next_get (NMLinuxPlatformPrivate *priv)
 {
-	/* generate a new sequence number, but never return zero.
-	 * Wrapping numbers are not a problem, because we don't rely
-	 * on strictly increasing sequence numbers. */
-	return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next);
+	/* generate a new sequence number, but skip zero. */
+	return priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
 }
 
 /**
@@ -4044,13 +4079,12 @@ _nlh_seq_next_get (NMLinuxPlatformPrivate *priv)
  * @response_type:
  * @response_out_data:
  *
- * Returns: 0 on success or a negative errno.
+ * Returns: 0 on success or a negative errno. Beware, it's an errno, not nlerror.
  */
 static int
 _nl_send_nlmsghdr (NMPlatform *platform,
                    struct nlmsghdr *nlhdr,
                    WaitForNlResponseResult *out_seq_result,
-                   char **out_errmsg,
                    DelayedActionWaitForNlResponseType response_type,
                    gpointer response_out_data)
 {
@@ -4095,7 +4129,7 @@ again:
 		}
 	}
 
-	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_errmsg,
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
 	                                              response_type, response_out_data);
 	return 0;
 }
@@ -4114,7 +4148,6 @@ static int
 _nl_send_nlmsg (NMPlatform *platform,
                 struct nl_msg *nlmsg,
                 WaitForNlResponseResult *out_seq_result,
-                char **out_errmsg,
                 DelayedActionWaitForNlResponseType response_type,
                 gpointer response_out_data)
 {
@@ -4133,7 +4166,7 @@ _nl_send_nlmsg (NMPlatform *platform,
 		return nle;
 	}
 
-	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_errmsg,
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
 	                                              response_type, response_out_data);
 	return 0;
 }
@@ -4171,7 +4204,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 	                          0,
 	                          0);
 	if (nlmsg) {
-		nle = _nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+		nle = _nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 		if (nle < 0) {
 			_LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
 			       ifindex, name ?: "",
@@ -4230,6 +4263,8 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		 * because we need the sequence number.
 		 */
 		nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+		if (!nlmsg)
+			continue;
 
 		if (   klass->obj_type == NMP_OBJECT_TYPE_QDISC
 		    || klass->obj_type == NMP_OBJECT_TYPE_TFILTER) {
@@ -4246,7 +4281,7 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (nle < 0)
 			continue;
 
-		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
+		if (_nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
 			nm_assert (*out_refresh_all_in_progess > 0);
 			*out_refresh_all_in_progess -= 1;
 		}
@@ -4290,7 +4325,7 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number)
 }
 
 static void
-event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult seq_result, const char *msg)
+event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult seq_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	DelayedActionWaitForNlResponseData *data;
@@ -4309,13 +4344,11 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu
 				/* We potentially receive many parts partial responses for the same sequence number.
 				 * Thus, we only remember the result, and collect it later. */
 				if (data->seq_result < 0) {
-					/* we already saw an error for this sequence number.
+					/* we already saw an error for this seqence number.
 					 * Preserve it. */
 				} else if (   seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
 				           || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
 					data->seq_result = seq_result;
-				if (data->out_errmsg && !*data->out_errmsg)
-					*data->out_errmsg = g_strdup (msg);
 				return;
 			}
 		}
@@ -4358,7 +4391,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	obj = nmp_object_new_from_nl (platform, cache, msg, id_only);
 	if (!obj) {
 		_LOGT ("event-notification: %s: ignore",
-		       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
+		       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 		return;
 	}
 
@@ -4376,7 +4409,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	}
 
 	_LOGT ("event-notification: %s%s: %s",
-	       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
+	       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
 	       is_dump ? ", in-dump" : "",
 	       nmp_object_to_string (obj,
 	                             id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
@@ -4514,14 +4547,13 @@ do_add_link_with_lookup (NMPlatform *platform,
 {
 	const NMPObject *obj = NULL;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	NMPCache *cache = nm_platform_get_cache (platform);
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
 		       name,
@@ -4541,7 +4573,7 @@ do_add_link_with_lookup (NMPlatform *platform,
 	        "do-add-link[%s/%s]: %s",
 	        name,
 	        nm_link_type_to_string (link_type),
-	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
 	if (out_link) {
 		obj = nmp_cache_lookup_link_full (cache, 0, name, FALSE, link_type, NULL, NULL);
@@ -4558,7 +4590,6 @@ do_add_addrroute (NMPlatform *platform,
                   gboolean suppress_netlink_failure)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 
@@ -4568,7 +4599,7 @@ do_add_addrroute (NMPlatform *platform,
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
@@ -4589,7 +4620,7 @@ do_add_addrroute (NMPlatform *platform,
 	        "do-add-%s[%s]: %s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
 	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
 		/* In rare cases, the object is not yet ready as we received the ACK from
@@ -4610,7 +4641,6 @@ static gboolean
 do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	gboolean success;
@@ -4618,7 +4648,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
@@ -4650,7 +4680,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	        "do-delete-%s[%s]: %s%s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)),
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
 
 	if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
@@ -4681,7 +4711,6 @@ do_change_link (NMPlatform *platform,
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int nle;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-	gs_free char *errmsg = NULL;
 	char s_buf[256];
 	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
 	NMLogLevel log_level = LOGL_DEBUG;
@@ -4697,7 +4726,7 @@ do_change_link (NMPlatform *platform,
 	}
 
 retry:
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		log_level = LOGL_ERR;
 		log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)",
@@ -4754,7 +4783,7 @@ out:
 	        "do-change-link[%d]: %s changing link: %s%s",
 	        ifindex,
 	        log_result,
-	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)),
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
 	return result;
 }
@@ -4831,12 +4860,6 @@ link_refresh (NMPlatform *platform, int ifindex)
 	return !!nm_platform_link_get_obj (platform, ifindex, TRUE);
 }
 
-static void
-refresh_all (NMPlatform *platform, NMPObjectType obj_type)
-{
-	do_request_one_type (platform, obj_type);
-}
-
 static gboolean
 link_set_netns (NMPlatform *platform,
                 int ifindex,
@@ -5369,7 +5392,6 @@ link_ip6tnl_add (NMPlatform *platform,
 	            & IP6_FLOWINFO_TCLASS_MASK;
 	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
 	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLAGS, props->flags);
 
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
@@ -5593,62 +5615,6 @@ nla_put_failure:
 }
 
 static gboolean
-link_tun_add (NMPlatform *platform,
-              const char *name,
-              const NMPlatformLnkTun *props,
-              const NMPlatformLink **out_link,
-              int *out_fd)
-{
-	const NMPObject *obj;
-	struct ifreq ifr = { };
-	nm_auto_close int fd = -1;
-
-	nm_assert (NM_IN_SET (props->type, IFF_TAP, IFF_TUN));
-	nm_assert (props->persist || out_fd);
-
-	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
-	if (fd < 0)
-		return FALSE;
-
-	nm_utils_ifname_cpy (ifr.ifr_name, name);
-	ifr.ifr_flags =   ((short) props->type)
-	                | ((short) IFF_TUN_EXCL)
-	                | (!props->pi          ? (short) IFF_NO_PI          : (short) 0)
-	                | ( props->vnet_hdr    ? (short) IFF_VNET_HDR       : (short) 0)
-	                | ( props->multi_queue ? (short) NM_IFF_MULTI_QUEUE : (short) 0);
-	if (ioctl (fd, TUNSETIFF, &ifr))
-		return FALSE;
-
-	if (props->owner_valid) {
-		if (ioctl (fd, TUNSETOWNER, (uid_t) props->owner))
-			return FALSE;
-	}
-
-	if (props->group_valid) {
-		if (ioctl (fd, TUNSETGROUP, (gid_t) props->group))
-			return FALSE;
-	}
-
-	if (props->persist) {
-		if (ioctl (fd, TUNSETPERSIST, 1))
-			return FALSE;
-	}
-
-	do_request_link (platform, 0, name);
-	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
-	                                  0, name, FALSE,
-	                                  NM_LINK_TYPE_TUN,
-	                                  NULL, NULL);
-
-	if (!obj)
-		return FALSE;
-
-	NM_SET_OUT (out_link, &obj->link);
-	NM_SET_OUT (out_fd, nm_steal_fd (&fd));
-	return TRUE;
-}
-
-static gboolean
 link_vxlan_add (NMPlatform *platform,
                 const char *name,
                 const NMPlatformLnkVxlan *props,
@@ -5888,6 +5854,64 @@ link_vlan_change (NMPlatform *platform,
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
+static int
+tun_add (NMPlatform *platform, const char *name, gboolean tap,
+         gint64 owner, gint64 group, gboolean pi, gboolean vnet_hdr,
+         gboolean multi_queue, const NMPlatformLink **out_link)
+{
+	const NMPObject *obj;
+	struct ifreq ifr = { };
+	int fd;
+
+	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
+	if (fd < 0)
+		return FALSE;
+
+	nm_utils_ifname_cpy (ifr.ifr_name, name);
+	ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN;
+
+	if (!pi)
+		ifr.ifr_flags |= IFF_NO_PI;
+	if (vnet_hdr)
+		ifr.ifr_flags |= IFF_VNET_HDR;
+	if (multi_queue)
+		ifr.ifr_flags |= NM_IFF_MULTI_QUEUE;
+
+	if (ioctl (fd, TUNSETIFF, &ifr)) {
+		nm_close (fd);
+		return FALSE;
+	}
+
+	if (owner >= 0 && owner < G_MAXINT32) {
+		if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) {
+			nm_close (fd);
+			return FALSE;
+		}
+	}
+
+	if (group >= 0 && group < G_MAXINT32) {
+		if (ioctl (fd, TUNSETGROUP, (gid_t) group)) {
+			nm_close (fd);
+			return FALSE;
+		}
+	}
+
+	if (ioctl (fd, TUNSETPERSIST, 1)) {
+		nm_close (fd);
+		return FALSE;
+	}
+	do_request_link (platform, 0, name);
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
+	                                  0, name, FALSE,
+	                                  tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN,
+	                                  NULL, NULL);
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
+
+	nm_close (fd);
+	return !!obj;
+}
+
 static gboolean
 link_enslave (NMPlatform *platform, int master, int slave)
 {
@@ -6429,7 +6453,7 @@ ip_route_get (NMPlatform *platform,
 		}
 
 		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, NULL, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
+		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
 		if (nle < 0) {
 			_LOGE ("get-route: failure sending netlink request \"%s\" (%d)",
 			       g_strerror (-nle), -nle);
@@ -6468,7 +6492,6 @@ qdisc_add (NMPlatform *platform,
            const NMPlatformQdisc *qdisc)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -6477,7 +6500,7 @@ qdisc_add (NMPlatform *platform,
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-qdisc: failed sending netlink request \"%s\" (%d)",
 		      nl_geterror (nle), -nle);
@@ -6492,7 +6515,7 @@ qdisc_add (NMPlatform *platform,
 	            ? LOGL_DEBUG
 	            : LOGL_WARN,
 	        "do-add-qdisc: %s",
-	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
 		return NM_PLATFORM_ERROR_SUCCESS;
@@ -6508,7 +6531,6 @@ tfilter_add (NMPlatform *platform,
              const NMPlatformTfilter *tfilter)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -6517,7 +6539,7 @@ tfilter_add (NMPlatform *platform,
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-tfilter: failed sending netlink request \"%s\" (%d)",
 		      nl_geterror (nle), -nle);
@@ -6532,7 +6554,7 @@ tfilter_add (NMPlatform *platform,
 	            ? LOGL_DEBUG
 	            : LOGL_WARN,
 	        "do-add-tfilter: %s",
-	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
 		return NM_PLATFORM_ERROR_SUCCESS;
@@ -6563,12 +6585,15 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	struct nl_sock *sk = priv->nlh;
-	int n;
-	int err = 0;
-	gboolean multipart = 0;
-	gboolean interrupted = FALSE;
+	int n, err = 0, multipart = 0, interrupted = 0;
 	struct nlmsghdr *hdr;
 	WaitForNlResponseResult seq_result;
+
+	/*
+	nla is passed on to not only to nl_recv() but may also be passed
+	to a function pointer provided by the caller which may or may not
+	initialize the variable. Thomas Graf.
+	*/
 	struct sockaddr_nl nla = {0};
 	nm_auto_free struct ucred *creds = NULL;
 	nm_auto_free unsigned char *buf = NULL;
@@ -6576,28 +6601,56 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 continue_reading:
 	g_clear_pointer (&buf, free);
 	g_clear_pointer (&creds, free);
+	errno = 0;
 	n = nl_recv (sk, &nla, &buf, &creds);
 
 	if (n <= 0) {
-
-		if (n == -NLE_MSG_TRUNC) {
-			int buf_size;
-
-			/* the message receive buffer was too small. We lost one message, which
-			 * is unfortunate. Try to double the buffer size for the next time. */
-			buf_size = nl_socket_get_msg_buf_size (sk);
-			if (buf_size < 512*1024) {
-				buf_size *= 2;
-				_LOGT ("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", buf_size);
-				if (nl_socket_set_msg_buf_size (sk, buf_size) < 0)
-					nm_assert_not_reached ();
-				if (!handle_events)
-					goto continue_reading;
-			}
+		/* workaround libnl3 <= 3.2.15 returning danling pointers in case nl_recv()
+		 * fails. Fixed by libnl3 69468517d0de1675d80f24661ff57a5dbac7275c. */
+		buf = NULL;
+		creds = NULL;
+	}
+
+	switch (n) {
+	case 0:
+		/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
+		if (errno == EAGAIN) {
+			/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
+			 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
+			G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
+			n = -NLE_AGAIN;
+		}
+		break;
+	case -NLE_MSG_TRUNC: {
+		int buf_size;
+
+		/* the message receive buffer was too small. We lost one message, which
+		 * is unfortunate. Try to double the buffer size for the next time. */
+		buf_size = nl_socket_get_msg_buf_size (sk);
+		if (buf_size < 512*1024) {
+			buf_size *= 2;
+			_LOGT ("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", buf_size);
+			if (nl_socket_set_msg_buf_size (sk, buf_size) < 0)
+				nm_assert_not_reached ();
+			if (!handle_events)
+				goto continue_reading;
 		}
+		n = -_NLE_MSG_TRUNC;
+		break;
+	}
+	case -NLE_NOMEM:
+		if (errno == ENOBUFS) {
+			/* we are very much interested in a overrun of the receive buffer.
+			 * nl_recv() maps all kinds of errors to NLE_NOMEM, so check also
+			 * for errno explicitly. And if so, hack our own return code to signal
+			 * the overrun. */
+			n = -_NLE_NM_NOBUFS;
+		}
+		break;
+	}
 
+	if (n <= 0)
 		return n;
-	}
 
 	hdr = (struct nlmsghdr *) buf;
 	while (nlmsg_ok (hdr, n)) {
@@ -6606,9 +6659,12 @@ continue_reading:
 		gboolean process_valid_msg = FALSE;
 		guint32 seq_number;
 		char buf_nlmsghdr[400];
-		const char *extack_msg = NULL;
 
-		msg = nlmsg_alloc_convert (hdr);
+		msg = nlmsg_convert (hdr);
+		if (!msg) {
+			err = -NLE_NOMEM;
+			goto out;
+		}
 
 		nlmsg_set_proto (msg, NETLINK_ROUTE);
 		nlmsg_set_src (msg, &nla);
@@ -6623,13 +6679,13 @@ continue_reading:
 		}
 
 		_LOGt ("netlink: recvmsg: new message %s",
-		       nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
+		       _nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 
 		if (creds)
 			nlmsg_set_creds (msg, creds);
 
 		if (hdr->nlmsg_flags & NLM_F_MULTI)
-			multipart = TRUE;
+			multipart = 1;
 
 		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
 			/*
@@ -6637,7 +6693,7 @@ continue_reading:
 			 * all messages until a NLMSG_DONE is
 			 * received and report the inconsistency.
 			 */
-			interrupted = TRUE;
+			interrupted = 1;
 		}
 
 		/* Other side wishes to see an ack for this message */
@@ -6652,7 +6708,7 @@ continue_reading:
 			 * 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. */
-			multipart = FALSE;
+			multipart = 0;
 			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		} else if (hdr->nlmsg_type == NLMSG_NOOP) {
 			/* Message to be ignored, the default action is to
@@ -6679,28 +6735,10 @@ continue_reading:
 			} else if (e->error) {
 				int errsv = e->error > 0 ? e->error : -e->error;
 
-				if (   NM_FLAGS_HAS (hdr->nlmsg_flags, NLM_F_ACK_TLVS)
-				    && hdr->nlmsg_len >= sizeof (*e) + e->msg.nlmsg_len) {
-					static const struct nla_policy policy[NLMSGERR_ATTR_MAX + 1] = {
-						[NLMSGERR_ATTR_MSG]     = { .type = NLA_STRING },
-						[NLMSGERR_ATTR_OFFS]    = { .type = NLA_U32 },
-					};
-					struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
-					struct nlattr *tlvs;
-
-					tlvs = (struct nlattr *) ((char *) e + sizeof (*e) + e->msg.nlmsg_len - NLMSG_HDRLEN);
-					if (!nla_parse (tb, NLMSGERR_ATTR_MAX, tlvs,
-					                hdr->nlmsg_len - sizeof (*e) - e->msg.nlmsg_len, policy)) {
-						if (tb[NLMSGERR_ATTR_MSG])
-							extack_msg = nla_get_string (tb[NLMSGERR_ATTR_MSG]);
-					}
-				}
-
 				/* Error message reported back from kernel. */
-				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
+				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d) for request %d",
 				       strerror (errsv),
 				       errsv,
-				       NM_PRINT_FMT_QUOTED (extack_msg, " \"", extack_msg, "\"", ""),
 				       nlmsg_hdr (msg)->nlmsg_seq);
 				seq_result = -errsv;
 			} else
@@ -6729,7 +6767,7 @@ continue_reading:
 			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		}
 
-		event_seq_check (platform, seq_number, seq_result, extack_msg);
+		event_seq_check (platform, seq_number, seq_result);
 
 		if (abort_parsing)
 			goto stop;
@@ -6749,9 +6787,9 @@ stop:
 		 * Repeat reading. */
 		goto continue_reading;
 	}
-
+out:
 	if (interrupted)
-		return -NLE_DUMP_INTR;
+		err = -NLE_DUMP_INTR;
 	return err;
 }
 
@@ -6762,50 +6800,46 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int r;
+	int r, nle;
 	struct pollfd pfd;
 	gboolean any = FALSE;
+	gint64 now_ns;
 	int timeout_ms;
+	guint i;
 	struct {
 		guint32 seq_number;
 		gint64 timeout_abs_ns;
-		gint64 now_ns;
-	} next;
+	} data_next;
 
-	if (!nm_platform_netns_push (platform, &netns)) {
-		delayed_action_wait_for_nl_response_complete_all (platform,
-		                                                  WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS);
+	if (!nm_platform_netns_push (platform, &netns))
 		return FALSE;
-	}
 
-	for (;;) {
-		for (;;) {
-			int nle;
+	while (TRUE) {
+
+		while (TRUE) {
 
 			nle = event_handler_recvmsgs (platform, TRUE);
 
 			if (nle < 0) {
 				switch (nle) {
-				case -EAGAIN:
+				case -NLE_AGAIN:
 					goto after_read;
 				case -NLE_DUMP_INTR:
 					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 					break;
-				case -NLE_MSG_TRUNC:
-				case -ENOBUFS:
+				case -_NLE_MSG_TRUNC:
+				case -_NLE_NM_NOBUFS:
 					_LOGI ("netlink: read: %s. Need to resynchronize platform cache",
 					       ({
 					            const char *_reason = "unknown";
 					            switch (nle) {
-					            case -NLE_MSG_TRUNC: _reason = "message truncated";       break;
-					            case -ENOBUFS:       _reason = "too many netlink events"; break;
+					            case -_NLE_MSG_TRUNC: _reason = "message truncated";       break;
+					            case -_NLE_NM_NOBUFS: _reason = "too many netlink events"; break;
 					            }
 					            _reason;
 					       }));
 					event_handler_recvmsgs (platform, FALSE);
-					delayed_action_wait_for_nl_response_complete_all (platform,
-					                                                  WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
-
+					delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
 					delayed_action_schedule (platform,
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
@@ -6829,23 +6863,39 @@ after_read:
 		if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
 			return any;
 
-		delayed_action_wait_for_nl_response_complete_check (platform,
-		                                                    WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN,
-		                                                    &next.seq_number,
-		                                                    &next.timeout_abs_ns,
-		                                                    &next.now_ns);
+		now_ns = 0;
+		data_next.seq_number = 0;
+		data_next.timeout_abs_ns = 0;
+
+		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
+			DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+			if (data->seq_result)
+				delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
+			else if ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) > data->timeout_abs_ns)
+				delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
+			else {
+				i++;
+
+				if (   data_next.seq_number == 0
+				    || data_next.timeout_abs_ns > data->timeout_abs_ns) {
+					data_next.seq_number = data->seq_number;
+					data_next.timeout_abs_ns = data->timeout_abs_ns;
+				}
+			}
+		}
 
 		if (   !wait_for_acks
 		    || !NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
 			return any;
 
-		nm_assert (next.seq_number);
-		nm_assert (next.now_ns > 0);
-		nm_assert (next.timeout_abs_ns > next.now_ns);
+		nm_assert (data_next.seq_number);
+		nm_assert (data_next.timeout_abs_ns > 0);
+		nm_assert (now_ns > 0);
 
-		_LOGT ("netlink: read: wait for ACK for sequence number %u...", next.seq_number);
+		_LOGT ("netlink: read: wait for ACK for sequence number %u...", data_next.seq_number);
 
-		timeout_ms = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
+		timeout_ms = (data_next.timeout_abs_ns - now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
 
 		memset (&pfd, 0, sizeof (pfd));
 		pfd.fd = nl_socket_get_fd (priv->nlh);
@@ -6856,7 +6906,6 @@ after_read:
 			/* timeout and there is nothing to read. */
 			goto after_read;
 		}
-
 		if (r < 0) {
 			int errsv = errno;
 
@@ -6998,10 +7047,11 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (self);
 
+	priv->nlh_seq_next = 1;
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
 	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
-	priv->wifi_data = g_hash_table_new_full (nm_direct_hash, NULL, NULL, (GDestroyNotify) wifi_utils_unref);
+	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
 }
 
 static void
@@ -7047,10 +7097,6 @@ constructed (GObject *_object)
 	nle = nl_socket_set_buffer_size (priv->nlh, 8*1024*1024, 0);
 	g_assert (!nle);
 
-	nle = nl_socket_set_ext_ack (priv->nlh, TRUE);
-	if (nle)
-		_LOGD ("could not enable extended acks on netlink socket");
-
 	/* explicitly set the msg buffer size and disable MSG_PEEK.
 	 * If we later encounter NLE_MSG_TRUNC, we will adjust the buffer size. */
 	nl_socket_disable_msg_peek (priv->nlh);
@@ -7130,8 +7176,7 @@ dispose (GObject *object)
 
 	_LOGD ("dispose");
 
-	delayed_action_wait_for_nl_response_complete_all (platform,
-	                                                  WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
+	delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
 
 	priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
 	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
@@ -7181,7 +7226,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
 
-	platform_class->refresh_all = refresh_all;
 	platform_class->link_refresh = link_refresh;
 
 	platform_class->link_set_netns = link_set_netns;
@@ -7220,6 +7264,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_vlan_change = link_vlan_change;
 	platform_class->link_vxlan_add = link_vxlan_add;
 
+	platform_class->tun_add = tun_add;
+
 	platform_class->infiniband_partition_add = infiniband_partition_add;
 	platform_class->infiniband_partition_delete = infiniband_partition_delete;
 
@@ -7244,7 +7290,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_macvlan_add = link_macvlan_add;
 	platform_class->link_ipip_add = link_ipip_add;
 	platform_class->link_sit_add = link_sit_add;
-	platform_class->link_tun_add = link_tun_add;
 
 	platform_class->object_delete = object_delete;
 	platform_class->ip4_address_add = ip4_address_add;
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
deleted file mode 100644
index 4cb19780..00000000
--- a/src/platform/nm-netlink.c
+++ /dev/null
@@ -1,1491 +0,0 @@
-/* -*- 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;
-}
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
deleted file mode 100644
index c0dc09c4..00000000
--- a/src/platform/nm-netlink.h
+++ /dev/null
@@ -1,511 +0,0 @@
-/* -*- 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.
- */
-
-#ifndef __NM_NETLINK_H__
-#define __NM_NETLINK_H__
-
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <linux/genetlink.h>
-
-/*****************************************************************************/
-#define _NLE_BASE               100000
-#define NLE_UNSPEC              (_NLE_BASE +  0)
-#define NLE_BUG                 (_NLE_BASE +  1)
-#define NLE_NATIVE_ERRNO        (_NLE_BASE +  2)
-#define NLE_SEQ_MISMATCH        (_NLE_BASE +  3)
-#define NLE_MSG_TRUNC           (_NLE_BASE +  4)
-#define NLE_MSG_TOOSHORT        (_NLE_BASE +  5)
-#define NLE_DUMP_INTR           (_NLE_BASE +  6)
-#define NLE_ATTRSIZE            (_NLE_BASE +  7)
-#define NLE_BAD_SOCK            (_NLE_BASE +  8)
-#define NLE_NOADDR              (_NLE_BASE +  9)
-#define NLE_MSG_OVERFLOW        (_NLE_BASE + 10)
-
-#define _NLE_BASE_END           (_NLE_BASE + 11)
-
-#define NLMSGERR_ATTR_UNUSED            0
-#define NLMSGERR_ATTR_MSG               1
-#define NLMSGERR_ATTR_OFFS              2
-#define NLMSGERR_ATTR_COOKIE            3
-#define NLMSGERR_ATTR_MAX               3
-
-#ifndef NLM_F_ACK_TLVS
-#define NLM_F_ACK_TLVS                  0x200
-#endif
-
-static inline int
-nl_errno (int err)
-{
-	/* the error codes from our netlink implementation are plain errno
-	 * extended with our own error in a particular range starting from
-	 * _NLE_BASE.
-	 *
-	 * However, often we encode errors as negative values. This function
-	 * normalizes the error and returns its positive value. */
-	return err >= 0
-	       ? err
-	       : ((err == G_MININT) ? NLE_BUG : -errno);
-}
-
-static inline int
-nl_syserr2nlerr (int err)
-{
-	if (err == G_MININT)
-		return NLE_NATIVE_ERRNO;
-	if (err < 0)
-		err = -err;
-	return (err >= _NLE_BASE && err < _NLE_BASE_END)
-	       ? NLE_NATIVE_ERRNO
-	       : err;
-}
-
-const char *nl_geterror (int err);
-
-/*****************************************************************************/
-
-/* Basic attribute data types */
-enum {
-	NLA_UNSPEC,     /* Unspecified type, binary data chunk */
-	NLA_U8,         /* 8 bit integer */
-	NLA_U16,        /* 16 bit integer */
-	NLA_U32,        /* 32 bit integer */
-	NLA_U64,        /* 64 bit integer */
-	NLA_STRING,     /* NUL terminated character string */
-	NLA_FLAG,       /* Flag */
-	NLA_MSECS,      /* Micro seconds (64bit) */
-	NLA_NESTED,     /* Nested attributes */
-	NLA_NESTED_COMPAT,
-	NLA_NUL_STRING,
-	NLA_BINARY,
-	NLA_S8,
-	NLA_S16,
-	NLA_S32,
-	NLA_S64,
-	__NLA_TYPE_MAX,
-};
-
-#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
-
-struct nl_msg;
-
-/*****************************************************************************/
-
-const char *nl_nlmsgtype2str (int type, char *buf, size_t size);
-
-const char *nl_nlmsg_flags2str (int flags, char *buf, size_t len);
-
-const char *nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len);
-
-/*****************************************************************************/
-
-struct nla_policy {
-	/* Type of attribute or NLA_UNSPEC */
-	uint16_t type;
-
-	/* Minimal length of payload required */
-	uint16_t minlen;
-
-	/* Maximal length of payload allowed */
-	uint16_t maxlen;
-};
-
-/*****************************************************************************/
-
-static inline int
-nla_attr_size(int payload)
-{
-	nm_assert (payload >= 0);
-
-	return NLA_HDRLEN + payload;
-}
-
-static inline int
-nla_total_size (int payload)
-{
-	return NLA_ALIGN (nla_attr_size (payload));
-}
-
-static inline int
-nla_padlen (int payload)
-{
-	return nla_total_size(payload) - nla_attr_size(payload);
-}
-
-struct nlattr *nla_reserve (struct nl_msg *msg, int attrtype, int attrlen);
-
-static inline int
-nla_len (const struct nlattr *nla)
-{
-	return nla->nla_len - NLA_HDRLEN;
-}
-
-static inline int
-nla_type (const struct nlattr *nla)
-{
-	return nla->nla_type & NLA_TYPE_MASK;
-}
-
-static inline void *
-nla_data (const struct nlattr *nla)
-{
-	nm_assert (nla);
-	return (char *) nla + NLA_HDRLEN;
-}
-
-static inline uint8_t
-nla_get_u8 (const struct nlattr *nla)
-{
-	return *(const uint8_t *) nla_data (nla);
-}
-
-static inline uint8_t
-nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_val)
-{
-	nm_assert (tb);
-	nm_assert (attr >= 0);
-
-	return tb[attr] ? nla_get_u8 (tb[attr]) : default_val;
-}
-
-static inline uint16_t
-nla_get_u16 (const struct nlattr *nla)
-{
-	return *(const uint16_t *) nla_data (nla);
-}
-
-static inline uint32_t
-nla_get_u32(const struct nlattr *nla)
-{
-	return *(const uint32_t *) nla_data (nla);
-}
-
-uint64_t nla_get_u64 (const struct nlattr *nla);
-
-static inline char *
-nla_get_string (const struct nlattr *nla)
-{
-	return (char *) nla_data (nla);
-}
-
-size_t nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize);
-
-int nla_memcpy (void *dest, const struct nlattr *src, int count);
-
-int nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data);
-
-static inline int
-nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
-{
-	return nla_put(msg, attrtype, strlen(str) + 1, str);
-}
-
-#define NLA_PUT(msg, attrtype, attrlen, data) \
-	do { \
-		if (nla_put(msg, attrtype, attrlen, data) < 0) \
-			goto nla_put_failure; \
-	} while(0)
-
-#define NLA_PUT_TYPE(msg, type, attrtype, value) \
-	do { \
-		type __tmp = value; \
-		NLA_PUT(msg, attrtype, sizeof(type), &__tmp); \
-	} while(0)
-
-#define NLA_PUT_U8(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
-
-#define NLA_PUT_U16(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
-
-#define NLA_PUT_U32(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
-
-#define NLA_PUT_U64(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
-
-#define NLA_PUT_STRING(msg, attrtype, value) \
-	NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
-
-struct nlattr *nla_find (const struct nlattr *head, int len, int attrtype);
-
-static inline int
-nla_ok (const struct nlattr *nla, int remaining)
-{
-	return remaining >= sizeof(*nla) &&
-	       nla->nla_len >= sizeof(*nla) &&
-	       nla->nla_len <= remaining;
-}
-
-static inline struct nlattr *
-nla_next(const struct nlattr *nla, int *remaining)
-{
-	int totlen = NLA_ALIGN(nla->nla_len);
-
-	*remaining -= totlen;
-	return (struct nlattr *) ((char *) nla + totlen);
-}
-
-#define nla_for_each_attr(pos, head, len, rem) \
-	for (pos = head, rem = len; \
-	     nla_ok(pos, rem); \
-	     pos = nla_next(pos, &(rem)))
-
-#define nla_for_each_nested(pos, nla, rem) \
-	for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); \
-	     nla_ok(pos, rem); \
-	     pos = nla_next(pos, &(rem)))
-
-void nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr);
-struct nlattr *nla_nest_start (struct nl_msg *msg, int attrtype);
-int nla_nest_end (struct nl_msg *msg, struct nlattr *start);
-
-int nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
-               const struct nla_policy *policy);
-
-static inline int
-nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
-                  const struct nla_policy *policy)
-{
-	return nla_parse (tb, maxtype, nla_data(nla), nla_len(nla), policy);
-}
-
-/*****************************************************************************/
-
-struct nl_msg *nlmsg_alloc (void);
-
-struct nl_msg *nlmsg_alloc_size (size_t max);
-
-struct nl_msg *nlmsg_alloc_inherit (struct nlmsghdr *hdr);
-
-struct nl_msg *nlmsg_alloc_convert (struct nlmsghdr *hdr);
-
-struct nl_msg *nlmsg_alloc_simple (int nlmsgtype, int flags);
-
-void *nlmsg_reserve (struct nl_msg *n, size_t len, int pad);
-
-int nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad);
-
-void nlmsg_free (struct nl_msg *msg);
-
-static inline int
-nlmsg_size (int payload)
-{
-	nm_assert (payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4);
-	return NLMSG_HDRLEN + payload;
-}
-
-static inline int
-nlmsg_total_size (int payload)
-{
-	return NLMSG_ALIGN (nlmsg_size (payload));
-}
-
-static inline int
-nlmsg_ok (const struct nlmsghdr *nlh, int remaining)
-{
-	return (remaining >= (int)sizeof(struct nlmsghdr) &&
-	       nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
-	       nlh->nlmsg_len <= remaining);
-}
-
-static inline struct nlmsghdr *
-nlmsg_next (struct nlmsghdr *nlh, int *remaining)
-{
-	int totlen = NLMSG_ALIGN(nlh->nlmsg_len);
-
-	*remaining -= totlen;
-
-	return (struct nlmsghdr *) ((unsigned char *) nlh + totlen);
-}
-
-int nlmsg_get_proto (struct nl_msg *msg);
-void nlmsg_set_proto (struct nl_msg *msg, int protocol);
-
-void nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr);
-
-struct ucred *nlmsg_get_creds (struct nl_msg *msg);
-void nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds);
-
-static inline void
-_nm_auto_nl_msg_cleanup (struct nl_msg **ptr)
-{
-	nlmsg_free (*ptr);
-}
-#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
-
-static inline void *
-nlmsg_data (const struct nlmsghdr *nlh)
-{
-	return (unsigned char *) nlh + NLMSG_HDRLEN;
-}
-
-static inline void *
-nlmsg_tail (const struct nlmsghdr *nlh)
-{
-	return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len);
-}
-
-struct nlmsghdr *nlmsg_hdr (struct nl_msg *n);
-
-static inline int
-nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
-{
-	if (nlh->nlmsg_len < nlmsg_size (hdrlen))
-		return 0;
-
-	return 1;
-}
-
-static inline int
-nlmsg_datalen (const struct nlmsghdr *nlh)
-{
-	return nlh->nlmsg_len - NLMSG_HDRLEN;
-}
-
-static inline int
-nlmsg_attrlen (const struct nlmsghdr *nlh, int hdrlen)
-{
-	return NM_MAX ((int) (nlmsg_datalen (nlh) - NLMSG_ALIGN (hdrlen)), 0);
-}
-
-static inline struct nlattr *
-nlmsg_attrdata (const struct nlmsghdr *nlh, int hdrlen)
-{
-	unsigned char *data = nlmsg_data(nlh);
-	return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
-}
-
-static inline struct nlattr *
-nlmsg_find_attr (struct nlmsghdr *nlh, int hdrlen, int attrtype)
-{
-	return nla_find (nlmsg_attrdata (nlh, hdrlen),
-	                 nlmsg_attrlen (nlh, hdrlen),
-	                 attrtype);
-}
-
-int nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-                 int maxtype, const struct nla_policy *policy);
-
-struct nlmsghdr *nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
-                            int type, int payload, int flags);
-
-/*****************************************************************************/
-
-#define NL_AUTO_PORT 0
-#define NL_AUTO_SEQ  0
-
-struct nl_sock;
-
-struct nl_sock *nl_socket_alloc (void);
-
-void nl_socket_free (struct nl_sock *sk);
-
-int nl_socket_get_fd (const struct nl_sock *sk);
-
-struct sockaddr_nl *nlmsg_get_dst (struct nl_msg *msg);
-
-size_t nl_socket_get_msg_buf_size (struct nl_sock *sk);
-int nl_socket_set_msg_buf_size (struct nl_sock *sk, size_t bufsize);
-
-int nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf);
-
-int nl_socket_set_passcred (struct nl_sock *sk, int state);
-
-int nl_socket_set_nonblocking (const struct nl_sock *sk);
-
-void nl_socket_disable_msg_peek (struct nl_sock *sk);
-
-uint32_t nl_socket_get_local_port (const struct nl_sock *sk);
-
-int nl_socket_add_memberships (struct nl_sock *sk, int group, ...);
-
-int nl_connect (struct nl_sock *sk, int protocol);
-
-int nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
-             unsigned char **buf, struct ucred **creds);
-
-int nl_send (struct nl_sock *sk, struct nl_msg *msg);
-
-int nl_send_auto (struct nl_sock *sk, struct nl_msg *msg);
-
-/*****************************************************************************/
-
-enum nl_cb_action {
-	/* Proceed with wathever would come next */
-	NL_OK,
-	/* Skip this message */
-	NL_SKIP,
-	/* Stop parsing altogether and discard remaining messages */
-	NL_STOP,
-};
-
-typedef int (*nl_recvmsg_msg_cb_t) (struct nl_msg *msg, void *arg);
-
-typedef int (*nl_recvmsg_err_cb_t) (struct sockaddr_nl *nla,
-                                    struct nlmsgerr *nlerr, void *arg);
-
-struct nl_cb {
-	nl_recvmsg_msg_cb_t     valid_cb;
-	void *                  valid_arg;
-
-	nl_recvmsg_msg_cb_t     finish_cb;
-	void *                  finish_arg;
-
-	nl_recvmsg_msg_cb_t     ack_cb;
-	void *                  ack_arg;
-
-	nl_recvmsg_err_cb_t     err_cb;
-	void *                  err_arg;
-};
-
-int nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr);
-
-int nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen);
-
-void nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg);
-
-int nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb);
-
-int nl_wait_for_ack (struct nl_sock *sk,
-                     const struct nl_cb *cb);
-
-int nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable);
-
-/*****************************************************************************/
-
-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);
-void *genlmsg_data (const struct genlmsghdr *gnlh);
-void *genlmsg_user_hdr (const struct genlmsghdr *gnlh);
-struct genlmsghdr *genlmsg_hdr (struct nlmsghdr *nlh);
-void *genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen);
-struct nlattr *genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen);
-int genlmsg_len (const struct genlmsghdr *gnlh);
-int genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen);
-int genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen);
-int genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-                   int maxtype, const struct nla_policy *policy);
-
-int genl_ctrl_resolve (struct nl_sock *sk, const char *name);
-
-/*****************************************************************************/
-
-#endif /* __NM_NETLINK_H__ */
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 114bf4b3..b664e8a9 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -589,8 +589,6 @@ nmp_utils_ip_config_source_coerce_to_rtprot (NMIPConfigSource source)
 	switch (source) {
 	case NM_IP_CONFIG_SOURCE_KERNEL:
 		return RTPROT_KERNEL;
-	case NM_IP_CONFIG_SOURCE_IP6LL:
-		return RTPROT_KERNEL;
 	case NM_IP_CONFIG_SOURCE_DHCP:
 		return RTPROT_DHCP;
 	case NM_IP_CONFIG_SOURCE_NDISC:
@@ -658,7 +656,6 @@ nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize
 	case NM_IP_CONFIG_SOURCE_KERNEL:          s = "kernel"; break;
 	case NM_IP_CONFIG_SOURCE_SHARED:          s = "shared"; break;
 	case NM_IP_CONFIG_SOURCE_IP4LL:           s = "ipv4ll"; break;
-	case NM_IP_CONFIG_SOURCE_IP6LL:           s = "ipv6ll"; break;
 	case NM_IP_CONFIG_SOURCE_PPP:             s = "ppp"; break;
 	case NM_IP_CONFIG_SOURCE_WWAN:            s = "wwan"; break;
 	case NM_IP_CONFIG_SOURCE_VPN:             s = "vpn"; break;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 84d862d2..c7ed90e3 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -353,45 +353,6 @@ nm_platform_process_events (NMPlatform *self)
 		klass->process_events (self);
 }
 
-const NMPlatformLink *
-nm_platform_process_events_ensure_link (NMPlatform *self,
-                                        int ifindex,
-                                        const char *ifname)
-{
-	const NMPObject *obj;
-	gboolean refreshed = FALSE;
-
-	g_return_val_if_fail (NM_IS_PLATFORM (self), NULL);
-
-	if (ifindex <= 0 && !ifname)
-		return NULL;
-
-	/* we look into the cache, whether a link for given ifindex/ifname
-	 * exits. If not, we poll the netlink socket, maybe the event
-	 * with the link is waiting.
-	 *
-	 * Then we try again to find the object.
-	 *
-	 * If the link is already cached the first time, we avoid polling
-	 * the netlink socket. */
-again:
-	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self),
-	                                  ifindex,
-	                                  ifname,
-	                                  FALSE, /* also invisible. We don't care here whether udev is ready */
-	                                  NM_LINK_TYPE_NONE,
-	                                  NULL, NULL);
-	if (obj)
-		return NMP_OBJECT_CAST_LINK (obj);
-	if (!refreshed) {
-		refreshed = TRUE;
-		nm_platform_process_events (self);
-		goto again;
-	}
-
-	return NULL;
-}
-
 /*****************************************************************************/
 
 /**
@@ -636,11 +597,11 @@ nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name)
 	 * further by moving children/slaves to the end. */
 	g_ptr_array_sort_with_data (links, _link_get_all_presort, GINT_TO_POINTER (sort_by_name));
 
-	unseen = g_hash_table_new (nm_direct_hash, NULL);
+	unseen = g_hash_table_new (g_direct_hash, g_direct_equal);
 	for (i = 0; i < links->len; i++) {
 		item = NMP_OBJECT_CAST_LINK (links->pdata[i]);
 		nm_assert (item->ifindex > 0);
-		if (!g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
+		if (!nm_g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
 			nm_assert_not_reached ();
 	}
 
@@ -1157,21 +1118,6 @@ nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
 }
 
 /**
- * nm_platform_refresh_all:
- * @self: platform instance
- * @obj_type: The object type to request.
- *
- * Resync and re-request all objects from kernel of a certain @obj_type.
- */
-void
-nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type)
-{
-	_CHECK_SELF_VOID (self, klass);
-
-	klass->refresh_all (self, obj_type);
-}
-
-/**
  * nm_platform_link_refresh:
  * @self: platform instance
  * @ifindex: Interface index
@@ -1379,26 +1325,30 @@ gconstpointer
 nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 {
 	const NMPlatformLink *pllink;
+	gconstpointer a = NULL;
+	guint8 l = 0;
 
 	_CHECK_SELF (self, klass, NULL);
 
+	if (length)
+		*length = 0;
+
 	g_return_val_if_fail (ifindex > 0, NULL);
 
 	pllink = nm_platform_link_get (self, ifindex);
-
-	if (   !pllink
-	    || pllink->addr.len <= 0) {
-		NM_SET_OUT (length, 0);
-		return NULL;
-	}
-
-	if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) {
-		NM_SET_OUT (length, 0);
-		g_return_val_if_reached (NULL);
+	if (pllink && pllink->addr.len > 0) {
+		if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) {
+			if (length)
+				*length = 0;
+			g_return_val_if_reached (NULL);
+		}
+		a = pllink->addr.data;
+		l = pllink->addr.len;
 	}
 
-	NM_SET_OUT (length, pllink->addr.len);
-	return pllink->addr.data;
+	if (length)
+		*length = l;
+	return a;
 }
 
 /**
@@ -1894,12 +1844,6 @@ nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLin
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_SIT, out_link);
 }
 
-const NMPlatformLnkTun *
-nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_TUN, out_link);
-}
-
 const NMPlatformLnkVlan *
 nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -2046,43 +1990,33 @@ nm_platform_link_vxlan_add (NMPlatform *self,
  * @vnet_hdr: whether to set the IFF_VNET_HDR flag
  * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
  * @out_link: on success, the link object
- * @out_fd: (allow-none): if give, return the file descriptor for the
- *   created device. Note that when creating a non-persistent device,
- *   this argument is mandatory, otherwise it makes no sense
- *   to create such an interface.
- *   The caller is responsible for closing this file descriptor.
  *
  * Create a TUN or TAP interface.
  */
 NMPlatformError
 nm_platform_link_tun_add (NMPlatform *self,
                           const char *name,
-                          const NMPlatformLnkTun *props,
-                          const NMPlatformLink **out_link,
-                          int *out_fd)
+                          gboolean tap,
+                          gint64 owner,
+                          gint64 group,
+                          gboolean pi,
+                          gboolean vnet_hdr,
+                          gboolean multi_queue,
+                          const NMPlatformLink **out_link)
 {
-	char b[255];
 	NMPlatformError plerr;
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (NM_IN_SET (props->type, IFF_TUN, IFF_TAP), NM_PLATFORM_ERROR_BUG);
-
-	/* creating a non-persistant device requires that the caller handles
-	 * the file descriptor. */
-	g_return_val_if_fail (props->persist || out_fd, NM_PLATFORM_ERROR_BUG);
 
-	NM_SET_OUT (out_fd, -1);
-
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_TUN, out_link);
+	plerr = _link_add_check_existing (self, name, tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding tun '%s' %s",
-	       name, nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
-	if (!klass->link_tun_add (self, name, props, out_link, out_fd))
+	_LOGD ("link: adding %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
+	       tap ? "tap" : "tun", name, owner, group);
+	if (!klass->tun_add (self, name, tap, owner, group, pi, vnet_hdr, multi_queue, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
@@ -2686,100 +2620,44 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 	return TRUE;
 }
 
-/**
- * nm_platform_link_tun_get_properties:
- * @self: the #NMPlatform instance
- * @ifindex: the ifindex to look up
- * @out_properties: (out): (allow-none): return the read properties
- *
- * Only recent versions of kernel export tun properties via netlink.
- * So, if that's the case, then we have the NMPlatformLnkTun instance
- * in the platform cache ready to return. Otherwise, this function
- * falls back reading sysctl to obtain the tun properties. That
- * is racy, because querying sysctl means that the object might
- * be already removed from cache (while NM didn't yet process the
- * netlink message).
- *
- * Hence, to lookup the tun properties, you always need to use this
- * function, and use it with care knowing that it might obtain its
- * data by reading sysctl. Note that we don't want to add this workaround
- * to the platform cache itself, because the cache should (mainly)
- * contain data from netlink. To access the sysctl side channel, the
- * user needs to do explicitly.
- *
- * Returns: #TRUE, if the properties could be read. */
 gboolean
-nm_platform_link_tun_get_properties (NMPlatform *self,
-                                     int ifindex,
-                                     NMPlatformLnkTun *out_properties)
+nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
 {
-	const NMPObject *plobj;
-	const NMPObject *pllnk;
+	nm_auto_close int dirfd = -1;
 	char ifname[IFNAMSIZ];
-	gint64 owner;
-	gint64 group;
 	gint64 flags;
-
+	gboolean success = TRUE;
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (props, FALSE);
 
-	/* we consider also invisible links (those that are not yet in udev). */
-	plobj = nm_platform_link_get_obj (self, ifindex, FALSE);
-	if (!plobj)
-		return FALSE;
-	if (NMP_OBJECT_CAST_LINK (plobj)->type != NM_LINK_TYPE_TUN)
-		return FALSE;
-
-	pllnk = plobj->_link.netlink.lnk;
-	if (pllnk) {
-		nm_assert (NMP_OBJECT_GET_TYPE (pllnk) == NMP_OBJECT_TYPE_LNK_TUN);
-		nm_assert (NMP_OBJECT_GET_CLASS (pllnk)->lnk_link_type == NM_LINK_TYPE_TUN);
+	memset (props, 0, sizeof (*props));
+	props->owner = -1;
+	props->group = -1;
 
-		/* recent kernels expose tun properties via netlink and thus we have them
-		 * in the platform cache. */
-		NM_SET_OUT (out_properties, pllnk->lnk_tun);
-		return TRUE;
-	}
-
-	/* fallback to reading sysctl. */
-	{
-		nm_auto_close int dirfd = -1;
+	dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname);
+	if (dirfd < 0)
+		return FALSE;
 
-		dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname);
-		if (dirfd < 0)
-			return FALSE;
+	props->owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXINT64, -1);
+	if (errno)
+		success = FALSE;
 
-		owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXUINT32, -2);
-		if (owner == -2)
-			return FALSE;
+	props->group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXINT64, -1);
+	if (errno)
+		success = FALSE;
 
-		group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXUINT32, -2);
-		if (group == -2)
-			return FALSE;
-
-		flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1);
-		if (flags == -1)
-			return FALSE;
-	}
+	flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1);
+	if (flags >= 0) {
+		props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
+		props->no_pi = !!(flags & IFF_NO_PI);
+		props->vnet_hdr = !!(flags & IFF_VNET_HDR);
+		props->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
+	} else
+		success = FALSE;
 
-	if (out_properties) {
-		memset (out_properties, 0, sizeof (*out_properties));
-		if (owner != -1) {
-			out_properties->owner_valid = TRUE;
-			out_properties->owner = owner;
-		}
-		if (group != -1) {
-			out_properties->group_valid = TRUE;
-			out_properties->group = group;
-		}
-		out_properties->type = (flags & TUN_TYPE_MASK);
-		out_properties->pi = !(flags & IFF_NO_PI);
-		out_properties->vnet_hdr = !!(flags & IFF_VNET_HDR);
-		out_properties->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
-		out_properties->persist = !!(flags & IFF_PERSIST);
-	}
-	return TRUE;
+	return success;
 }
 
 gboolean
@@ -3054,7 +2932,7 @@ nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *ob
  * @user_data: user data for @predicate
  *
  * Returns the result of lookup in a GPtrArray. The result array contains
- * references objects from the cache, its destroy function will unref them.
+ * references objects from the cache, it's destroy function will unref them.
  *
  * The user must unref the GPtrArray, which will also unref the NMPObject
  * elements.
@@ -3233,68 +3111,24 @@ nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr addr
 }
 
 static gboolean
-_addr_array_clean_expired (int addr_family, int ifindex, GPtrArray *array, guint32 now, GHashTable **idx)
+array_contains_ip6_address (const GPtrArray *addresses, const NMPlatformIP6Address *address, gint32 now)
 {
+	guint len = addresses ? addresses->len : 0;
 	guint i;
-	gboolean any_addrs = FALSE;
-
-	nm_assert_addr_family (addr_family);
-	nm_assert (ifindex > 0);
-	nm_assert (now > 0);
 
-	if (!array)
-		return FALSE;
-
-	/* remove all addresses that are already expired. */
-	for (i = 0; i < array->len; i++) {
-		const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS (array->pdata[i]);
-
-#if NM_MORE_ASSERTS > 10
-		nm_assert (a);
-		nm_assert (a->ifindex == ifindex);
-		{
-			const NMPObject *o = NMP_OBJECT_UP_CAST (a);
-			guint j;
-
-			nm_assert (NMP_OBJECT_GET_CLASS (o)->addr_family == addr_family);
-			for (j = i + 1; j < array->len; j++) {
-				const NMPObject *o2 = array->pdata[j];
-
-				nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_GET_TYPE (o2));
-				nm_assert (!nmp_object_id_equal (o, o2));
-			}
-		}
-#endif
-
-		if (   addr_family == AF_INET6
-		    && NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_TEMPORARY)) {
-			/* temporary addresses are never added explicitly by NetworkManager but
-			 * kernel adds them via mngtempaddr flag.
-			 *
-			 * We drop them from this list. */
-			goto clear_and_next;
-		}
+	for (i = 0; i < len; i++) {
+		NMPlatformIP6Address *candidate = NMP_OBJECT_CAST_IP6_ADDRESS (addresses->pdata[i]);
 
-		if (!nm_utils_lifetime_get (a->timestamp, a->lifetime, a->preferred,
-		                            now, NULL))
-			goto clear_and_next;
+		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen) {
+			guint32 lifetime, preferred;
 
-		if (idx) {
-			if (G_UNLIKELY (!*idx)) {
-				*idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
-				                         (GEqualFunc) nmp_object_id_equal);
-			}
-			if (!g_hash_table_add (*idx, (gpointer) NMP_OBJECT_UP_CAST (a)))
-				nm_assert_not_reached ();
+			if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
+			                           now, &lifetime, &preferred))
+				return TRUE;
 		}
-		any_addrs = TRUE;
-		continue;
-
-clear_and_next:
-		nmp_object_unref (g_steal_pointer (&array->pdata[i]));
 	}
 
-	return any_addrs;
+	return FALSE;
 }
 
 static gboolean
@@ -3345,7 +3179,7 @@ ip4_addr_subnets_build_index (const GPtrArray *addresses,
 
 	nm_assert (addresses && addresses->len);
 
-	subnets = g_hash_table_new (nm_direct_hash, NULL);
+	subnets = g_hash_table_new (NULL, NULL);
 
 	/* Build a hash table of all addresses per subnet */
 	for (i = 0; i < addresses->len; i++) {
@@ -3478,8 +3312,39 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	if (!_addr_array_clean_expired (AF_INET, ifindex, known_addresses, now, &known_addresses_idx))
-		known_addresses = NULL;
+	if (known_addresses) {
+		/* remove all addresses that are already expired. */
+		for (i = 0; i < known_addresses->len; i++) {
+			const NMPObject *o;
+
+			o = known_addresses->pdata[i];
+			nm_assert (o);
+
+			known_address = NMP_OBJECT_CAST_IP4_ADDRESS (known_addresses->pdata[i]);
+
+			if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+			                            now, &lifetime, &preferred))
+				goto delete_and_next;
+
+			if (G_UNLIKELY (!known_addresses_idx)) {
+				known_addresses_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
+				                                        (GEqualFunc) nmp_object_id_equal);
+			}
+			if (!nm_g_hash_table_insert (known_addresses_idx, (gpointer) o, (gpointer) o)) {
+				/* duplicate? Keep only the first instance. */
+				goto delete_and_next;
+			}
+
+			continue;
+delete_and_next:
+			nmp_object_unref (o);
+			known_addresses->pdata[i] = NULL;
+		}
+
+		if (   !known_addresses_idx
+		    || g_hash_table_size (known_addresses_idx) == 0)
+			known_addresses = NULL;
+	}
 
 	plat_addresses = nm_platform_lookup_clone (self,
 	                                           nmp_lookup_init_object (&lookup,
@@ -3576,9 +3441,8 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 
 		known_address = NMP_OBJECT_CAST_IP4_ADDRESS (o);
 
-		lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-		                                  now, &preferred);
-		if (!lifetime)
+		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                            now, &lifetime, &preferred))
 			goto delete_and_next2;
 
 		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
@@ -3600,15 +3464,9 @@ delete_and_next2:
  * nm_platform_ip6_address_sync:
  * @self: platform instance
  * @ifindex: Interface index
- * @known_addresses: List of addresses. The list will be modified and only
- *   addresses that were successfully added will be kept in the list.
- *   That means, expired addresses and addresses that could not be added
- *   will be dropped.
- *   Hence, the input argument @known_addresses is also an output argument
- *   telling which addresses were succesfully added.
- *   Addresses are removed by unrefing the instance via nmp_object_unref()
- *   and leaving a NULL tombstone.
- * @full_sync: Also remove link-local and temporary addresses.
+ * @known_addresses: List of IPv6 addresses, as NMPObject. The list
+ *   is not modified.
+ * @keep_link_local: Don't remove link-local address
  *
  * A convenience function to synchronize addresses for a specific interface
  * with the least possible disturbance. It simply removes addresses that are
@@ -3619,117 +3477,32 @@ delete_and_next2:
 gboolean
 nm_platform_ip6_address_sync (NMPlatform *self,
                               int ifindex,
-                              GPtrArray *known_addresses,
-                              gboolean full_sync)
+                              const GPtrArray *known_addresses,
+                              gboolean keep_link_local)
 {
 	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
+	NMPlatformIP6Address *address;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
-	guint i_plat, i_know;
-	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
+	guint i;
 	NMPLookup lookup;
 	guint32 ifa_flags;
 
-	if (!_addr_array_clean_expired (AF_INET6, ifindex, known_addresses, now, &known_addresses_idx))
-		known_addresses = NULL;
-
-	/* @plat_addresses is in decreasing priority order (highest priority addresses first), contrary to
-	 * @known_addresses which is in increasing priority order (lowest priority addresses first). */
+	/* Delete unknown addresses */
 	plat_addresses = nm_platform_lookup_clone (self,
 	                                           nmp_lookup_init_object (&lookup,
 	                                                                   NMP_OBJECT_TYPE_IP6_ADDRESS,
 	                                                                   ifindex),
 	                                           NULL, NULL);
-
 	if (plat_addresses) {
-		guint known_addresses_len;
-
-		known_addresses_len = known_addresses ? known_addresses->len : 0;
-
-		/* First, compare every address whether it is still a "known address", that is, whether
-		 * to keep it or to delete it.
-		 *
-		 * If we don't find a matching valid address in @known_addresses, we will delete
-		 * plat_addr.
-		 *
-		 * Certain addresses, like temporary addresses, are ignored by this function
-		 * if not run with full_sync. These addresses are usually not managed by NetworkManager
-		 * directly, or at least, they are not managed via nm_platform_ip6_address_sync().
-		 * Only in full_sync mode, we really want to get rid of them (usually, when we take
-		 * the interface down).
-		 *
-		 * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */
-		for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) {
-			const NMPObject *plat_obj = plat_addresses->pdata[i_plat];
-			const NMPObject *know_obj;
-			const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_obj);
-
-			if (NM_FLAGS_HAS (plat_addr->n_ifa_flags, IFA_F_TEMPORARY)) {
-				if (!full_sync) {
-					/* just mark as handled, without actually deleting the address. */
-					goto clear_and_next;
-				}
-			} else if (known_addresses_idx) {
-				know_obj = g_hash_table_lookup (known_addresses_idx, plat_obj);
-				if (   know_obj
-				    && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS (know_obj)->plen) {
-					/* technically, plen is not part of the ID for IPv6 addresses and thus
-					 * @plat_addr is essentially the same address as @know_addr (regrading
-					 * its identity, not its other attributes).
-					 * However, we cannot modify an existing addresses' plen without
-					 * removing and readding it. Thus, only keep plat_addr, if the plen
-					 * matches.
-					 *
-					 * keep this one, and continue */
-					continue;
-				}
-			}
-
-			nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen);
-clear_and_next:
-			nmp_object_unref (g_steal_pointer (&plat_addresses->pdata[i_plat]));
-		}
+		for (i = 0; i < plat_addresses->len; i++) {
+			address = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[i]);
 
-		/* Next, we must preserve the priority of the routes. That is, source address
-		 * selection will choose addresses in the order as they are reported by kernel.
-		 * Note that the order in @plat_addresses of the remaining matches is highest
-		 * priority first.
-		 * We need to compare this to the order in @known_addresses (which has lowest
-		 * priority first).
-		 *
-		 * If we find a first discrepancy, we need to delete all remaining addresses
-		 * from that point on, because below we must re-add all the addresses in the
-		 * right order to get their priority right. */
-		i_plat = plat_addresses->len;
-		i_know = 0;
-		while (i_plat > 0) {
-			const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]);
-
-			if (!plat_addr)
+			/* Leave link local address management to the kernel */
+			if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
 				continue;
 
-			for (; i_know < known_addresses_len; i_know++) {
-				const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
-
-				if (!know_addr)
-					continue;
-
-				if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) {
-					/* we have a match. Mark address as handled. */
-					i_know++;
-					goto next_plat;
-				}
-
-				/* all remainging addresses need to be removed as well, so that we can
-				 * re-add them in the correct order. Signal that, by setting @i_know
-				 * so that the next @i_plat iteration, we won't enter the loop and
-				 * delete the address right away */
-				i_know = known_addresses_len;
-				break;
-			}
-
-			nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen);
-next_plat:
-			;
+			if (!array_contains_ip6_address (known_addresses, address, now))
+				nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen);
 		}
 	}
 
@@ -3740,18 +3513,19 @@ next_plat:
 	            ? IFA_F_NOPREFIXROUTE
 	            : 0;
 
-	/* Add missing addresses. New addresses are added by kernel with top
-	 * priority.
-	 */
-	for (i_know = 0; i_know < known_addresses->len; i_know++) {
-		const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
+	/* Add missing addresses */
+	for (i = 0; i < known_addresses->len; i++) {
+		const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i]);
 		guint32 lifetime, preferred;
 
-		if (!known_address)
+		if (NM_FLAGS_HAS (known_address->n_ifa_flags, IFA_F_TEMPORARY)) {
+			/* Kernel manages these */
 			continue;
+		}
 
-		lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-		                                  now, &preferred);
+		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                            now, &lifetime, &preferred))
+			continue;
 
 		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
 		                                  known_address->plen, known_address->peer_address,
@@ -3779,7 +3553,7 @@ nm_platform_ip_address_flush (NMPlatform *self,
 	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET))
 		success &= nm_platform_ip4_address_sync (self, ifindex, NULL);
 	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6))
-		success &= nm_platform_ip6_address_sync (self, ifindex, NULL, TRUE);
+		success &= nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE);
 	return success;
 }
 
@@ -3918,8 +3692,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 
 	for (i_type = 0; routes && i_type < 2; i_type++) {
 		for (i = 0; i < routes->len; i++) {
-			NMPlatformError plerr, plerr2;
-			gboolean gateway_route_added = FALSE;
+			NMPlatformError plerr;
 
 			conf_o = routes->pdata[i];
 
@@ -3939,7 +3712,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				routes_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
 				                               (GEqualFunc) nmp_object_id_equal);
 			}
-			if (!g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
+			if (!nm_g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
 				_LOGD ("route-sync: skip adding duplicate route %s",
 				       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
 				continue;
@@ -3965,7 +3738,6 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				}
 			}
 
-sync_route_add:
 			plerr = nm_platform_ip_route_add (self,
 			                                    NMP_NLM_FLAG_APPEND
 			                                  | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
@@ -3990,11 +3762,6 @@ sync_route_add:
 							       nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
 						}
 					}
-				} else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
-					_LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s",
-					       vt->is_ip4 ? '4' : '6',
-					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
 				} else if (   -((int) plerr) == EINVAL
 				           && out_temporary_not_available
 				           && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) {
@@ -4004,66 +3771,25 @@ sync_route_add:
 					if (!*out_temporary_not_available)
 						*out_temporary_not_available = g_ptr_array_new_full (0, (GDestroyNotify) nmp_object_unref);
 					g_ptr_array_add (*out_temporary_not_available, (gpointer) nmp_object_ref (conf_o));
-				} else if (   !gateway_route_added
-				           && (   (   -((int) plerr) == ENETUNREACH
-				                   && vt->is_ip4
-				                   && !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway)
-				               || (   -((int) plerr) == EHOSTUNREACH
-				                   && !vt->is_ip4
-				                   && !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))) {
-					NMPObject oo;
-
-					if (vt->is_ip4) {
-						const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (conf_o);
-
-						nmp_object_stackinit (&oo,
-						                      NMP_OBJECT_TYPE_IP4_ROUTE,
-						                      &((NMPlatformIP4Route) {
-						                          .network = r->gateway,
-						                          .plen = 32,
-						                          .metric = r->metric,
-						                          .rt_source = r->rt_source,
-						                          .table_coerced = r->table_coerced,
-						                      }));
-					} else {
-						const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (conf_o);
-
-						nmp_object_stackinit (&oo,
-						                      NMP_OBJECT_TYPE_IP6_ROUTE,
-						                      &((NMPlatformIP6Route) {
-						                          .network = r->gateway,
-						                          .plen = 128,
-						                          .metric = r->metric,
-						                          .rt_source = r->rt_source,
-						                          .table_coerced = r->table_coerced,
-						                      }));
-					}
-
-					_LOGD ("route-sync: failure to add IPv%c route: %s: %s; try adding direct route to gateway %s",
+				} else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
+					_LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s",
 					       vt->is_ip4 ? '4' : '6',
 					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)),
-					       nmp_object_to_string (&oo, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
-
-					plerr2 = nm_platform_ip_route_add (self,
-					                                     NMP_NLM_FLAG_APPEND
-					                                   | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-					                                   &oo);
-
-					if (plerr2 != NM_PLATFORM_ERROR_SUCCESS) {
-						_LOGD ("route-sync: failure to add gateway IPv%c route: %s: %s",
-						       vt->is_ip4 ? '4' : '6',
-						       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-						       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
-					}
-
-					gateway_route_added = TRUE;
-					goto sync_route_add;
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
 				} else {
-					_LOGW ("route-sync: failure to add IPv%c route: %s: %s",
+					const char *reason = "";
+
+					if (   -((int) plerr) == ENETUNREACH
+					    && (  vt->is_ip4
+					        ? !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway
+					        : !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))
+						reason = "; is the gateway directly reachable?";
+
+					_LOGW ("route-sync: failure to add IPv%c route: %s: %s%s",
 					       vt->is_ip4 ? '4' : '6',
 					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)),
+					       reason);
 					success = FALSE;
 				}
 			}
@@ -4987,7 +4713,6 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            "%s" /* encap limit */
 	            "%s" /* flow label */
 	            "%s" /* proto */
-	            " flags 0x%x"
 	            "",
 	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
 	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
@@ -4996,8 +4721,7 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass),
 	            nm_sprintf_buf (str_encap, " encap-limit %u", lnk->encap_limit),
 	            nm_sprintf_buf (str_flow, " flow-label 0x05%x", lnk->flow_label),
-	            nm_sprintf_buf (str_proto, " proto %u", lnk->proto),
-	            (guint) lnk->flags);
+	            nm_sprintf_buf (str_proto, " proto %u", lnk->proto));
 	return buf;
 }
 
@@ -5118,43 +4842,6 @@ nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len
 }
 
 const char *
-nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len)
-{
-	char str_owner[50];
-	char str_group[50];
-	char str_type[50];
-	const char *type;
-
-	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
-		return buf;
-
-	if (lnk->type == IFF_TUN)
-		type = "tun";
-	else if (lnk->type == IFF_TAP)
-		type = "tap";
-	else
-		type = nm_sprintf_buf (str_type, "tun type %u", (guint) lnk->type);
-
-	g_snprintf (buf, len,
-	            "%s" /* type */
-	            "%s" /* pi */
-	            "%s" /* vnet_hdr */
-	            "%s" /* multi_queue */
-	            "%s" /* persist */
-	            "%s" /* owner */
-	            "%s" /* group */
-	            "",
-	            type,
-	            lnk->pi ? " pi" : "",
-	            lnk->vnet_hdr ? " vnet_hdr" : "",
-	            lnk->multi_queue ? " multi_queue" : "",
-	            lnk->persist ? " persist" : "",
-	            lnk->owner_valid ? nm_sprintf_buf (str_owner, " owner %u", (guint) lnk->owner) : "",
-	            lnk->group_valid ? nm_sprintf_buf (str_group, " group %u", (guint) lnk->group) : "");
-	return buf;
-}
-
-const char *
 nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len)
 {
 	char *b;
@@ -5356,10 +5043,10 @@ NM_UTILS_FLAGS2STR_DEFINE (nm_platform_addr_flags2str, unsigned,
 	NM_UTILS_FLAGS2STR (IFA_F_OPTIMISTIC, "optimistic"),
 	NM_UTILS_FLAGS2STR (IFA_F_HOMEADDRESS, "homeaddress"),
 	NM_UTILS_FLAGS2STR (IFA_F_DEPRECATED, "deprecated"),
+	NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"),
 	NM_UTILS_FLAGS2STR (IFA_F_PERMANENT, "permanent"),
 	NM_UTILS_FLAGS2STR (IFA_F_MANAGETEMPADDR, "mngtmpaddr"),
 	NM_UTILS_FLAGS2STR (IFA_F_NOPREFIXROUTE, "noprefixroute"),
-	NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"),
 );
 
 NM_UTILS_ENUM2STR_DEFINE (nm_platform_route_scope2str, int,
@@ -5850,8 +5537,7 @@ nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState
 	                     obj->tclass,
 	                     obj->encap_limit,
 	                     obj->proto,
-	                     obj->flow_label,
-	                     obj->flags);
+	                     obj->flow_label);
 }
 
 int
@@ -5866,7 +5552,6 @@ nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6
 	NM_CMP_FIELD (a, b, encap_limit);
 	NM_CMP_FIELD (a, b, flow_label);
 	NM_CMP_FIELD (a, b, proto);
-	NM_CMP_FIELD (a, b, flags);
 	return 0;
 }
 
@@ -5985,38 +5670,6 @@ nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b)
 }
 
 void
-nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h)
-{
-	nm_hash_update_vals (h,
-	                     obj->type,
-	                     obj->owner,
-	                     obj->group,
-	                     NM_HASH_COMBINE_BOOLS (guint8,
-	                                            obj->owner_valid,
-	                                            obj->group_valid,
-	                                            obj->pi,
-	                                            obj->vnet_hdr,
-	                                            obj->multi_queue,
-	                                            obj->persist));
-}
-
-int
-nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b)
-{
-	NM_CMP_SELF (a, b);
-	NM_CMP_FIELD (a, b, type);
-	NM_CMP_FIELD (a, b, owner);
-	NM_CMP_FIELD (a, b, group);
-	NM_CMP_FIELD_BOOL (a, b, owner_valid);
-	NM_CMP_FIELD_BOOL (a, b, group_valid);
-	NM_CMP_FIELD_BOOL (a, b, pi);
-	NM_CMP_FIELD_BOOL (a, b, vnet_hdr);
-	NM_CMP_FIELD_BOOL (a, b, multi_queue);
-	NM_CMP_FIELD_BOOL (a, b, persist);
-	return 0;
-}
-
-void
 nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h)
 {
 	nm_hash_update_vals (h,
@@ -6607,13 +6260,14 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	const NMPObject *o;
 	const NMPClass *klass;
 
-	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, NM_PLATFORM_SIGNAL_NONE,
-	                                                             NM_PLATFORM_SIGNAL_ADDED,
-	                                                             NM_PLATFORM_SIGNAL_CHANGED,
-	                                                             NM_PLATFORM_SIGNAL_REMOVED));
+	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED));
 
 	ASSERT_nmp_cache_ops (nm_platform_get_cache (self), cache_op, obj_old, obj_new);
 
+	nm_assert (NM_IN_SET (nm_platform_netns_get (self),
+	                      NULL,
+	                      nmp_netns_get_current ()));
+
 	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (self);
 
 	switch (cache_op) {
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index e6cef63b..f6bf02bf 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -25,7 +25,6 @@
 #include <linux/if.h>
 #include <linux/if_addr.h>
 #include <linux/if_link.h>
-#include <linux/ip6_tunnel.h>
 
 #include "nm-dbus-interface.h"
 #include "nm-core-types-internal.h"
@@ -33,7 +32,6 @@
 #include "nm-core-utils.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-wired.h"
-#include "nm-setting-ip-tunnel.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -171,34 +169,6 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_ERROR_CANT_SET_MTU,
 } NMPlatformError;
 
-typedef enum {
-
-	/* match-flags are strictly inclusive. That means,
-	 * by default nothing is matched, but if you enable a particular
-	 * flag, a candidate that matches passes the check.
-	 *
-	 * In other words: adding more flags can only extend the result
-	 * set of matching objects.
-	 *
-	 * Also, the flags form partitions. Like, an address can be either of
-	 * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for
-	 * the ADDRSTATE match types.
-	 */
-	NM_PLATFORM_MATCH_WITH_NONE                                 = 0,
-
-	NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL                      = (1LL <<  0),
-	NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL                   = (1LL <<  1),
-	NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY                        =   NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
-	                                                              | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL,
-
-	NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL                     = (1LL <<  2),
-	NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE                  = (1LL <<  3),
-	NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED                  = (1LL <<  4),
-	NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY                       =   NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
-	                                                              | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE
-	                                                              | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED,
-} NMPlatformMatchFlags;
-
 #define NM_PLATFORM_LINK_OTHER_NETNS    (-1)
 
 #define __NMPlatformObject_COMMON \
@@ -284,8 +254,6 @@ struct _NMPlatformObject {
 	__NMPlatformObject_COMMON;
 };
 
-#define NM_PLATFORM_IP_ADDRESS_CAST(address) \
-	NM_CONSTCAST (NMPlatformIPAddress, (address), NMPlatformIPXAddress, NMPlatformIP4Address, NMPlatformIP6Address)
 
 #define __NMPlatformIPAddress_COMMON \
 	__NMPlatformObject_COMMON; \
@@ -434,7 +402,7 @@ typedef union {
 	 * do not exist from the point-of-view of platform users.
 	 * Such a route is not alive, according to nmp_object_is_alive().
 	 *
-	 * NOTE: currently we ignore all flags except RTM_F_CLONED
+	 * XXX: currently we ignore all flags except RTM_F_CLONED
 	 * and RTNH_F_ONLINK for IPv4.
 	 * We also may not properly consider the flags as part of the ID
 	 * in route-cmp. */ \
@@ -636,7 +604,6 @@ typedef struct {
 	guint8 encap_limit;
 	guint8 proto;
 	guint flow_label;
-	guint32 flags;
 } NMPlatformLnkIp6Tnl;
 
 typedef struct {
@@ -684,21 +651,6 @@ typedef struct {
 } NMPlatformLnkSit;
 
 typedef struct {
-	guint32 owner;
-	guint32 group;
-
-	guint8 type;
-
-	bool owner_valid:1;
-	bool group_valid:1;
-
-	bool pi:1;
-	bool vnet_hdr:1;
-	bool multi_queue:1;
-	bool persist:1;
-} NMPlatformLnkTun;
-
-typedef struct {
 	/* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */
 	guint16 id;
 	NMVlanFlags flags;
@@ -725,6 +677,15 @@ typedef struct {
 	bool l3miss:1;
 } NMPlatformLnkVxlan;
 
+typedef struct {
+	gint64 owner;
+	gint64 group;
+	const char *mode;
+	bool no_pi:1;
+	bool vnet_hdr:1;
+	bool multi_queue:1;
+} NMPlatformTunProperties;
+
 typedef enum {
 	NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
 	NM_PLATFORM_LINK_DUPLEX_HALF,
@@ -753,8 +714,6 @@ typedef struct {
 	gboolean (*sysctl_set) (NMPlatform *, const char *pathid, int dirfd, const char *path, const char *value);
 	char * (*sysctl_get) (NMPlatform *, const char *pathid, int dirfd, const char *path);
 
-	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
-
 	gboolean (*link_add) (NMPlatform *,
 	                      const char *name,
 	                      NMLinkType type,
@@ -850,15 +809,12 @@ typedef struct {
 	                          const NMPlatformLnkSit *props,
 	                          const NMPlatformLink **out_link);
 
-	gboolean (*link_tun_add) (NMPlatform *platform,
-	                          const char *name,
-	                          const NMPlatformLnkTun *props,
-	                          const NMPlatformLink **out_link,
-	                          int *out_fd);
-
 	gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, const NMPlatformLink **out_link);
 	gboolean (*infiniband_partition_delete) (NMPlatform *, int parent, int p_key);
 
+	gboolean (*tun_add) (NMPlatform *platform, const char *name, gboolean tap, gint64 owner, gint64 group, gboolean pi,
+	                     gboolean vnet_hdr, gboolean multi_queue, const NMPlatformLink **out_link);
+
 	gboolean    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
 	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
 	GByteArray *(*wifi_get_ssid)         (NMPlatform *, int ifindex);
@@ -1072,8 +1028,6 @@ gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char
 const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */);
 int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname);
 
-void nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type);
-
 const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
                                            int ifindex,
                                            gboolean visible_only);
@@ -1117,21 +1071,7 @@ gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex);
 guint32 nm_platform_link_get_mtu (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex);
-
 gconstpointer nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length);
-
-static inline GBytes *
-nm_platform_link_get_address_as_bytes (NMPlatform *self, int ifindex)
-{
-	gconstpointer p;
-	gsize l;
-
-	p = nm_platform_link_get_address (self, ifindex, &l);
-	return p
-	       ? g_bytes_new (p, l)
-	       : NULL;
-}
-
 int nm_platform_link_get_master (NMPlatform *self, int slave);
 
 gboolean nm_platform_link_can_assume (NMPlatform *self, int ifindex);
@@ -1143,10 +1083,6 @@ const char *nm_platform_link_get_type_name (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_refresh (NMPlatform *self, int ifindex);
 void nm_platform_process_events (NMPlatform *self);
 
-const NMPlatformLink *nm_platform_process_events_ensure_link (NMPlatform *self,
-                                                              int ifindex,
-                                                              const char *ifname);
-
 gboolean nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmware);
 gboolean nm_platform_link_set_down (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_set_arp (NMPlatform *self, int ifindex);
@@ -1196,7 +1132,6 @@ const NMPlatformLnkMacsec *nm_platform_link_get_lnk_macsec (NMPlatform *self, in
 const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacvtap *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
@@ -1224,6 +1159,16 @@ NMPlatformError nm_platform_link_vxlan_add (NMPlatform *self,
                                             const NMPlatformLnkVxlan *props,
                                             const NMPlatformLink **out_link);
 
+NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
+                                          const char *name,
+                                          gboolean tap,
+                                          gint64 owner,
+                                          gint64 group,
+                                          gboolean pi,
+                                          gboolean vnet_hdr,
+                                          gboolean multi_queue,
+                                          const NMPlatformLink **out_link);
+
 NMPlatformError nm_platform_link_infiniband_add (NMPlatform *self,
                                                  int parent,
                                                  int p_key,
@@ -1234,9 +1179,7 @@ NMPlatformError nm_platform_link_infiniband_delete (NMPlatform *self,
 gboolean nm_platform_link_infiniband_get_properties (NMPlatform *self, int ifindex, int *parent, int *p_key, const char **mode);
 
 gboolean nm_platform_link_veth_get_properties   (NMPlatform *self, int ifindex, int *out_peer_ifindex);
-gboolean nm_platform_link_tun_get_properties    (NMPlatform *self,
-                                                 int ifindex,
-                                                 NMPlatformLnkTun *out_properties);
+gboolean nm_platform_link_tun_get_properties    (NMPlatform *self, int ifindex, NMPlatformTunProperties *properties);
 
 gboolean    nm_platform_wifi_get_capabilities (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
 gboolean    nm_platform_wifi_get_bssid        (NMPlatform *self, int ifindex, guint8 *bssid);
@@ -1284,11 +1227,6 @@ NMPlatformError nm_platform_link_sit_add (NMPlatform *self,
                                           const char *name,
                                           const NMPlatformLnkSit *props,
                                           const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
-                                          const char *name,
-                                          const NMPlatformLnkTun *props,
-                                          const NMPlatformLink **out_link,
-                                          int *out_fd);
 
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
@@ -1313,8 +1251,8 @@ gboolean nm_platform_ip6_address_add (NMPlatform *self,
                                       guint32 flags);
 gboolean nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 gboolean nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
-gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses);
-gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses, gboolean full_sync);
+gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresse);
+gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GPtrArray *known_addresses, gboolean keep_link_local);
 gboolean nm_platform_ip_address_flush (NMPlatform *self,
                                        int addr_family,
                                        int ifindex);
@@ -1372,7 +1310,6 @@ const char *nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *
 const char *nm_platform_lnk_macsec_to_string (const NMPlatformLnkMacsec *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len);
-const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
 const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
@@ -1396,7 +1333,6 @@ int nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpI
 int nm_platform_lnk_macsec_cmp (const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b);
 int nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b);
 int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b);
-int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
 int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
@@ -1432,7 +1368,6 @@ void nm_platform_lnk_ipip_hash_update (const NMPlatformLnkIpIp *obj, NMHashState
 void nm_platform_lnk_macsec_hash_update (const NMPlatformLnkMacsec *obj, NMHashState *h);
 void nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHashState *h);
 void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *h);
-void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
 
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index f1092fe9..d8561aef 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -200,8 +200,8 @@ _stack_current_ns_types (NMPNetns *netns, int ns_types)
 		}
 
 		for (i = 0; i < G_N_ELEMENTS (ns_types_check); i++) {
-			if (   NM_FLAGS_ANY (ns_types, ns_types_check[i])
-			    && NM_FLAGS_ANY (info->ns_types, ns_types_check[i])) {
+			if (   NM_FLAGS_HAS (ns_types, ns_types_check[i])
+			    && NM_FLAGS_HAS (info->ns_types, ns_types_check[i])) {
 				res = NM_FLAGS_SET (res, ns_types_check[i]);
 				ns_types = NM_FLAGS_UNSET (ns_types, ns_types_check[i]);
 			}
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 29bb999b..1a9b9325 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -325,7 +325,7 @@ _vlan_xgress_qos_mappings_cmp (guint n_map,
 
 static void
 _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
-                               NMVlanQosMapping **dst_map,
+                               const NMVlanQosMapping **dst_map,
                                guint src_n_map,
                                const NMVlanQosMapping *src_map)
 {
@@ -388,7 +388,7 @@ _nmp_object_fixup_link_udev_fields (NMPObject **obj_new, NMPObject *obj_orig, gb
 	/* The link contains internal fields that are combined by
 	 * properties from netlink and udev. Update those properties */
 
-	/* When a link is not in netlink, its udev fields don't matter. */
+	/* When a link is not in netlink, it's udev fields don't matter. */
 	if (obj->_link.netlink.is_in_netlink) {
 		driver = _link_get_driver (obj->_link.udev.device,
 		                           obj->link.kind,
@@ -532,7 +532,7 @@ _nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type)
 }
 
 const NMPObject *
-nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj)
+nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj)
 {
 	const NMPClass *klass = nmp_class_from_type (obj_type);
 
@@ -865,6 +865,12 @@ _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
 	return c;
 }
 
+gboolean
+nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+	return nmp_object_cmp (obj1, obj2) == 0;
+}
+
 /* @src is a const object, which is not entirely correct for link types, where
  * we increase the ref count for src->_link.udev.device.
  * Hence, nmp_object_copy() can violate the const promise of @src.
@@ -916,11 +922,11 @@ _vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
 {
 	dst->lnk_vlan = src->lnk_vlan;
 	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_ingress_qos_map,
-	                               NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.ingress_qos_map),
+	                               &dst->_lnk_vlan.ingress_qos_map,
 	                               src->_lnk_vlan.n_ingress_qos_map,
 	                               src->_lnk_vlan.ingress_qos_map);
 	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_egress_qos_map,
-	                               NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map),
+	                               &dst->_lnk_vlan.egress_qos_map,
 	                               src->_lnk_vlan.n_egress_qos_map,
 	                               src->_lnk_vlan.egress_qos_map);
 }
@@ -1082,7 +1088,7 @@ nmp_object_id_hash (const NMPObject *obj)
 	NMHashState h;
 
 	if (!obj)
-		return nm_hash_static (914932607u);
+		return 0;
 
 	nm_hash_init (&h, 914932607u);
 	nmp_object_id_hash_update (obj, &h);
@@ -1526,6 +1532,8 @@ const NMPLookup *
 nmp_lookup_init_obj_type (NMPLookup *lookup,
                           NMPObjectType obj_type)
 {
+	NMPObject *o;
+
 	nm_assert (lookup);
 
 	switch (obj_type) {
@@ -1536,7 +1544,7 @@ nmp_lookup_init_obj_type (NMPLookup *lookup,
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
 	case NMP_OBJECT_TYPE_QDISC:
 	case NMP_OBJECT_TYPE_TFILTER:
-		_nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
+		o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
 		lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE;
 		return _L (lookup);
 	default:
@@ -1768,54 +1776,6 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 
 /*****************************************************************************/
 
-static NMDedupMultiIdxMode
-_obj_get_add_mode (const NMPObject *obj)
-{
-	/* new objects are usually appended to the list. Except for
-	 * addresses, which are prepended during `ip address add`.
-	 *
-	 * Actually, for routes it is more complicated, because depending on
-	 * `ip route append`, `ip route replace`, `ip route prepend`, the object
-	 * will be added at the tail, at the front, or even replace an element
-	 * in the list. However, that is handled separately by nmp_cache_update_netlink_route()
-	 * and of no concern here. */
-	if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj),
-	               NMP_OBJECT_TYPE_IP4_ADDRESS,
-	               NMP_OBJECT_TYPE_IP6_ADDRESS))
-		return NM_DEDUP_MULTI_IDX_MODE_PREPEND;
-	return NM_DEDUP_MULTI_IDX_MODE_APPEND;
-}
-
-static void
-_idxcache_update_order_for_dump (NMPCache *cache,
-                                 const NMDedupMultiEntry *entry)
-{
-	const NMPClass *klass;
-	const guint8 *i_idx_type;
-	const NMDedupMultiEntry *entry2;
-
-	nm_dedup_multi_entry_reorder (entry, NULL, TRUE);
-
-	klass = NMP_OBJECT_GET_CLASS (entry->obj);
-	for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) {
-		NMPCacheIdType id_type = *i_idx_type;
-
-		if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE)
-			continue;
-
-		entry2 = nm_dedup_multi_index_lookup_obj (cache->multi_idx,
-		                                          _idx_type_get (cache, id_type),
-		                                          entry->obj);
-		if (!entry2)
-			continue;
-
-		nm_assert (entry2 != entry);
-		nm_assert (entry2->obj == entry->obj);
-
-		nm_dedup_multi_entry_reorder (entry2, NULL, TRUE);
-	}
-}
-
 static void
 _idxcache_update_other_cache_ids (NMPCache *cache,
                                   NMPCacheIdType cache_id_type,
@@ -1875,7 +1835,7 @@ _idxcache_update_other_cache_ids (NMPCache *cache,
 		                               obj_new,
 		                               is_dump
 		                                 ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
-		                                 : _obj_get_add_mode (obj_new),
+		                                 : NM_DEDUP_MULTI_IDX_MODE_APPEND,
 		                               is_dump
 		                                 ? NULL
 		                                 : entry_order,
@@ -1953,7 +1913,7 @@ _idxcache_update (NMPCache *cache,
 		                               obj_new,
 		                               is_dump
 		                                 ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
-		                                 : _obj_get_add_mode (obj_new),
+		                                 : NM_DEDUP_MULTI_IDX_MODE_APPEND,
 		                               NULL,
 		                               entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
 		                               NULL,
@@ -2210,8 +2170,6 @@ nmp_cache_update_netlink (NMPCache *cache,
 	}
 
 	if (nmp_object_equal (obj_old, obj_hand_over)) {
-		if (is_dump)
-			_idxcache_update_order_for_dump (cache, entry_old);
 		nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
 		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
 		return NMP_CACHE_OPS_UNCHANGED;
@@ -2285,8 +2243,6 @@ nmp_cache_update_netlink_route (NMPCache *cache,
 	}
 
 	if (nmp_object_equal (entry_old->obj, obj_hand_over)) {
-		if (is_dump)
-			_idxcache_update_order_for_dump (cache, entry_old);
 		nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
 		goto update_done;
 	}
@@ -2310,8 +2266,9 @@ update_done:
 	 * properly find @obj_replaced. */
 	resync_required = FALSE;
 	entry_replace = NULL;
-	if (is_dump)
+	if (is_dump) {
 		goto out;
+	}
 
 	if (!entry_new) {
 		if (   NM_FLAGS_HAS (nlmsgflags, NLM_F_REPLACE)
@@ -2326,8 +2283,6 @@ update_done:
 		goto out;
 	}
 
-	/* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID
-	 * index. For all other indexes their order becomes messed up. */
 	entry_cur = _lookup_entry_with_idx_type (cache,
 	                                         NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
 	                                         entry_new->obj);
@@ -2780,17 +2735,6 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_sit_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp,
 	},
-	[NMP_OBJECT_TYPE_LNK_TUN - 1] = {
-		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
-		.obj_type                           = NMP_OBJECT_TYPE_LNK_TUN,
-		.sizeof_data                        = sizeof (NMPObjectLnkTun),
-		.sizeof_public                      = sizeof (NMPlatformLnkTun),
-		.obj_type_name                      = "tun",
-		.lnk_link_type                      = NM_LINK_TYPE_TUN,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_tun_to_string,
-		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_tun_hash_update,
-		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_tun_cmp,
-	},
 	[NMP_OBJECT_TYPE_LNK_VLAN - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_VLAN,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index f473f462..e17b17b0 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -88,7 +88,7 @@ typedef enum { /*< skip >*/
 	/* Consider all the destination fields of a route, that is, the ID without the ifindex
 	 * and gateway (meaning: network/plen,metric).
 	 * The reason for this is that `ip route change` can replace an existing route
-	 * and modify its ifindex/gateway. Effectively, that means it deletes an existing
+	 * and modify it's ifindex/gateway. Effectively, that means it deletes an existing
 	 * route and adds a different one (as the ID of the route changes). However, it only
 	 * sends one RTM_NEWADDR notification without notifying about the deletion. We detect
 	 * that by having this index to contain overlapping routes which require special
@@ -197,10 +197,6 @@ typedef struct {
 } NMPObjectLnkSit;
 
 typedef struct {
-	NMPlatformLnkTun _public;
-} NMPObjectLnkTun;
-
-typedef struct {
 	NMPlatformLnkVlan _public;
 
 	guint n_ingress_qos_map;
@@ -269,9 +265,6 @@ struct _NMPObject {
 		NMPlatformLnkSit        lnk_sit;
 		NMPObjectLnkSit         _lnk_sit;
 
-		NMPlatformLnkTun        lnk_tun;
-		NMPObjectLnkTun         _lnk_tun;
-
 		NMPlatformLnkVlan       lnk_vlan;
 		NMPObjectLnkVlan        _lnk_vlan;
 
@@ -467,8 +460,6 @@ nmp_object_ref (const NMPObject *obj)
 static inline void
 nmp_object_unref (const NMPObject *obj)
 {
-	nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
-
 	nm_dedup_multi_obj_unref ((const NMDedupMultiObj *) obj);
 }
 
@@ -490,7 +481,7 @@ nmp_object_unref (const NMPObject *obj)
 NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob);
 NMPObject *nmp_object_new_link (int ifindex);
 
-const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj);
+const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj);
 
 static inline NMPObject *
 nmp_object_stackinit_obj (NMPObject *obj, const NMPObject *src)
@@ -508,13 +499,7 @@ const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifinde
 const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size);
 void nmp_object_hash_update (const NMPObject *obj, NMHashState *h);
 int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2);
-
-static inline gboolean
-nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
-{
-	return nmp_object_cmp (obj1, obj2) == 0;
-}
-
+gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2);
 void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only);
 NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only);
 
@@ -739,16 +724,6 @@ const NMDedupMultiEntry *nm_platform_lookup_entry (NMPlatform *platform,
                                                    NMPCacheIdType cache_id_type,
                                                    const NMPObject *obj);
 
-static inline const NMPObject *
-nm_platform_lookup_obj (NMPlatform *platform,
-                        NMPCacheIdType cache_id_type,
-                        const NMPObject *obj)
-{
-	return nm_dedup_multi_entry_get_obj (nm_platform_lookup_entry (platform,
-	                                                               cache_id_type,
-	                                                               obj));
-}
-
 static inline const NMDedupMultiHeadEntry *
 nm_platform_lookup_obj_type (NMPlatform *platform,
                              NMPObjectType obj_type)
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
deleted file mode 100644
index 0571efac..00000000
--- a/src/platform/tests/meson.build
+++ /dev/null
@@ -1,37 +0,0 @@
-test_units = [
-  ['test-link-fake', 'test-link.c', 60],
-  ['test-link-linux', 'test-link.c', 60],
-  ['test-address-fake', 'test-address.c'],
-  ['test-address-linux', 'test-address.c'],
-  ['test-general', 'test-general.c'],
-  ['test-nmp-object', 'test-nmp-object.c'],
-  ['test-route-fake', 'test-route.c'],
-  ['test-route-linux', 'test-route.c'],
-  ['test-cleanup-fake', 'test-cleanup.c'],
-  ['test-cleanup-linux', 'test-cleanup.c'],
-]
-
-foreach test_unit: test_units
-  exe = executable(
-    'platform-' + test_unit[0],
-    test_unit[1],
-    dependencies: test_nm_dep,
-    c_args: test_cflags_platform
-  )
-
-  test(
-    'platform/' + test_unit[0],
-    test_script,
-    timeout: test_unit.length() > 2 ? test_unit[2] : 30,
-    args: test_args + [exe.full_path()]
-  )
-endforeach
-
-test = 'monitor'
-
-executable(
-  test,
-  test + '.c',
-  dependencies: test_nm_dep,
-  c_args: test_cflags_platform
-)
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index f0e3e6cf..e1220052 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -1,5 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
+/* NetworkManager audit support
+ *
  * 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
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index ddef8853..93851ff7 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -1,5 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
+/* NetworkManager audit support
+ *
  * 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
@@ -148,7 +149,7 @@ test_ip6_address_general (void)
 
 	/* Add address again (aka update) */
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
-	accept_signals (address_changed, 0, 2);
+	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
 	addresses = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index a213b31f..937cd12c 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -1,5 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
+/* NetworkManager audit support
+ *
  * 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
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 7885c083..d56e681e 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1,5 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
+/* NetworkManager audit support
+ *
  * 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
@@ -23,7 +24,6 @@
 #include <sched.h>
 #include <sys/wait.h>
 #include <fcntl.h>
-#include <linux/if_tun.h>
 
 #include "test-common.h"
 
@@ -628,10 +628,7 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 const NMPlatformLink *
 nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms)
 {
-	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type,
-	                                   timeout_ms
-	                                     ? nm_utils_get_monotonic_timestamp_ms () + timeout_ms
-	                                     : 0);
+	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
 }
 
 const NMPlatformLink *
@@ -639,7 +636,6 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 {
 	const NMPlatformLink *plink;
 	gint64 now;
-	gboolean waited_once = FALSE;
 
 	_init_platform (&platform, FALSE);
 
@@ -651,24 +647,29 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 		    && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type))
 			return plink;
 
-		if (until_ms == 0) {
-			/* don't wait, don't even poll the socket. */
-			return NULL;
-		}
-
-		if (   waited_once
-		    && until_ms < now) {
-			/* timeout reached (+ we already waited for a signal at least once). */
+		if (until_ms < now)
 			return NULL;
-		}
 
-		waited_once = TRUE;
-		/* regardless of whether timeout is already reached, we poll the netlink
-		 * socket a bit. */
 		nmtstp_wait_for_signal (platform, until_ms - now);
 	}
 }
 
+const NMPlatformLink *
+nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+{
+	return nmtstp_assert_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+}
+
+const NMPlatformLink *
+nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+{
+	const NMPlatformLink *plink;
+
+	plink = nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms);
+	g_assert (plink);
+	return plink;
+}
+
 /*****************************************************************************/
 
 int
@@ -1283,10 +1284,6 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
 	char buffer[INET6_ADDRSTRLEN];
-	char encap[20];
-	char tclass[20];
-	gboolean encap_ignore;
-	gboolean tclass_inherit;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1312,18 +1309,15 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 			g_assert_not_reached ();
 		}
 
-		encap_ignore = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_IGN_ENCAP_LIMIT);
-		tclass_inherit = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS);
-
-		success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %s encaplimit %s flowlabel %x",
+		success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %02x encaplimit %u flowlabel %x",
 		                                name,
 		                                mode,
 		                                dev,
 		                                nm_utils_inet6_ntop (&lnk->local, NULL),
 		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
 		                                lnk->ttl,
-		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
-		                                encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit),
+		                                lnk->tclass,
+		                                lnk->encap_limit,
 		                                lnk->flow_label);
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IP6TNL, 100);
@@ -1469,70 +1463,6 @@ nmtstp_link_sit_add (NMPlatform *platform,
 }
 
 const NMPlatformLink *
-nmtstp_link_tun_add (NMPlatform *platform,
-                     gboolean external_command,
-                     const char *name,
-                     const NMPlatformLnkTun *lnk,
-                     int *out_fd)
-{
-	const NMPlatformLink *pllink = NULL;
-	NMPlatformError plerr;
-	int err;
-
-	g_assert (nm_utils_is_valid_iface_name (name, NULL));
-	g_assert (lnk);
-	g_assert (NM_IN_SET (lnk->type, IFF_TUN, IFF_TAP));
-	g_assert (!out_fd || *out_fd == -1);
-
-	if (!lnk->persist) {
-		/* ip tuntap does not support non-persistent devices.
-		 *
-		 * Add this device only via NMPlatform. */
-		if (external_command == -1)
-			external_command = FALSE;
-	}
-
-	external_command = nmtstp_run_command_check_external (external_command);
-
-	_init_platform (&platform, external_command);
-
-	if (external_command) {
-		g_assert (lnk->persist);
-
-		err = nmtstp_run_command ("ip tuntap add"
-		                          " mode %s"
-		                          "%s" /* user */
-		                          "%s" /* group */
-		                          "%s" /* pi */
-		                          "%s" /* vnet_hdr */
-		                          "%s" /* multi_queue */
-		                          " name %s",
-		                          lnk->type == IFF_TUN ? "tun" : "tap",
-		                          lnk->owner_valid ? nm_sprintf_bufa (100, " user %u", (guint) lnk->owner) : "",
-		                          lnk->group_valid ? nm_sprintf_bufa (100, " group %u", (guint) lnk->group) : "",
-		                          lnk->pi ? " pi" : "",
-		                          lnk->vnet_hdr ? " vnet_hdr" : "",
-		                          lnk->multi_queue ? " multi_queue" : "",
-		                          name);
-		/* Older versions of iproute2 don't support adding  devices.
-		 * On failure, fallback to using platform code. */
-		if (err == 0)
-			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_TUN, 100);
-		else
-			g_error ("failure to add tun/tap device via ip-route");
-	} else {
-		g_assert (lnk->persist || out_fd);
-		plerr = nm_platform_link_tun_add (platform, name, lnk, &pllink, out_fd);
-		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
-	}
-
-	g_assert (pllink);
-	g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_TUN);
-	g_assert_cmpstr (pllink->name, ==, name);
-	return pllink;
-}
-
-const NMPlatformLink *
 nmtstp_link_vxlan_add (NMPlatform *platform,
                        gboolean external_command,
                        const char *name,
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index bd02b0d7..fb406a3f 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -1,21 +1,3 @@
-/*
- * 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.
- *
- * Copyright 2016 - 2017 Red Hat, Inc.
- */
-
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
@@ -129,11 +111,8 @@ const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const ch
 			g_assert_not_reached (); \
 	} G_STMT_END
 
-#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_ms) \
-	nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_ms))
-
-#define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \
-	nmtst_assert_nonnull (nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms))
+const NMPlatformLink *nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
 /*****************************************************************************/
 
@@ -310,11 +289,6 @@ const NMPlatformLink *nmtstp_link_sit_add (NMPlatform *platform,
                                            gboolean external_command,
                                            const char *name,
                                            const NMPlatformLnkSit *lnk);
-const NMPlatformLink *nmtstp_link_tun_add (NMPlatform *platform,
-                                           gboolean external_command,
-                                           const char *name,
-                                           const NMPlatformLnkTun *lnk,
-                                           int *out_fd);
 const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform,
                                              gboolean external_command,
                                              const char *name,
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index dcd600ee..ef78cc24 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1,5 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
+/* NetworkManager audit support
+ *
  * 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
@@ -23,7 +24,6 @@
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <linux/if_tun.h>
 
 #include "platform/nmp-object.h"
 #include "platform/nmp-netns.h"
@@ -698,8 +698,6 @@ test_software_detect (gconstpointer user_data)
 	const NMPObject *lnk;
 	guint i_step;
 	const gboolean ext = test_data->external_command;
-	NMPlatformLnkTun lnk_tun;
-	nm_auto_close int tun_fd = -1;
 
 	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
 	ifindex_parent = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
@@ -763,27 +761,13 @@ test_software_detect (gconstpointer user_data)
 			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_tunnel", NULL) != 0;
 		}
 
-		switch (test_data->test_mode) {
-		case 0:
-			lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15");
-			lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16");
-			lnk_ip6tnl.parent_ifindex = ifindex_parent;
-			lnk_ip6tnl.tclass = 20;
-			lnk_ip6tnl.encap_limit = 6;
-			lnk_ip6tnl.flow_label = 1337;
-			lnk_ip6tnl.proto = IPPROTO_IPV6;
-			break;
-		case 1:
-			lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::17");
-			lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::18");
-			lnk_ip6tnl.parent_ifindex = ifindex_parent;
-			lnk_ip6tnl.tclass = 0;
-			lnk_ip6tnl.encap_limit = 0;
-			lnk_ip6tnl.flow_label = 1338;
-			lnk_ip6tnl.proto = IPPROTO_IPV6;
-			lnk_ip6tnl.flags = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS;
-			break;
-		}
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.tclass = 20;
+		lnk_ip6tnl.encap_limit = 6;
+		lnk_ip6tnl.flow_label = 1337;
+		lnk_ip6tnl.proto = IPPROTO_IPV6;
 
 		if (!nmtstp_link_ip6tnl_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
 			if (gracefully_skip) {
@@ -895,38 +879,6 @@ test_software_detect (gconstpointer user_data)
 		g_assert (nmtstp_link_vxlan_add (NULL, ext, DEVICE_NAME, &lnk_vxlan));
 		break;
 	}
-	case NM_LINK_TYPE_TUN: {
-		gboolean owner_valid = nmtst_get_rand_bool ();
-		gboolean group_valid = nmtst_get_rand_bool ();
-
-		switch (test_data->test_mode) {
-		case 0:
-			lnk_tun = (NMPlatformLnkTun) {
-				.type = nmtst_get_rand_bool () ? IFF_TUN : IFF_TAP,
-				.owner = owner_valid ? getuid () : 0,
-				.owner_valid = owner_valid,
-				.group = group_valid ? getgid () : 0,
-				.group_valid = group_valid,
-				.pi = nmtst_get_rand_bool (),
-				.vnet_hdr = nmtst_get_rand_bool (),
-				.multi_queue = nmtst_get_rand_bool (),
-
-				/* if we add the device via iproute2 (external), we can only
-				 * create persistent devices. */
-				.persist = (ext == 1) ? TRUE : nmtst_get_rand_bool (),
-			};
-			break;
-		default:
-			g_assert_not_reached ();
-			break;
-		}
-
-		g_assert (nmtstp_link_tun_add (NULL, ext, DEVICE_NAME, &lnk_tun,
-		                               (!lnk_tun.persist || nmtst_get_rand_bool ())
-		                                 ? &tun_fd
-		                                 : NULL));
-		break;
-	}
 	default:
 		g_assert_not_reached ();
 	}
@@ -957,13 +909,7 @@ test_software_detect (gconstpointer user_data)
 		lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, test_data->link_type, &plink);
 		g_assert (plink);
 		g_assert_cmpint (plink->ifindex, ==, ifindex);
-
-		if (   !lnk
-		    && test_data->link_type == NM_LINK_TYPE_TUN) {
-			/* this is ok. Kernel apparently does not support tun properties via netlink. We
-			 * fetch them from sysfs below. */
-		} else
-			g_assert (lnk);
+		g_assert (lnk);
 
 		switch (test_data->link_type) {
 		case NM_LINK_TYPE_GRE: {
@@ -985,31 +931,15 @@ test_software_detect (gconstpointer user_data)
 		case NM_LINK_TYPE_IP6TNL: {
 			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
 
-			switch (test_data->test_mode) {
-			case 0:
-				g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
-				g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
-				nmtst_assert_ip6_address (&plnk->local, "fd01::15");
-				nmtst_assert_ip6_address (&plnk->remote, "fd01::16");
-				g_assert_cmpint (plnk->ttl, ==, 0);
-				g_assert_cmpint (plnk->tclass, ==, 20);
-				g_assert_cmpint (plnk->encap_limit, ==, 6);
-				g_assert_cmpint (plnk->flow_label, ==, 1337);
-				g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
-				break;
-			case 1:
-				g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
-				g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
-				nmtst_assert_ip6_address (&plnk->local, "fd01::17");
-				nmtst_assert_ip6_address (&plnk->remote, "fd01::18");
-				g_assert_cmpint (plnk->ttl, ==, 0);
-				g_assert_cmpint (plnk->flow_label, ==, 1338);
-				g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
-				g_assert_cmpint (plnk->flags & 0xFFFF, /* ignore kernel internal flags */
-				                 ==,
-				                 IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS);
-				break;
-			}
+			g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fd01::15");
+			nmtst_assert_ip6_address (&plnk->remote, "fd01::16");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tclass, ==, 20);
+			g_assert_cmpint (plnk->encap_limit, ==, 6);
+			g_assert_cmpint (plnk->flow_label, ==, 1337);
+			g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
 			break;
 		}
 		case NM_LINK_TYPE_IPIP: {
@@ -1052,27 +982,6 @@ test_software_detect (gconstpointer user_data)
 			g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE);
 			break;
 		}
-		case NM_LINK_TYPE_TUN: {
-			const NMPlatformLnkTun *plnk;
-			NMPlatformLnkTun lnk_tun2;
-
-			g_assert ((lnk ? &lnk->lnk_tun : NULL) == nm_platform_link_get_lnk_tun (NM_PLATFORM_GET, ifindex, NULL));
-
-			/* kernel might not expose tun options via netlink. Either way, try
-			 * to read them (either from platform cache, or fallback to sysfs).
-			 * See also: rh#1547213. */
-			if (!nm_platform_link_tun_get_properties (NM_PLATFORM_GET,
-			                                          ifindex,
-			                                          &lnk_tun2))
-				g_assert_not_reached ();
-
-			plnk = lnk ? &lnk->lnk_tun : &lnk_tun2;
-			if (lnk)
-				g_assert (memcmp (plnk, &lnk_tun2, sizeof (NMPlatformLnkTun)) == 0);
-
-			g_assert (nm_platform_lnk_tun_cmp (plnk, &lnk_tun) == 0);
-			break;
-		}
 		case NM_LINK_TYPE_VLAN: {
 			const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan;
 
@@ -2339,7 +2248,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 		p = pl_base;
 		for (j = nstack; j >= 1; ) {
 			j--;
-			if (NM_FLAGS_ANY (stack[j].ns_types, ns_type)) {
+			if (NM_FLAGS_HAS (stack[j].ns_types, ns_type)) {
 				p = stack[j].pl;
 				break;
 			}
@@ -2637,13 +2546,11 @@ _nmtstp_setup_tests (void)
 		g_test_add_func ("/link/external", test_external);
 
 		test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0);
-		test_software_detect_add ("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0);
-		test_software_detect_add ("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1);
+		test_software_detect_add ("/link/software/detect/ip6tnl", NM_LINK_TYPE_IP6TNL, 0);
 		test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
 		test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
 		test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
 		test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0);
-		test_software_detect_add ("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0);
 		test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1);
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 047d7a85..3228de83 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -54,20 +54,27 @@ test_obj_base (void)
 	gs_unref_object GCancellable *obj_cancellable = g_cancellable_new ();
 	nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link (10);
 
-	g_assert (&g->g_type_instance              == (void *) &o->_class);
-	g_assert (&g->g_type_instance.g_class      == (void *) &o->_class);
+#define STATIC_ASSERT(cond) \
+	G_STMT_START { \
+		G_STATIC_ASSERT (cond); \
+		G_STATIC_ASSERT_EXPR (cond); \
+		g_assert (cond); \
+	} G_STMT_END
 
-	g_assert (sizeof (o->parent.parent)        == sizeof (GTypeInstance));
+	STATIC_ASSERT (&g->g_type_instance              == (void *) &o->_class);
+	STATIC_ASSERT (&g->g_type_instance.g_class      == (void *) &o->_class);
 
-	g_assert (&c->parent                       == (void *) c);
-	g_assert (&c->parent.parent.g_type_class   == (void *) c);
-	g_assert (&c->parent.parent.g_type         == (void *) c);
-	g_assert (&c->parent.parent.g_type         == &k->g_type);
+	STATIC_ASSERT (sizeof (o->parent.parent)        == sizeof (GTypeInstance));
 
-	g_assert (sizeof (c->parent.parent)        == sizeof (GTypeClass));
+	STATIC_ASSERT (&c->parent                       == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type_class   == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type         == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type         == &k->g_type);
 
-	g_assert (&o->parent                       == (void *) o);
-	g_assert (&o->parent.klass                 == (void *) &o->_class);
+	STATIC_ASSERT (sizeof (c->parent.parent)        == sizeof (GTypeClass));
+
+	STATIC_ASSERT (&o->parent                       == (void *) o);
+	STATIC_ASSERT (&o->parent.klass                 == (void *) &o->_class);
 
 	obj = (NMObjBaseInst *) obj_cancellable;
 	g_assert (!NMP_CLASS_IS_VALID ((NMPClass *) obj->klass));
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 85b14b57..13648f16 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -1,5 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
+/* NetworkManager audit support
+ *
  * 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
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c
index db187a1f..a5f25b02 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/wifi-utils-nl80211.c
@@ -22,17 +22,17 @@
 
 #include "nm-default.h"
 
-#include "wifi-utils-nl80211.h"
-
 #include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
 #include <linux/nl80211.h>
 
-#include "platform/nm-netlink.h"
 #include "wifi-utils-private.h"
+#include "wifi-utils-nl80211.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
 #include "nm-utils.h"
@@ -46,14 +46,223 @@
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
+/*****************************************************************************/
+
+static int
+_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+               const struct nla_policy *policy)
+{
+	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
+}
+#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
+
+static int
+_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+                      const struct nla_policy *policy)
+{
+	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
+}
+#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
+
+/*****************************************************************************
+ * Copied from libnl3/genl:
+ *****************************************************************************/
+
+static 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;
+}
+
+static void *
+genlmsg_data (const struct genlmsghdr *gnlh)
+{
+	return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+static void *
+genlmsg_user_hdr (const struct genlmsghdr *gnlh)
+{
+	return genlmsg_data (gnlh);
+}
+
+static struct genlmsghdr *
+genlmsg_hdr (struct nlmsghdr *nlh)
+{
+	return nlmsg_data (nlh);
+}
+
+static void *
+genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen)
+{
+	return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen);
+}
+
+static struct nlattr *
+genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_user_data (gnlh, hdrlen);
+}
+
+static 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);
+}
+
+static int
+genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen);
+}
+
+static 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;
+}
+
+static 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);
+}
+
+/*****************************************************************************
+ * Reimplementation of libnl3/genl functions:
+ *****************************************************************************/
+
+static int
+probe_response (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;
+}
+
+static int
+genl_ctrl_resolve (struct nl_sock *sk, const char *name)
+{
+	struct nl_msg *msg;
+	struct nl_cb *cb, *orig;
+	int rc;
+	int result = -NLE_OBJ_NOTFOUND;
+	gint32 response_data = -1;
+
+	if (!(orig = nl_socket_get_cb (sk)))
+		goto out;
+
+	cb = nl_cb_clone (orig);
+	nl_cb_put (orig);
+	if (!cb)
+		goto out;
+
+	msg = nlmsg_alloc ();
+	if (!msg)
+		goto out_cb_free;
+
+	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
+	                  0, 0, CTRL_CMD_GETFAMILY, 1))
+		goto out_msg_free;
+
+	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
+		goto out_msg_free;
+
+	rc = nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, probe_response, &response_data);
+	if (rc < 0)
+		goto out_msg_free;
+
+	rc = nl_send_auto_complete (sk, msg);
+	if (rc < 0)
+		goto out_msg_free;
+
+	rc = nl_recvmsgs (sk, cb);
+	if (rc < 0)
+		goto out_msg_free;
+
+	/* If search was successful, request may be ACKed after data */
+	rc = nl_wait_for_ack (sk);
+	if (rc < 0)
+		goto out_msg_free;
+
+	if (response_data > 0)
+		result = response_data;
+
+out_msg_free:
+	nlmsg_free (msg);
+out_cb_free:
+	nl_cb_put (cb);
+out:
+	if (result >= 0)
+		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: resolved \"%s\" as 0x%x", name, result);
+	else
+		_LOGE (LOGD_WIFI, "genl_ctrl_resolve: failed resolve \"%s\"", name);
+	return result;
+}
+
+/*****************************************************************************
+ * </libn-genl-3>
+ *****************************************************************************/
+
 typedef struct {
 	WifiData parent;
 	struct nl_sock *nl_sock;
-	guint32 *freqs;
 	int id;
+	struct nl_cb *nl_cb;
+	guint32 *freqs;
 	int num_freqs;
 	int phy;
-	bool can_wowlan:1;
 } WifiDataNl80211;
 
 static int
@@ -83,16 +292,19 @@ error_handler (struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
 static struct nl_msg *
 _nl80211_alloc_msg (int id, int ifindex, int phy, guint32 cmd, guint32 flags)
 {
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 
 	msg = nlmsg_alloc ();
-	genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0);
-	NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex);
-	if (phy != -1)
-		NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy);
-	return g_steal_pointer (&msg);
+	if (msg) {
+		genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0);
+		NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex);
+		if (phy != -1)
+			NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy);
+	}
+	return msg;
 
-nla_put_failure:
+ nla_put_failure:
+	nlmsg_free (msg);
 	return NULL;
 }
 
@@ -102,37 +314,42 @@ nl80211_alloc_msg (WifiDataNl80211 *nl80211, guint32 cmd, guint32 flags)
 	return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags);
 }
 
+/* NOTE: this function consumes 'msg' */
 static int
 _nl80211_send_and_recv (struct nl_sock *nl_sock,
+                        struct nl_cb *nl_cb,
                         struct nl_msg *msg,
                         int (*valid_handler) (struct nl_msg *, void *),
                         void *valid_data)
 {
-	int err;
-	int done = 0;
-	const struct nl_cb cb = {
-		.err_cb     = error_handler,
-		.err_arg    = &done,
-		.finish_cb  = finish_handler,
-		.finish_arg = &done,
-		.ack_cb     = ack_handler,
-		.ack_arg    = &done,
-		.valid_cb   = valid_handler,
-		.valid_arg  = valid_data,
-	};
+	struct nl_cb *cb;
+	int err, done;
 
 	g_return_val_if_fail (msg != NULL, -ENOMEM);
 
-	err = nl_send_auto (nl_sock, msg);
+	cb = nl_cb_clone (nl_cb);
+	if (!cb) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = nl_send_auto_complete (nl_sock, msg);
 	if (err < 0)
-		return err;
+		goto out;
+
+	done = 0;
+	nl_cb_err (cb, NL_CB_CUSTOM, error_handler, &done);
+	nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &done);
+	nl_cb_set (cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &done);
+	if (valid_handler)
+		nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data);
 
 	/* Loop until one of our NL callbacks says we're done; on success
 	 * done will be 1, on error it will be < 0.
 	 */
 	while (!done) {
-		err = nl_recvmsgs (nl_sock, &cb);
-		if (err < 0 && err != -EAGAIN) {
+		err = nl_recvmsgs (nl_sock, cb);
+		if (err && err != -NLE_AGAIN) {
 			/* Kernel scan list can change while we are dumping it, as new scan
 			 * results from H/W can arrive. BSS info is assured to be consistent
 			 * and we don't need consistent view of whole scan list. Hence do
@@ -147,9 +364,12 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 			break;
 		}
 	}
-
-	if (err >= 0 && done < 0)
+	if (err == 0 && done < 0)
 		err = done;
+
+ out:
+	nl_cb_put (cb);
+	nlmsg_free (msg);
 	return err;
 }
 
@@ -159,7 +379,7 @@ nl80211_send_and_recv (WifiDataNl80211 *nl80211,
                        int (*valid_handler) (struct nl_msg *, void *),
                        void *valid_data)
 {
-	return _nl80211_send_and_recv (nl80211->nl_sock, msg,
+	return _nl80211_send_and_recv (nl80211->nl_sock, nl80211->nl_cb, msg,
 	                               valid_handler, valid_data);
 }
 
@@ -170,6 +390,8 @@ wifi_nl80211_deinit (WifiData *parent)
 
 	if (nl80211->nl_sock)
 		nl_socket_free (nl80211->nl_sock);
+	if (nl80211->nl_cb)
+		nl_cb_put (nl80211->nl_cb);
 	g_free (nl80211->freqs);
 }
 
@@ -213,12 +435,12 @@ wifi_nl80211_get_mode (WifiData *data)
 	struct nl80211_iface_info iface_info = {
 		.mode = NM_802_11_MODE_UNKNOWN,
 	};
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_INTERFACE, 0);
 
 	if (nl80211_send_and_recv (nl80211, msg, nl80211_iface_info_handler,
-	                           &iface_info) < 0)
+				   &iface_info) < 0)
 		return NM_802_11_MODE_UNKNOWN;
 
 	return iface_info.mode;
@@ -228,7 +450,7 @@ static gboolean
 wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 	int err;
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_INTERFACE, 0);
@@ -248,7 +470,7 @@ wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
 	}
 
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err >= 0;
+	return err ? FALSE : TRUE;
 
  nla_put_failure:
 	nlmsg_free (msg);
@@ -259,14 +481,14 @@ static gboolean
 wifi_nl80211_set_powersave (WifiData *data, guint32 powersave)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 	int err;
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_POWER_SAVE, 0);
 	NLA_PUT_U32 (msg, NL80211_ATTR_PS_STATE,
 	             powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED);
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err >= 0;
+	return err ? FALSE : TRUE;
 
 nla_put_failure:
 	nlmsg_free (msg);
@@ -296,7 +518,7 @@ struct nl80211_bss_info {
 	gboolean valid;
 };
 
-#define WLAN_EID_SSID 0
+#define WLAN_EID_SSID	0
 
 static void
 find_ssid (guint8 *ies, guint32 ies_len,
@@ -396,7 +618,7 @@ static void
 nl80211_get_bss_info (WifiDataNl80211 *nl80211,
                       struct nl80211_bss_info *bss_info)
 {
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 
 	memset (bss_info, 0, sizeof (*bss_info));
 
@@ -520,7 +742,7 @@ static void
 nl80211_get_ap_info (WifiDataNl80211 *nl80211,
                      struct nl80211_station_info *sta_info)
 {
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 	struct nl80211_bss_info bss_info;
 
 	memset (sta_info, 0, sizeof (*sta_info));
@@ -568,38 +790,35 @@ wifi_nl80211_get_qual (WifiData *data)
 	return sta_info.signal;
 }
 
+#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS
 static gboolean
 wifi_nl80211_indicate_addressing_running (WifiData *data, gboolean running)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 	int err;
 
 	msg = nl80211_alloc_msg (nl80211,
-	                         running
-	                           ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */
-	                           : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */,
+	                         running ? NL80211_CMD_CRIT_PROTOCOL_START :
+	                         NL80211_CMD_CRIT_PROTOCOL_STOP,
 	                         0);
 	/* Despite the DHCP name, we're using this for any type of IP addressing,
 	 * DHCPv4, DHCPv6, and IPv6 SLAAC.
 	 */
-	NLA_PUT_U16 (msg,
-	             179 /* NL80211_ATTR_CRIT_PROT_ID */,
-	             1 /* NL80211_CRIT_PROTO_DHCP */);
+	NLA_PUT_U16 (msg, NL80211_ATTR_CRIT_PROT_ID, NL80211_CRIT_PROTO_DHCP);
 	if (running) {
 		/* Give DHCP 5 seconds to complete */
-		NLA_PUT_U16 (msg,
-		             180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */,
-		             5000);
+		NLA_PUT_U16 (msg, NL80211_ATTR_MAX_CRIT_PROT_DURATION, 5000);
 	}
 
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err >= 0;
+	return err ? FALSE : TRUE;
 
 nla_put_failure:
 	nlmsg_free (msg);
 	return FALSE;
 }
+#endif
 
 struct nl80211_wowlan_info {
 	gboolean enabled;
@@ -628,14 +847,12 @@ static gboolean
 wifi_nl80211_get_wowlan (WifiData *data)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 	struct nl80211_wowlan_info info;
 
-	if (!nl80211->can_wowlan)
-		return FALSE;
-
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
 	nl80211_send_and_recv (nl80211, msg, nl80211_wowlan_handler, &info);
+
 	return info.enabled;
 }
 
@@ -845,22 +1062,8 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 WifiData *
 wifi_nl80211_init (int ifindex)
 {
-	static const WifiDataClass klass = {
-		.struct_size = sizeof (WifiDataNl80211),
-		.get_mode = wifi_nl80211_get_mode,
-		.set_mode = wifi_nl80211_set_mode,
-		.set_powersave = wifi_nl80211_set_powersave,
-		.get_freq = wifi_nl80211_get_freq,
-		.find_freq = wifi_nl80211_find_freq,
-		.get_bssid = wifi_nl80211_get_bssid,
-		.get_rate = wifi_nl80211_get_rate,
-		.get_qual = wifi_nl80211_get_qual,
-		.get_wowlan = wifi_nl80211_get_wowlan,
-		.indicate_addressing_running = wifi_nl80211_indicate_addressing_running,
-		.deinit = wifi_nl80211_deinit,
-	};
 	WifiDataNl80211 *nl80211;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nl_msg *msg;
 	struct nl80211_device_info device_info = {};
 	char ifname[IFNAMSIZ];
 
@@ -870,7 +1073,19 @@ wifi_nl80211_init (int ifindex)
 		nm_sprintf_buf (ifname, "if %d", ifindex);
 	}
 
-	nl80211 = wifi_data_new (&klass, ifindex);
+	nl80211 = wifi_data_new (ifindex, sizeof (*nl80211));
+	nl80211->parent.get_mode = wifi_nl80211_get_mode;
+	nl80211->parent.set_mode = wifi_nl80211_set_mode;
+	nl80211->parent.set_powersave = wifi_nl80211_set_powersave;
+	nl80211->parent.get_freq = wifi_nl80211_get_freq;
+	nl80211->parent.find_freq = wifi_nl80211_find_freq;
+	nl80211->parent.get_bssid = wifi_nl80211_get_bssid;
+	nl80211->parent.get_rate = wifi_nl80211_get_rate;
+	nl80211->parent.get_qual = wifi_nl80211_get_qual;
+#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS
+	nl80211->parent.indicate_addressing_running = wifi_nl80211_indicate_addressing_running;
+#endif
+	nl80211->parent.deinit = wifi_nl80211_deinit;
 
 	nl80211->nl_sock = nl_socket_alloc ();
 	if (nl80211->nl_sock == NULL)
@@ -880,10 +1095,12 @@ wifi_nl80211_init (int ifindex)
 		goto error;
 
 	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
-	if (nl80211->id < 0) {
-		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\"");
+	if (nl80211->id < 0)
+		goto error;
+
+	nl80211->nl_cb = nl_cb_alloc (NL_CB_DEFAULT);
+	if (nl80211->nl_cb == NULL)
 		goto error;
-	}
 
 	nl80211->phy = -1;
 
@@ -936,15 +1153,18 @@ wifi_nl80211_init (int ifindex)
 	nl80211->freqs = device_info.freqs;
 	nl80211->num_freqs = device_info.num_freqs;
 	nl80211->parent.caps = device_info.caps;
-	nl80211->can_wowlan = device_info.can_wowlan;
+
+	if (device_info.can_wowlan)
+		nl80211->parent.get_wowlan = wifi_nl80211_get_wowlan;
 
 	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
 	       "(%s): using nl80211 for WiFi device control",
 	       ifname);
+
 	return (WifiData *) nl80211;
 
 error:
-	wifi_utils_unref ((WifiData *) nl80211);
+	wifi_utils_deinit ((WifiData *) nl80211);
 	return NULL;
 }
 
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/wifi-utils-private.h
index 59386514..11a0f060 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/wifi-utils-private.h
@@ -24,8 +24,9 @@
 #include "nm-dbus-interface.h"
 #include "wifi-utils.h"
 
-typedef struct {
-	gsize struct_size;
+struct WifiData {
+	int ifindex;
+	NMDeviceWifiCapabilities caps;
 
 	NM80211Mode (*get_mode) (WifiData *data);
 
@@ -65,14 +66,9 @@ typedef struct {
 	gboolean (*set_mesh_ssid) (WifiData *data, const guint8 *ssid, gsize len);
 
 	gboolean (*indicate_addressing_running) (WifiData *data, gboolean running);
-} WifiDataClass;
-
-struct WifiData {
-	const WifiDataClass *klass;
-	int ifindex;
-	NMDeviceWifiCapabilities caps;
 };
 
-gpointer wifi_data_new (const WifiDataClass *klass, int ifindex);
+gpointer wifi_data_new (int ifindex, gsize len);
+void wifi_data_free (WifiData *data);
 
 #endif  /* __WIFI_UTILS_PRIVATE_H__ */
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c
index c8744f79..c4d3c999 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/wifi-utils-wext.c
@@ -21,14 +21,17 @@
 
 #include "nm-default.h"
 
-#include "wifi-utils-wext.h"
-
 #include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
 
+#include "wifi-utils-private.h"
+#include "wifi-utils-wext.h"
+#include "nm-utils.h"
+#include "platform/nm-platform-utils.h"
+
 /* Hacks necessary to #include wireless.h; yay for WEXT */
 #ifndef __user
 #define __user
@@ -38,10 +41,6 @@
 #include <sys/socket.h>
 #include <linux/wireless.h>
 
-#include "wifi-utils-private.h"
-#include "nm-utils.h"
-#include "platform/nm-platform-utils.h"
-
 typedef struct {
 	WifiData parent;
 	int fd;
@@ -629,21 +628,6 @@ wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range)
 WifiData *
 wifi_wext_init (int ifindex, gboolean check_scan)
 {
-	static const WifiDataClass klass = {
-		.struct_size = sizeof (WifiDataWext),
-		.get_mode = wifi_wext_get_mode,
-		.set_mode = wifi_wext_set_mode,
-		.set_powersave = wifi_wext_set_powersave,
-		.get_freq = wifi_wext_get_freq,
-		.find_freq = wifi_wext_find_freq,
-		.get_bssid = wifi_wext_get_bssid,
-		.get_rate = wifi_wext_get_rate,
-		.get_qual = wifi_wext_get_qual,
-		.deinit = wifi_wext_deinit,
-		.get_mesh_channel = wifi_wext_get_mesh_channel,
-		.set_mesh_channel = wifi_wext_set_mesh_channel,
-		.set_mesh_ssid = wifi_wext_set_mesh_ssid,
-	};
 	WifiDataWext *wext;
 	struct iw_range range;
 	guint32 response_len = 0;
@@ -658,7 +642,19 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 		return NULL;
 	}
 
-	wext = wifi_data_new (&klass, ifindex);
+	wext = wifi_data_new (ifindex, sizeof (*wext));
+	wext->parent.get_mode = wifi_wext_get_mode;
+	wext->parent.set_mode = wifi_wext_set_mode;
+	wext->parent.set_powersave = wifi_wext_set_powersave;
+	wext->parent.get_freq = wifi_wext_get_freq;
+	wext->parent.find_freq = wifi_wext_find_freq;
+	wext->parent.get_bssid = wifi_wext_get_bssid;
+	wext->parent.get_rate = wifi_wext_get_rate;
+	wext->parent.get_qual = wifi_wext_get_qual;
+	wext->parent.deinit = wifi_wext_deinit;
+	wext->parent.get_mesh_channel = wifi_wext_get_mesh_channel;
+	wext->parent.set_mesh_channel = wifi_wext_set_mesh_channel;
+	wext->parent.set_mesh_ssid = wifi_wext_set_mesh_ssid;
 
 	wext->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 	if (wext->fd < 0)
@@ -734,7 +730,7 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 	return (WifiData *) wext;
 
 error:
-	wifi_utils_unref ((WifiData *) wext);
+	wifi_utils_deinit ((WifiData *) wext);
 	return NULL;
 }
 
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
index 8818dc9d..d0052121 100644
--- a/src/platform/wifi/wifi-utils.c
+++ b/src/platform/wifi/wifi-utils.c
@@ -38,19 +38,22 @@
 #include "platform/nm-platform-utils.h"
 
 gpointer
-wifi_data_new (const WifiDataClass *klass, int ifindex)
+wifi_data_new (int ifindex, gsize len)
 {
 	WifiData *data;
 
-	nm_assert (klass);
-	nm_assert (klass->struct_size > sizeof (WifiData));
-
-	data = g_malloc0 (klass->struct_size);
-	data->klass = klass;
+	data = g_malloc0 (len);
 	data->ifindex = ifindex;
 	return data;
 }
 
+void
+wifi_data_free (WifiData *data)
+{
+	memset (data, 0, sizeof (*data));
+	g_free (data);
+}
+
 /*****************************************************************************/
 
 WifiData *
@@ -82,14 +85,14 @@ wifi_utils_get_caps (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE);
 
-	return data->caps;
+	return data->caps;	
 }
 
 NM80211Mode
 wifi_utils_get_mode (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN);
-	return data->klass->get_mode (data);
+	return data->get_mode (data);
 }
 
 gboolean
@@ -101,7 +104,7 @@ wifi_utils_set_mode (WifiData *data, const NM80211Mode mode)
 	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
 
 	/* nl80211 probably doesn't need this */
-	return data->klass->set_mode ? data->klass->set_mode (data, mode) : TRUE;
+	return data->set_mode ? data->set_mode (data, mode) : TRUE;
 }
 
 gboolean
@@ -109,14 +112,14 @@ wifi_utils_set_powersave (WifiData *data, guint32 powersave)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
 
-	return data->klass->set_powersave ? data->klass->set_powersave (data, powersave) : TRUE;
+	return data->set_powersave ? data->set_powersave (data, powersave) : TRUE;
 }
 
 guint32
 wifi_utils_get_freq (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_freq (data);
+	return data->get_freq (data);
 }
 
 guint32
@@ -124,7 +127,7 @@ wifi_utils_find_freq (WifiData *data, const guint32 *freqs)
 {
 	g_return_val_if_fail (data != NULL, 0);
 	g_return_val_if_fail (freqs != NULL, 0);
-	return data->klass->find_freq (data, freqs);
+	return data->find_freq (data, freqs);
 }
 
 gboolean
@@ -134,40 +137,38 @@ wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid)
 	g_return_val_if_fail (out_bssid != NULL, FALSE);
 
 	memset (out_bssid, 0, ETH_ALEN);
-	return data->klass->get_bssid (data, out_bssid);
+	return data->get_bssid (data, out_bssid);
 }
 
 guint32
 wifi_utils_get_rate (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_rate (data);
+	return data->get_rate (data);
 }
 
 int
 wifi_utils_get_qual (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_qual (data);
+	return data->get_qual (data);
 }
 
 gboolean
 wifi_utils_get_wowlan (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-
-	if (!data->klass->get_wowlan)
+	if (!data->get_wowlan)
 		return FALSE;
-	return data->klass->get_wowlan (data);
+	return data->get_wowlan (data);
 }
 
 void
-wifi_utils_unref (WifiData *data)
+wifi_utils_deinit (WifiData *data)
 {
 	g_return_if_fail (data != NULL);
-
-	data->klass->deinit (data);
-	g_free (data);
+	data->deinit (data);
+	wifi_data_free (data);
 }
 
 gboolean
@@ -190,8 +191,8 @@ guint32
 wifi_utils_get_mesh_channel (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->klass->get_mesh_channel != NULL, FALSE);
-	return data->klass->get_mesh_channel (data);
+	g_return_val_if_fail (data->get_mesh_channel != NULL, FALSE);
+	return data->get_mesh_channel (data);
 }
 
 gboolean
@@ -199,24 +200,24 @@ wifi_utils_set_mesh_channel (WifiData *data, guint32 channel)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
 	g_return_val_if_fail (channel <= 13, FALSE);
-	g_return_val_if_fail (data->klass->set_mesh_channel != NULL, FALSE);
-	return data->klass->set_mesh_channel (data, channel);
+	g_return_val_if_fail (data->set_mesh_channel != NULL, FALSE);
+	return data->set_mesh_channel (data, channel);
 }
 
 gboolean
 wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->klass->set_mesh_ssid != NULL, FALSE);
-	return data->klass->set_mesh_ssid (data, ssid, len);
+	g_return_val_if_fail (data->set_mesh_ssid != NULL, FALSE);
+	return data->set_mesh_ssid (data, ssid, len);
 }
 
 gboolean
 wifi_utils_indicate_addressing_running (WifiData *data, gboolean running)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
-	if (data->klass->indicate_addressing_running)
-		return data->klass->indicate_addressing_running (data, running);
+	if (data->indicate_addressing_running)
+		return data->indicate_addressing_running (data, running);
 	return FALSE;
 }
 
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index 2633e965..705717b0 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -34,7 +34,7 @@ WifiData *wifi_utils_init (int ifindex, gboolean check_scan);
 
 int wifi_utils_get_ifindex (WifiData *data);
 
-void wifi_utils_unref (WifiData *data);
+void wifi_utils_deinit (WifiData *data);
 
 NMDeviceWifiCapabilities wifi_utils_get_caps (WifiData *data);