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.c13
-rw-r--r--src/platform/nm-fake-platform.h1
-rw-r--r--src/platform/nm-linux-platform.c607
-rw-r--r--src/platform/nm-linux-platform.h1
-rw-r--r--src/platform/nm-netlink.c3
-rw-r--r--src/platform/nm-netlink.h1
-rw-r--r--src/platform/nm-platform-private.h1
-rw-r--r--src/platform/nm-platform-utils.c481
-rw-r--r--src/platform/nm-platform-utils.h1
-rw-r--r--src/platform/nm-platform.c413
-rw-r--r--src/platform/nm-platform.h345
-rw-r--r--src/platform/nmp-netns.c1
-rw-r--r--src/platform/nmp-netns.h1
-rw-r--r--src/platform/nmp-object.c10
-rw-r--r--src/platform/nmp-object.h120
-rw-r--r--src/platform/nmp-rules-manager.c118
-rw-r--r--src/platform/nmp-rules-manager.h5
-rw-r--r--src/platform/tests/meson.build29
-rw-r--r--src/platform/tests/monitor.c1
-rw-r--r--src/platform/tests/test-address.c1
-rw-r--r--src/platform/tests/test-cleanup.c1
-rw-r--r--src/platform/tests/test-common.c9
-rw-r--r--src/platform/tests/test-link.c155
-rw-r--r--src/platform/tests/test-nmp-object.c5
-rw-r--r--src/platform/tests/test-platform-general.c (renamed from src/platform/tests/test-general.c)28
-rw-r--r--src/platform/tests/test-route.c84
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c28
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.h1
-rw-r--r--src/platform/wifi/nm-wifi-utils-private.h1
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c1
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.h1
-rw-r--r--src/platform/wifi/nm-wifi-utils.c4
-rw-r--r--src/platform/wifi/nm-wifi-utils.h1
-rw-r--r--src/platform/wpan/nm-wpan-utils.c8
34 files changed, 1709 insertions, 771 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 40a85390..967aaee8 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-platform-fake.c - Fake platform interaction code for testing NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -276,9 +275,9 @@ link_add_pre (NMPlatform *platform,
 	o->_link.netlink.is_in_netlink = TRUE;
 
 	if (address) {
-		g_assert (address_len > 0 && address_len <= sizeof (link->addr.data));
-		memcpy (link->addr.data, address, address_len);
-		link->addr.len = address_len;
+		g_assert (address_len > 0 && address_len <= sizeof (link->l_address.data));
+		memcpy (link->l_address.data, address, address_len);
+		link->l_address.len = address_len;
 	} else
 		g_assert (address_len == 0);
 
@@ -584,9 +583,9 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 		return -NME_PL_EXISTS;
 
 	obj_tmp = nmp_object_clone (device->obj, FALSE);
-	obj_tmp->link.addr.len = len;
-	memset (obj_tmp->link.addr.data, 0, sizeof (obj_tmp->link.addr.data));
-	memcpy (obj_tmp->link.addr.data, addr, len);
+	obj_tmp->link.l_address.len = len;
+	memset (obj_tmp->link.l_address.data, 0, sizeof (obj_tmp->link.l_address.data));
+	memcpy (obj_tmp->link.l_address.data, addr, len);
 
 	link_set_obj (platform, device, obj_tmp);
 	return 0;
diff --git a/src/platform/nm-fake-platform.h b/src/platform/nm-fake-platform.h
index 8f42d11a..d5be8c65 100644
--- a/src/platform/nm-fake-platform.h
+++ b/src/platform/nm-fake-platform.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-fake-platform.h - Fake platform interaction code for testing NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index d4b01152..e2e1e581 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-linux-platform.c - Linux kernel & udev network configuration layer
  *
  * This program is free software; you can redistribute it and/or modify
@@ -34,6 +33,7 @@
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/ip6_tunnel.h>
+#include <linux/tc_act/tc_mirred.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
 #include <poll.h>
@@ -84,6 +84,13 @@ enum {
 
 /*****************************************************************************/
 
+/* Compat with older kernels. */
+
+#define TCA_FQ_CODEL_CE_THRESHOLD 7
+#define TCA_FQ_CODEL_MEMORY_LIMIT 9
+
+/*****************************************************************************/
+
 #define VLAN_FLAG_MVRP 0x8
 
 /*****************************************************************************/
@@ -732,29 +739,27 @@ _timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
 static guint32
 _addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 {
-	struct timespec tp;
-	gint64 now_nl, now_nm, result;
-	int err;
+	gint64 now_nl;
+	gint64 now_nm;
+	gint64 result;
 
 	/* timestamp is unset. Default to 1. */
 	if (!timestamp) {
-		if (out_now_nm)
-			*out_now_nm = 0;
+		NM_SET_OUT (out_now_nm, 0);
 		return 1;
 	}
 
 	/* do all the calculations in milliseconds scale */
 
-	err = clock_gettime (CLOCK_MONOTONIC, &tp);
-	g_assert (err == 0);
 	now_nm = nm_utils_get_monotonic_timestamp_ms ();
-	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
-	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
+	now_nl = nm_utils_clock_gettime_ms (CLOCK_MONOTONIC);
+
+	nm_assert (now_nm >= 1000);
+	nm_assert (now_nl >= 0);
 
 	result = now_nm - (now_nl - _timestamp_nl_to_ms (timestamp, now_nl));
 
-	if (out_now_nm)
-		*out_now_nm = now_nm / 1000;
+	NM_SET_OUT (out_now_nm, now_nm / 1000);
 
 	/* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() scale is
 	 * a good guess but fails in the following situations:
@@ -2579,6 +2584,25 @@ link_wireguard_change (NMPlatform *platform,
 
 /*****************************************************************************/
 
+static void
+_nmp_link_address_set (NMPLinkAddress *dst,
+                       const struct nlattr *nla)
+{
+	*dst = (NMPLinkAddress) {
+		.len = 0,
+	};
+	if (nla) {
+		int l = nla_len (nla);
+
+		if (   l > 0
+		    && l <= NM_UTILS_HWADDR_LEN_MAX) {
+			G_STATIC_ASSERT_EXPR (sizeof (dst->data) == NM_UTILS_HWADDR_LEN_MAX);
+			memcpy (dst->data, nla_data (nla), l);
+			dst->len = l;
+		}
+	}
+}
+
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
@@ -2622,6 +2646,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	const NMPObject *link_cached = NULL;
 	const NMPObject *lnk_data = NULL;
 	gboolean address_complete_from_cache = TRUE;
+	gboolean broadcast_complete_from_cache = TRUE;
 	gboolean lnk_data_complete_from_cache = TRUE;
 	gboolean need_ext_data = FALSE;
 	gboolean af_inet6_token_valid = FALSE;
@@ -2720,16 +2745,15 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	}
 
 	if (tb[IFLA_ADDRESS]) {
-		int l = nla_len (tb[IFLA_ADDRESS]);
-
-		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
-			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->link.addr.data));
-			memcpy (obj->link.addr.data, nla_data (tb[IFLA_ADDRESS]), l);
-			obj->link.addr.len = l;
-		}
+		_nmp_link_address_set (&obj->link.l_address, tb[IFLA_ADDRESS]);
 		address_complete_from_cache = FALSE;
 	}
 
+	if (tb[IFLA_BROADCAST]) {
+		_nmp_link_address_set (&obj->link.l_broadcast, tb[IFLA_BROADCAST]);
+		broadcast_complete_from_cache = FALSE;
+	}
+
 	if (tb[IFLA_AF_SPEC]) {
 		struct nlattr *af_attr;
 		int remaining;
@@ -2803,6 +2827,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	    && (   lnk_data_complete_from_cache
 	        || need_ext_data
 	        || address_complete_from_cache
+	        || broadcast_complete_from_cache
 	        || !af_inet6_token_valid
 	        || !af_inet6_addr_gen_mode_valid
 	        || !tb[IFLA_STATS64])) {
@@ -2832,7 +2857,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 			}
 
 			if (address_complete_from_cache)
-				obj->link.addr = link_cached->link.addr;
+				obj->link.l_address = link_cached->link.l_address;
+			if (broadcast_complete_from_cache)
+				obj->link.l_broadcast = link_cached->link.l_broadcast;
 			if (!af_inet6_token_valid)
 				obj->link.inet6_token = link_cached->link.inet6_token;
 			if (!af_inet6_addr_gen_mode_valid)
@@ -3481,6 +3508,7 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 {
 	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
+		[TCA_OPTIONS] = { .type = NLA_NESTED },
 	};
 	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	const struct tcmsg *tcm;
@@ -3506,6 +3534,50 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 	obj->qdisc.parent = tcm->tcm_parent;
 	obj->qdisc.info = tcm->tcm_info;
 
+	if (nm_streq0 (obj->qdisc.kind, "fq_codel")) {
+		obj->qdisc.fq_codel.memory_limit = NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET;
+		obj->qdisc.fq_codel.ce_threshold = NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED;
+	}
+
+	if (tb[TCA_OPTIONS]) {
+		struct nlattr *options_attr;
+		int remaining;
+
+		nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) {
+			if (nla_len (options_attr) < sizeof (uint32_t))
+				continue;
+
+			if (nm_streq0 (obj->qdisc.kind, "fq_codel")) {
+				switch (nla_type (options_attr)) {
+				case TCA_FQ_CODEL_LIMIT:
+					obj->qdisc.fq_codel.limit = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_FLOWS:
+					obj->qdisc.fq_codel.flows = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_TARGET:
+					obj->qdisc.fq_codel.target = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_INTERVAL:
+					obj->qdisc.fq_codel.interval = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_QUANTUM:
+					obj->qdisc.fq_codel.quantum = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_CE_THRESHOLD:
+					obj->qdisc.fq_codel.ce_threshold = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_MEMORY_LIMIT:
+					obj->qdisc.fq_codel.memory_limit = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_ECN:
+					obj->qdisc.fq_codel.ecn = !!nla_get_u32 (options_attr);
+					break;
+				}
+			}
+		}
+	}
+
 	return obj;
 }
 
@@ -3628,7 +3700,7 @@ _nl_msg_new_link_set_afspec (struct nl_msg *msg,
 
 	return TRUE;
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -3779,7 +3851,7 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 
 	return TRUE;
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static struct nl_msg *
@@ -4161,6 +4233,7 @@ _nl_msg_new_qdisc (int nlmsg_type,
                    const NMPlatformQdisc *qdisc)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nlattr *tc_options;
 	const struct tcmsg tcm = {
 		.tcm_family = qdisc->addr_family,
 		.tcm_ifindex = qdisc->ifindex,
@@ -4176,55 +4249,34 @@ _nl_msg_new_qdisc (int nlmsg_type,
 
 	NLA_PUT_STRING (msg, TCA_KIND, qdisc->kind);
 
-	return g_steal_pointer (&msg);
-
-nla_put_failure:
-	g_return_val_if_reached (NULL);
-}
-
-static gboolean
-_add_action_simple (struct nl_msg *msg,
-                    const NMPlatformActionSimple *simple)
-{
-	struct nlattr *act_options;
-	struct tc_defact sel = { 0, };
-
-	if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
-		goto nla_put_failure;
-
-	NLA_PUT (msg, TCA_DEF_PARMS, sizeof (sel), &sel);
-	NLA_PUT (msg, TCA_DEF_DATA, sizeof (simple->sdata), simple->sdata);
-
-	nla_nest_end (msg, act_options);
-
-	return TRUE;
-
-nla_put_failure:
-	return FALSE;
-}
-
-static gboolean
-_add_action (struct nl_msg *msg,
-             const NMPlatformAction *action)
-{
-	struct nlattr *prio;
-
-	nm_assert (action || action->kind);
-
-	if (!(prio = nla_nest_start (msg, 1 /* priority */)))
+	if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
 		goto nla_put_failure;
 
-	NLA_PUT_STRING (msg, TCA_ACT_KIND, action->kind);
-
-	if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
-		_add_action_simple (msg, &action->simple);
+	if (strcmp (qdisc->kind, "fq_codel") == 0) {
+		if (qdisc->fq_codel.limit)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit);
+		if (qdisc->fq_codel.flows)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows);
+		if (qdisc->fq_codel.target)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target);
+		if (qdisc->fq_codel.interval)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval);
+		if (qdisc->fq_codel.quantum)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum);
+		if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold);
+		if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit);
+		if (qdisc->fq_codel.ecn)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn);
+	}
 
-	nla_nest_end (msg, prio);
+	nla_nest_end (msg, tc_options);
 
-	return TRUE;
+	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (NULL);
 }
 
 static struct nl_msg *
@@ -4256,8 +4308,52 @@ _nl_msg_new_tfilter (int nlmsg_type,
 	if (!(act_tab = nla_nest_start (msg, TCA_OPTIONS))) // 3 TCA_ACT_KIND TCA_ACT_KIND
 		goto nla_put_failure;
 
-	if (tfilter->action.kind)
-		_add_action (msg, &tfilter->action);
+	if (tfilter->action.kind) {
+		const NMPlatformAction *action = &tfilter->action;
+		struct nlattr *prio;
+		struct nlattr *act_options;
+
+		if (!(prio = nla_nest_start (msg, 1 /* priority */)))
+			goto nla_put_failure;
+
+		NLA_PUT_STRING (msg, TCA_ACT_KIND, action->kind);
+
+		if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
+			const NMPlatformActionSimple *simple = &action->simple;
+			struct tc_defact sel = { 0, };
+
+			if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
+				goto nla_put_failure;
+
+			NLA_PUT (msg, TCA_DEF_PARMS, sizeof (sel), &sel);
+			NLA_PUT (msg, TCA_DEF_DATA, sizeof (simple->sdata), simple->sdata);
+
+			nla_nest_end (msg, act_options);
+
+		} else if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			const NMPlatformActionMirred *mirred = &action->mirred;
+			struct tc_mirred sel = { 0, };
+
+			if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
+				goto nla_put_failure;
+
+			if (mirred->egress && mirred->redirect)
+				sel.eaction = TCA_EGRESS_REDIR;
+			else if (mirred->egress && mirred->mirror)
+				sel.eaction = TCA_EGRESS_MIRROR;
+			else if (mirred->ingress && mirred->redirect)
+				sel.eaction = TCA_INGRESS_REDIR;
+			else if (mirred->ingress && mirred->mirror)
+				sel.eaction = TCA_INGRESS_MIRROR;
+			sel.ifindex = mirred->ifindex;
+
+			NLA_PUT (msg, TCA_MIRRED_PARMS, sizeof (sel), &sel);
+
+			nla_nest_end (msg, act_options);
+		}
+
+		nla_nest_end (msg, prio);
+	}
 
 	nla_nest_end (msg, tc_options);
 
@@ -4298,6 +4394,14 @@ _genl_sock (NMLinuxPlatform *platform)
 		} \
 	} G_STMT_END
 
+/*****************************************************************************/
+
+/* core sysctl-set functions can be called from a non-main thread.
+ * Hence, we require locking from nm-logging. Indicate that by
+ * setting NM_THREAD_SAFE_ON_MAIN_THREAD to zero. */
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
+
 static void
 _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
 {
@@ -4308,18 +4412,18 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, c
 	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
 	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
 	                                &contents, NULL, &error) < 0) {
-		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", pathid, value_escaped, error->message);
+		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", pathid ?: path, value_escaped, error->message);
 		g_clear_error (&error);
 		return;
 	}
 
 	g_strstrip (contents);
 	if (nm_streq (contents, value))
-		_LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", pathid, value_escaped);
+		_LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", pathid ?: path, value_escaped);
 	else {
 		gs_free char *contents_escaped = g_strescape (contents, NULL);
 
-		_LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", pathid, value_escaped, contents_escaped);
+		_LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", pathid ?: path, value_escaped, contents_escaped);
 	}
 	g_free (contents);
 }
@@ -4332,9 +4436,12 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, c
 	} G_STMT_END
 
 static gboolean
-sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
+sysctl_set_internal (NMPlatform *platform,
+                     const char *pathid,
+                     int dirfd,
+                     const char *path,
+                     const char *value)
 {
-	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int fd, tries;
 	gssize nwrote;
 	gssize len;
@@ -4342,17 +4449,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	gs_free char *actual_free = NULL;
 	int errsv;
 
-	g_return_val_if_fail (path != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
-
 	if (dirfd < 0) {
-		if (!nm_platform_netns_push (platform, &netns)) {
-			errno = ENETDOWN;
-			return FALSE;
-		}
-
 		pathid = path;
 
 		fd = open (path, O_WRONLY | O_TRUNC | O_CLOEXEC);
@@ -4452,6 +4549,207 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	return TRUE;
 }
 
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
+
+/*****************************************************************************/
+
+static gboolean
+sysctl_set (NMPlatform *platform,
+            const char *pathid,
+            int dirfd,
+            const char *path,
+            const char *value)
+{
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+
+	g_return_val_if_fail (path, FALSE);
+	g_return_val_if_fail (value, FALSE);
+
+	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
+
+	if (   dirfd < 0
+	    && !nm_platform_netns_push (platform, &netns)) {
+		errno = ENETDOWN;
+		return FALSE;
+	}
+
+	return sysctl_set_internal (platform, pathid, dirfd, path, value);
+}
+
+typedef struct {
+	NMPlatform *platform;
+	char *pathid;
+	int dirfd;
+	char *path;
+	char **values;
+	GCancellable *cancellable;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+} SysctlAsyncInfo;
+
+static void
+sysctl_async_info_free (SysctlAsyncInfo *info)
+{
+	g_object_unref (info->platform);
+	g_free (info->pathid);
+	if (info->dirfd >= 0)
+		nm_close (info->dirfd);
+	g_free (info->path);
+	g_strfreev (info->values);
+	g_object_unref (info->cancellable);
+	g_slice_free (SysctlAsyncInfo, info);
+}
+
+static void
+sysctl_async_cb (GObject *object,
+                 GAsyncResult *res,
+                 gpointer user_data)
+{
+	NMPlatform *platform;
+	GTask *task = G_TASK (res);
+	SysctlAsyncInfo *info;
+	gs_free_error GError *error = NULL;
+	gs_free char *values_str = NULL;
+
+	info = g_task_get_task_data (task);
+
+	if (g_task_propagate_boolean (task, &error)) {
+		platform = info->platform;
+		_LOGD ("sysctl: successfully set-async '%s' to values '%s'",
+		       info->pathid ?: info->path,
+		       (values_str = g_strjoinv (", ", info->values)));
+	}
+
+	if (info->callback)
+		info->callback (error, info->callback_data);
+}
+
+static void
+sysctl_async_thread_fn (GTask *task,
+                        gpointer source_object,
+                        gpointer task_data,
+                        GCancellable *cancellable)
+{
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+	SysctlAsyncInfo *info = task_data;
+	GError *error = NULL;
+	char **value;
+
+	if (g_task_return_error_if_cancelled (task))
+		return;
+
+	if (   info->dirfd < 0
+	    && !nm_platform_netns_push (info->platform, &netns)) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     "sysctl: failed changing namespace");
+		g_task_return_error (task, error);
+		return;
+	}
+
+	for (value = info->values; *value; value++) {
+		if (!sysctl_set_internal (info->platform,
+		                          info->pathid,
+		                          info->dirfd,
+		                          info->path,
+		                          *value)) {
+			g_set_error (&error,
+			             NM_UTILS_ERROR,
+			             NM_UTILS_ERROR_UNKNOWN,
+			             "sysctl: failed setting '%s' to value '%s': %s",
+			             info->pathid ?: info->path,
+			             *value,
+			             nm_strerror_native (errno));
+			g_task_return_error (task, error);
+			return;
+		}
+		if (g_task_return_error_if_cancelled (task))
+			return;
+	}
+	g_task_return_boolean (task, TRUE);
+}
+
+static void
+sysctl_set_async_return_idle (gpointer user_data,
+                              GCancellable *cancellable)
+{
+	gs_unref_object NMPlatform *platform = NULL;
+	gs_free_error GError *cancelled_error = NULL;
+	gs_free_error GError *error = NULL;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+
+	nm_utils_user_data_unpack (user_data, &platform, &callback, &callback_data, &error);
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	callback (cancelled_error ?: error, callback_data);
+}
+
+static void
+sysctl_set_async (NMPlatform *platform,
+                  const char *pathid,
+                  int dirfd,
+                  const char *path,
+                  const char *const *values,
+                  NMPlatformAsyncCallback callback,
+                  gpointer data,
+                  GCancellable *cancellable)
+{
+	SysctlAsyncInfo *info;
+	GTask *task;
+	int dirfd_dup, errsv;
+	gpointer packed;
+	GError *error = NULL;
+
+	g_return_if_fail (platform);
+	g_return_if_fail (path);
+	g_return_if_fail (values && values[0]);
+	g_return_if_fail (cancellable);
+	g_return_if_fail (!data || callback);
+
+	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
+
+	if (dirfd >= 0) {
+		dirfd_dup = fcntl (dirfd, F_DUPFD_CLOEXEC, 0);
+		if (dirfd_dup < 0) {
+			if (!callback)
+				return;
+			errsv = errno;
+			g_set_error (&error,
+			             NM_UTILS_ERROR,
+			             NM_UTILS_ERROR_UNKNOWN,
+			             "sysctl: failure duplicating directory fd: %s",
+			             nm_strerror_native (errsv));
+			packed = nm_utils_user_data_pack (g_object_ref (platform),
+			                                  callback,
+			                                  data,
+			                                  error);
+			nm_utils_invoke_on_idle (sysctl_set_async_return_idle,
+			                         packed,
+			                         cancellable);
+			return;
+		}
+	} else
+		dirfd_dup = -1;
+
+	info = g_slice_new0 (SysctlAsyncInfo);
+	info->platform = g_object_ref (platform);
+	info->pathid = g_strdup (pathid);
+	info->dirfd = dirfd_dup;
+	info->path = g_strdup (path);
+	info->values = g_strdupv ((char **) values);
+	info->callback = callback;
+	info->callback_data = data;
+	info->cancellable = g_object_ref (cancellable);
+
+	task = g_task_new (platform, cancellable, sysctl_async_cb, NULL);
+	g_task_set_task_data (task, info, (GDestroyNotify) sysctl_async_info_free);
+	g_task_set_return_on_cancel (task, FALSE);
+	g_task_run_in_thread (task, sysctl_async_thread_fn);
+	g_object_unref (task);
+}
+
 static GSList *sysctl_clear_cache_list;
 
 void
@@ -5310,7 +5608,7 @@ cache_on_change (NMPlatform *platform,
 				 * Request it again. */
 				re_request_link = TRUE;
 			} else if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
-			           && obj_new->link.addr.len == 0) {
+			           && obj_new->link.l_address.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
@@ -6170,8 +6468,8 @@ retry:
 	} else if (   NM_IN_SET (-((int) seq_result), ENFILE)
 	           && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS
 	           && (obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex))
-	           && obj_cache->link.addr.len == data->set_address.length
-	           && memcmp (obj_cache->link.addr.data, data->set_address.address, data->set_address.length) == 0) {
+	           && obj_cache->link.l_address.len == data->set_address.length
+	           && memcmp (obj_cache->link.l_address.data, data->set_address.address, data->set_address.length) == 0) {
 		/* workaround ENFILE which may be wrongly returned (bgo #770456).
 		 * If the MAC address is as expected, assume success? */
 		log_result = "success";
@@ -6409,7 +6707,8 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 	 * us whether the device actually supports carrier detection in the first
 	 * place. We assume any device that does implements one of these two APIs.
 	 */
-	return nmp_utils_ethtool_supports_carrier_detect (ifindex) || nmp_utils_mii_supports_carrier_detect (ifindex);
+	return    nmp_utils_ethtool_supports_carrier_detect (ifindex)
+	       || nmp_utils_mii_supports_carrier_detect (ifindex);
 }
 
 static gboolean
@@ -6534,35 +6833,74 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-link_set_sriov_params (NMPlatform *platform,
-                       int ifindex,
-                       guint num_vfs,
-                       NMTernary autoprobe)
+static void
+sriov_idle_cb (gpointer user_data,
+               GCancellable *cancellable)
+{
+	gs_unref_object NMPlatform *platform = NULL;
+	gs_free_error GError *cancelled_error = NULL;
+	gs_free_error GError *error = NULL;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	nm_utils_user_data_unpack (user_data, &platform, &error, &callback, &callback_data);
+	callback (cancelled_error ?: error, callback_data);
+}
+
+static void
+link_set_sriov_params_async (NMPlatform *platform,
+                             int ifindex,
+                             guint num_vfs,
+                             NMTernary autoprobe,
+                             NMPlatformAsyncCallback callback,
+                             gpointer data,
+                             GCancellable *cancellable)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
+	gs_free_error GError *error = NULL;
 	nm_auto_close int dirfd = -1;
 	int current_autoprobe;
-	guint total;
+	guint i, total;
 	gint64 current_num;
 	char ifname[IFNAMSIZ];
+	gpointer packed;
+	const char *values[3];
 	char buf[64];
-	int errsv;
 
-	if (!nm_platform_netns_push (platform, &netns))
-		return FALSE;
+	g_return_if_fail (callback || !data);
+	g_return_if_fail (cancellable);
+
+	if (!nm_platform_netns_push (platform, &netns)) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     "couldn't change namespace");
+		goto out_idle;
+	}
 
 	dirfd = nm_platform_sysctl_open_netdir (platform, ifindex, ifname);
-	if (!dirfd)
-		return FALSE;
+	if (!dirfd) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     "couldn't open netdir");
+		goto out_idle;
+	}
 
 	total = nm_platform_sysctl_get_int_checked (platform,
 	                                            NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                                      ifname,
 	                                                                      "device/sriov_totalvfs"),
 	                                            10, 0, G_MAXUINT, 0);
-	if (errno)
-		return FALSE;
+	if (errno) {
+		g_set_error (&error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_UNKNOWN,
+		             "failed reading sriov_totalvfs value: %s",
+		             nm_strerror_native (errno));
+		goto out_idle;
+	}
 	if (num_vfs > total) {
 		_LOGW ("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
 		num_vfs = total;
@@ -6594,23 +6932,7 @@ link_set_sriov_params (NMPlatform *platform,
 
 	if (   current_num == num_vfs
 	    && (autoprobe == NM_TERNARY_DEFAULT || current_autoprobe == autoprobe))
-		return TRUE;
-
-	if (current_num != 0) {
-		/* We need to destroy all other VFs before changing any value */
-		if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
-		                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
-		                                                       ifname,
-		                                                      "device/sriov_numvfs"),
-		                             "0")) {
-			errsv = errno;
-			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", nm_strerror_native (errsv));
-			return FALSE;
-		}
-	}
-
-	if (num_vfs == 0)
-		return TRUE;
+		goto out_idle;
 
 	if (   NM_IN_SET (autoprobe, NM_TERNARY_TRUE, NM_TERNARY_FALSE)
 	    && current_autoprobe != autoprobe
@@ -6619,22 +6941,40 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                          ifname,
 	                                                          "device/sriov_drivers_autoprobe"),
 	                                nm_sprintf_buf (buf, "%d", (int) autoprobe))) {
-		errsv = errno;
-		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, nm_strerror_native (errsv));
-		return FALSE;
+		g_set_error (&error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_UNKNOWN,
+		             "couldn't set SR-IOV drivers-autoprobe to %d: %s",
+		            (int) autoprobe, nm_strerror_native (errno));
+		goto out_idle;
 	}
 
-	if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
-	                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                       ifname,
-	                                                       "device/sriov_numvfs"),
-	                             nm_sprintf_buf (buf, "%u", num_vfs))) {
-		errsv = errno;
-		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, nm_strerror_native (errsv));
-		return FALSE;
-	}
+	if (current_num == 0 && num_vfs == 0)
+		goto out_idle;
 
-	return TRUE;
+	i = 0;
+	if (current_num != 0)
+		values[i++] = "0";
+	if (num_vfs != 0)
+		values[i++] = nm_sprintf_bufa (32, "%u", num_vfs);
+	values[i++] = NULL;
+
+	sysctl_set_async (platform,
+	                  NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "device/sriov_numvfs"),
+	                  values,
+	                  callback,
+	                  data,
+	                  cancellable);
+	return;
+
+out_idle:
+	if (callback) {
+		packed = nm_utils_user_data_pack (g_object_ref (platform),
+		                                  g_steal_pointer (&error),
+		                                  callback,
+		                                  data);
+		nm_utils_invoke_on_idle (sriov_idle_cb, packed, cancellable);
+	}
 }
 
 static gboolean
@@ -8167,6 +8507,9 @@ qdisc_add (NMPlatform *platform,
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
+	/* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
+	 * is undefined. */
+
 	msg = _nl_msg_new_qdisc (RTM_NEWQDISC, flags, qdisc);
 
 	event_handler_read_netlink (platform, FALSE);
@@ -8208,6 +8551,9 @@ tfilter_add (NMPlatform *platform,
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
+	/* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
+	 * and tfilter.action.kind is undefined. */
+
 	msg = _nl_msg_new_tfilter (RTM_NEWTFILTER, flags, tfilter);
 
 	event_handler_read_netlink (platform, FALSE);
@@ -8913,6 +9259,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	object_class->finalize = finalize;
 
 	platform_class->sysctl_set = sysctl_set;
+	platform_class->sysctl_set_async = sysctl_set_async;
 	platform_class->sysctl_get = sysctl_get;
 
 	platform_class->link_add = link_add;
@@ -8936,7 +9283,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_get_permanent_address = link_get_permanent_address;
 	platform_class->link_set_mtu = link_set_mtu;
 	platform_class->link_set_name = link_set_name;
-	platform_class->link_set_sriov_params = link_set_sriov_params;
+	platform_class->link_set_sriov_params_async = link_set_sriov_params_async;
 	platform_class->link_set_sriov_vfs = link_set_sriov_vfs;
 	platform_class->link_set_bridge_vlans = link_set_bridge_vlans;
 
diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h
index bff6c00c..f08f91aa 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/platform/nm-linux-platform.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-linux-platform.h - Linux kernel & udev network configuration layer
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
index 71506a2c..da009d01 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -510,7 +509,7 @@ 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)
+	if (nla_put (msg, NLA_F_NESTED | attrtype, 0, NULL) < 0)
 		return NULL;
 
 	return start;
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index b12fd243..c793f04a 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-platform-private.h b/src/platform/nm-platform-private.h
index b6c94baa..5a24eda0 100644
--- a/src/platform/nm-platform-private.h
+++ b/src/platform/nm-platform-private.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 456871a7..bc9ec11d 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -69,37 +68,209 @@ nmp_utils_if_nametoindex (const char *ifname)
 
 typedef struct {
 	int fd;
-	int ifindex;
+	const int ifindex;
 	char ifname[IFNAMSIZ];
 } SocketHandle;
 
+#define SOCKET_HANDLE_INIT(_ifindex) \
+	{ \
+		.fd = -1, \
+		.ifindex = (_ifindex), \
+	}
+
+static void
+_nm_auto_socket_handle (SocketHandle *shandle)
+{
+	if (shandle->fd >= 0)
+		nm_close (shandle->fd);
+}
+
+#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle)
+
+/*****************************************************************************/
+
+typedef enum {
+	IOCTL_CALL_DATA_TYPE_NONE,
+	IOCTL_CALL_DATA_TYPE_IFRDATA,
+	IOCTL_CALL_DATA_TYPE_IFRU,
+} IoctlCallDataType;
+
 static int
-socket_handle_init (SocketHandle *shandle, int ifindex)
+_ioctl_call (const char *log_ioctl_type,
+             const char *log_subtype,
+             unsigned long int ioctl_request,
+             int ifindex,
+             int *inout_fd,
+             char *inout_ifname,
+             IoctlCallDataType edata_type,
+             gpointer edata,
+             gsize edata_size,
+             struct ifreq *out_ifreq)
 {
-	if (!nmp_utils_if_indextoname (ifindex, shandle->ifname)) {
-		shandle->ifindex = 0;
-		return -ENODEV;
+	nm_auto_close int fd_close = -1;
+	int fd;
+	int r;
+	gpointer edata_backup = NULL;
+	gs_free gpointer edata_backup_free = NULL;
+	guint try_count;
+	char known_ifnames[2][IFNAMSIZ];
+	const char *failure_reason = NULL;
+	struct ifreq ifr;
+
+	nm_assert (ifindex > 0);
+	nm_assert (NM_IN_SET (edata_type, IOCTL_CALL_DATA_TYPE_NONE,
+	                                  IOCTL_CALL_DATA_TYPE_IFRDATA,
+	                                  IOCTL_CALL_DATA_TYPE_IFRU));
+	nm_assert (edata_type != IOCTL_CALL_DATA_TYPE_NONE    || edata_size == 0);
+	nm_assert (edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0);
+	nm_assert (edata_type != IOCTL_CALL_DATA_TYPE_IFRU    || (edata_size > 0 && edata_size <= sizeof (ifr.ifr_ifru)));
+	nm_assert (edata_size == 0 || edata);
+
+	/* open a file descriptor (or use the one provided). */
+	if (   inout_fd
+	    && *inout_fd >= 0)
+		fd = *inout_fd;
+	else {
+		fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+		if (fd < 0) {
+			r = -NM_ERRNO_NATIVE (errno);
+			failure_reason = "failed creating socket or ioctl";
+			goto out;
+		}
+		if (inout_fd)
+			*inout_fd = fd;
+		else
+			fd_close = fd;
 	}
 
-	shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	if (shandle->fd < 0) {
-		shandle->ifindex = 0;
-		return -NM_ERRNO_NATIVE (errno);
+	/* resolve the ifindex to name (or use the one provided). */
+	if (   inout_ifname
+	    && inout_ifname[0])
+		nm_utils_ifname_cpy (known_ifnames[0], inout_ifname);
+	else {
+		if (!nmp_utils_if_indextoname (ifindex, known_ifnames[0])) {
+			failure_reason = "cannot resolve ifindex";
+			r = -ENODEV;
+			goto out;
+		}
+		if (inout_ifname)
+			nm_utils_ifname_cpy (inout_ifname, known_ifnames[0]);
 	}
 
-	shandle->ifindex = ifindex;
-	return 0;
-}
+	/* we might need to retry the request. Backup edata so that we can
+	 * restore it on retry. */
+	if (edata_size > 0)
+		edata_backup = nm_memdup_maybe_a (500, edata, edata_size, &edata_backup_free);
 
-static void
-socket_handle_destroy (SocketHandle *shandle)
-{
-	if (shandle->ifindex) {
-		shandle->ifindex = 0;
-		nm_close (shandle->fd);
+	try_count = 0;
+
+again:
+	{
+		const char *ifname = known_ifnames[try_count % 2];
+
+		nm_assert (ifindex > 0);
+		nm_assert (ifname && nm_utils_is_valid_iface_name (ifname, NULL));
+		nm_assert (fd >= 0);
+
+		memset (&ifr, 0, sizeof (ifr));
+		nm_utils_ifname_cpy (ifr.ifr_name, ifname);
+		if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA)
+			ifr.ifr_data = edata;
+		else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU)
+			memcpy (&ifr.ifr_ifru, edata, NM_MIN (edata_size, sizeof (ifr.ifr_ifru)));
+
+		if (ioctl (fd, ioctl_request, &ifr) < 0) {
+			r = -NM_ERRNO_NATIVE (errno);
+			nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s, %s: failed: %s",
+			              log_ioctl_type,
+			              ifindex,
+			              log_subtype,
+			              ifname,
+			              nm_strerror_native (-r));
+		} else {
+			r = 0;
+			nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s, %s: success",
+			              log_ioctl_type,
+			              ifindex,
+			              log_subtype,
+			              ifname);
+		}
+	}
+
+	try_count++;
+
+	/* resolve the name again to see whether the ifindex still has the same name. */
+	if (!nmp_utils_if_indextoname (ifindex, known_ifnames[try_count % 2])) {
+		/* we could not find the ifindex again. Probably the device just got
+		 * removed.
+		 *
+		 * In both cases we return the error code we got from ioctl above.
+		 * Either it failed because the device was gone already or it still
+		 * managed to complete the call. In both cases, the error code is good. */
+		failure_reason = "cannot resolve ifindex after ioctl call. Probably the device was just removed";
+		goto out;
 	}
+
+	/* check whether the ifname changed in the meantime. If yes, would render the result
+	 * invalid. Note that this cannot detect every race regarding renames, for example:
+	 *
+	 *  - if_indextoname(#10) gives eth0
+	 *  - rename(#10) => eth0_tmp
+	 *  - rename(#11) => eth0
+	 *  - ioctl(eth0) (wrongly fetching #11, formerly eth1)
+	 *  - rename(#11) => eth_something
+	 *  - rename(#10) => eth0
+	 *  - if_indextoname(#10) gives eth0
+	 */
+	if (!nm_streq (known_ifnames[0], known_ifnames[1])) {
+		gboolean retry;
+
+		/* we detected a possible(!) rename.
+		 *
+		 * For getters it's straight forward to just retry the call.
+		 *
+		 * For setters we also always retry. If our previous call operated on the right device,
+		 * calling it again should have no bad effect (just setting the same thing more than once).
+		 *
+		 * The only potential bad thing is if there was a race involving swapping names, and we just
+		 * set the ioctl option on the wrong device. But then the bad thing already happend and
+		 * we cannot detect it (nor do anything about it). At least, we can retry and set the
+		 * option on the right interface. */
+		retry = (try_count < 5);
+
+		nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s",
+		              log_ioctl_type,
+		              ifindex,
+		              log_subtype,
+		              known_ifnames[(try_count - 1) % 2],
+		              known_ifnames[ try_count      % 2],
+		                retry
+		              ? "Retry"
+		              : "No retry");
+		if (inout_ifname)
+			nm_utils_ifname_cpy (inout_ifname, known_ifnames[try_count % 2]);
+		if (retry) {
+			if (edata_size > 0)
+				memcpy (edata, edata_backup, edata_size);
+			goto again;
+		}
+	}
+
+out:
+	if (failure_reason) {
+		nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s: %s: %s",
+		              log_ioctl_type,
+		              ifindex,
+		              log_subtype,
+		              failure_reason,
+		                r < 0
+		              ? nm_strerror_native (-r)
+		              : "assume success");
+	}
+	if (r >= 0)
+		NM_SET_OUT (out_ifreq, ifr);
+	return r;
 }
-#define nm_auto_socket_handle nm_auto(socket_handle_destroy)
 
 /******************************************************************************
  * ethtool
@@ -121,9 +292,13 @@ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
 );
 
 static const char *
-_ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
+_ethtool_edata_to_string (gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len)
 {
-	return _ethtool_cmd_to_string (*((guint32 *) edata), buf, len);
+	nm_assert (edata);
+	nm_assert (edata_size >= sizeof (guint32));
+	nm_assert ((((intptr_t) edata) % _nm_alignof (guint32)) == 0);
+
+	return _ethtool_cmd_to_string (*((guint32 *) edata), sbuf, sbuf_len);
 }
 
 /*****************************************************************************/
@@ -136,56 +311,37 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
 #endif
 
 static int
-ethtool_call_handle (SocketHandle *shandle, gpointer edata)
+_ethtool_call_handle (SocketHandle *shandle, gpointer edata, gsize edata_size)
 {
-	struct ifreq ifr = {
-		.ifr_data = edata,
-	};
 	char sbuf[50];
-	int errsv;
-
-	nm_assert (shandle);
-	nm_assert (shandle->ifindex);
-	nm_assert (shandle->ifname[0]);
-	nm_assert (strlen (shandle->ifname) < IFNAMSIZ);
-	nm_assert (edata);
 
-	memcpy (ifr.ifr_name, shandle->ifname, IFNAMSIZ);
-	if (ioctl (shandle->fd, SIOCETHTOOL, &ifr) < 0) {
-		errsv = errno;
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s",
-		              shandle->ifindex,
-		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              shandle->ifname,
-		              nm_strerror_native (errsv));
-		return -NM_ERRNO_NATIVE (errsv);
-	}
-
-	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
-	              shandle->ifindex,
-	              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-	              shandle->ifname);
-	return 0;
+	return _ioctl_call ("ethtool",
+	                    _ethtool_edata_to_string (edata, edata_size, sbuf, sizeof (sbuf)),
+	                    SIOCETHTOOL,
+	                    shandle->ifindex,
+	                    &shandle->fd,
+	                    shandle->ifname,
+	                    IOCTL_CALL_DATA_TYPE_IFRDATA,
+	                    edata,
+	                    edata_size,
+	                    NULL);
 }
 
 static int
-ethtool_call_ifindex (int ifindex, gpointer edata)
+_ethtool_call_once (int ifindex, gpointer edata, gsize edata_size)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	int r;
 	char sbuf[50];
 
-	nm_assert (edata);
-
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              nm_strerror_native (-r));
-		return r;
-	}
-
-	return ethtool_call_handle (&shandle, edata);
+	return _ioctl_call ("ethtool",
+	                    _ethtool_edata_to_string (edata, edata_size, sbuf, sizeof (sbuf)),
+	                    SIOCETHTOOL,
+	                    ifindex,
+	                    NULL,
+	                    NULL,
+	                    IOCTL_CALL_DATA_TYPE_IFRDATA,
+	                    edata,
+	                    edata_size,
+	                    NULL);
 }
 
 /*****************************************************************************/
@@ -196,27 +352,29 @@ ethtool_get_stringset (SocketHandle *shandle, int stringset_id)
 	struct {
 		struct ethtool_sset_info info;
 		guint32 sentinel;
-	} sset_info = { };
+	} sset_info = {
+		.info.cmd = ETHTOOL_GSSET_INFO,
+		.info.reserved = 0,
+		.info.sset_mask = (1ULL << stringset_id),
+	};
 	gs_free struct ethtool_gstrings *gstrings = NULL;
+	gsize gstrings_len;
 	guint32 i, len;
 
-	sset_info.info.cmd = ETHTOOL_GSSET_INFO;
-	sset_info.info.reserved = 0;
-	sset_info.info.sset_mask = (1ULL << stringset_id);
-
-	if (ethtool_call_handle (shandle, &sset_info) < 0)
+	if (_ethtool_call_handle (shandle, &sset_info, sizeof (sset_info)) < 0)
 		return NULL;
 	if (!sset_info.info.sset_mask)
 		return NULL;
 
 	len = sset_info.info.data[0];
 
-	gstrings = g_malloc0 (sizeof (*gstrings) + (len * ETH_GSTRING_LEN));
+	gstrings_len = sizeof (*gstrings) + (len * ETH_GSTRING_LEN);
+	gstrings = g_malloc0 (gstrings_len);
 	gstrings->cmd = ETHTOOL_GSTRINGS;
 	gstrings->string_set = stringset_id;
 	gstrings->len = len;
 	if (gstrings->len > 0) {
-		if (ethtool_call_handle (shandle, gstrings) < 0)
+		if (_ethtool_call_handle (shandle, gstrings, gstrings_len) < 0)
 			return NULL;
 		for (i = 0; i < gstrings->len; i++) {
 			/* ensure NUL terminated */
@@ -402,18 +560,20 @@ ethtool_get_features (SocketHandle *shandle)
 		return NULL;
 
 	if (ss_features->len > 0) {
-		gs_free struct ethtool_gfeatures *gfeatures = NULL;
+		gs_free struct ethtool_gfeatures *gfeatures_free = NULL;
+		struct ethtool_gfeatures *gfeatures;
+		gsize gfeatures_len;
 		guint idx;
 		const NMEthtoolFeatureState *states_list0 = NULL;
 		const NMEthtoolFeatureState *const*states_plist0 = NULL;
 		guint states_plist_n = 0;
 
-		gfeatures = g_malloc0 (  sizeof (struct ethtool_gfeatures)
-		                       + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0])));
-
+		gfeatures_len =   sizeof (struct ethtool_gfeatures)
+		                + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0]));
+		gfeatures = nm_malloc0_maybe_a (300, gfeatures_len, &gfeatures_free);
 		gfeatures->cmd = ETHTOOL_GFEATURES;
 		gfeatures->size = NM_DIV_ROUND_UP (ss_features->len, 32u);
-		if (ethtool_call_handle (shandle, gfeatures) < 0)
+		if (_ethtool_call_handle (shandle, gfeatures, gfeatures_len) < 0)
 			return NULL;
 
 		for (idx = 0; idx < G_N_ELEMENTS (_ethtool_feature_infos); idx++) {
@@ -477,20 +637,11 @@ ethtool_get_features (SocketHandle *shandle)
 NMEthtoolFeatureStates *
 nmp_utils_ethtool_get_features (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
 	NMEthtoolFeatureStates *features;
-	int r;
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "get-features",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
 	features = ethtool_get_features (&shandle);
 
 	if (!features) {
@@ -530,8 +681,10 @@ nmp_utils_ethtool_set_features (int ifindex,
                                 const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
                                 gboolean do_set /* or reset */)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	gs_free struct ethtool_sfeatures *sfeatures = NULL;
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+	gs_free struct ethtool_sfeatures *sfeatures_free = NULL;
+	struct ethtool_sfeatures *sfeatures;
+	gsize sfeatures_len;
 	int r;
 	guint i, j;
 	struct {
@@ -614,16 +767,9 @@ nmp_utils_ethtool_set_features (int ifindex,
 		return TRUE;
 	}
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "set-features",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
-	sfeatures = g_malloc0 (sizeof (struct ethtool_sfeatures)
-	                       + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0])));
+	sfeatures_len =   sizeof (struct ethtool_sfeatures)
+	                + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0]));
+	sfeatures = nm_malloc0_maybe_a (300, sfeatures_len, &sfeatures_free);
 	sfeatures->cmd = ETHTOOL_SFEATURES;
 	sfeatures->size = NM_DIV_ROUND_UP (features->n_ss_features, 32U);
 
@@ -649,7 +795,8 @@ nmp_utils_ethtool_set_features (int ifindex,
 			sfeatures->features[i_block].requested &= ~i_flag;
 	}
 
-	if ((r = ethtool_call_handle (&shandle, sfeatures)) < 0) {
+	r = _ethtool_call_handle (&shandle, sfeatures, sfeatures_len);
+	if (r < 0) {
 		success = FALSE;
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
 		              ifindex,
@@ -687,10 +834,10 @@ nmp_utils_ethtool_get_driver_info (int ifindex,
 	g_return_val_if_fail (data, FALSE);
 
 	drvinfo = (struct ethtool_drvinfo *) data;
-
-	memset (drvinfo, 0, sizeof (*drvinfo));
-	drvinfo->cmd = ETHTOOL_GDRVINFO;
-	return ethtool_call_ifindex (ifindex, drvinfo) >= 0;
+	*drvinfo = (struct ethtool_drvinfo) {
+		.cmd = ETHTOOL_GDRVINFO,
+	};
+	return _ethtool_call_once (ifindex, drvinfo, sizeof (*drvinfo)) >= 0;
 }
 
 gboolean
@@ -701,16 +848,16 @@ nmp_utils_ethtool_get_permanent_address (int ifindex,
 	struct {
 		struct ethtool_perm_addr e;
 		guint8 _extra_data[NM_UTILS_HWADDR_LEN_MAX + 1];
-	} edata;
+	} edata = {
+		.e.cmd = ETHTOOL_GPERMADDR,
+		.e.size = NM_UTILS_HWADDR_LEN_MAX,
+	};
+
 	guint i;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	memset (&edata, 0, sizeof (edata));
-	edata.e.cmd = ETHTOOL_GPERMADDR;
-	edata.e.size = NM_UTILS_HWADDR_LEN_MAX;
-
-	if (ethtool_call_ifindex (ifindex, &edata.e) < 0)
+	if (_ethtool_call_once (ifindex, &edata, sizeof (edata)) < 0)
 		return FALSE;
 
 	if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX)
@@ -747,27 +894,20 @@ nmp_utils_ethtool_supports_carrier_detect (int ifindex)
 	 * assume the device supports carrier-detect, otherwise we assume it
 	 * doesn't.
 	 */
-	return ethtool_call_ifindex (ifindex, &edata) >= 0;
+	return _ethtool_call_once (ifindex, &edata, sizeof (edata)) >= 0;
 }
 
 gboolean
 nmp_utils_ethtool_supports_vlans (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	int r;
-	gs_free struct ethtool_gfeatures *features = NULL;
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+	gs_free struct ethtool_gfeatures *features_free = NULL;
+	struct ethtool_gfeatures *features;
+	gsize features_len;
 	int idx, block, bit, size;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "support-vlans",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
 	idx = ethtool_get_stringset_index (&shandle, ETH_SS_FEATURES, "vlan-challenged");
 	if (idx < 0) {
 		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: vlan-challenged ethtool feature does not exist?", ifindex);
@@ -778,11 +918,13 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 	bit = idx % 32;
 	size = block + 1;
 
-	features = g_malloc0 (sizeof (*features) + size * sizeof (struct ethtool_get_features_block));
+	features_len =   sizeof (*features)
+	               + (size * sizeof (struct ethtool_get_features_block));
+	features = nm_malloc0_maybe_a (300, features_len, &features_free);
 	features->cmd = ETHTOOL_GFEATURES;
 	features->size = size;
 
-	if (ethtool_call_handle (&shandle, features) < 0)
+	if (_ethtool_call_handle (&shandle, features, features_len) < 0)
 		return FALSE;
 
 	return !(features->features[block].active & (1 << bit));
@@ -791,32 +933,25 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 int
 nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	int r;
-
-	gs_free struct ethtool_stats *stats = NULL;
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+	gsize stats_len;
+	gs_free struct ethtool_stats *stats_free = NULL;
+	struct ethtool_stats *stats;
 	int peer_ifindex_stat;
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "get-peer-ifindex",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
 	peer_ifindex_stat = ethtool_get_stringset_index (&shandle, ETH_SS_STATS, "peer_ifindex");
 	if (peer_ifindex_stat < 0) {
 		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex);
 		return FALSE;
 	}
 
-	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
+	stats_len = sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64);
+	stats = nm_malloc0_maybe_a (300, stats_len, &stats_free);
 	stats->cmd = ETHTOOL_GSTATS;
 	stats->n_stats = peer_ifindex_stat + 1;
-	if (ethtool_call_ifindex (ifindex, stats) < 0)
+	if (_ethtool_call_handle (&shandle, stats, stats_len) < 0)
 		return 0;
 
 	return stats->data[peer_ifindex_stat];
@@ -825,13 +960,13 @@ nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 gboolean
 nmp_utils_ethtool_get_wake_on_lan (int ifindex)
 {
-	struct ethtool_wolinfo wol;
+	struct ethtool_wolinfo wol = {
+		.cmd = ETHTOOL_GWOL,
+	};
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	memset (&wol, 0, sizeof (wol));
-	wol.cmd = ETHTOOL_GWOL;
-	if (ethtool_call_ifindex (ifindex, &wol) < 0)
+	if (_ethtool_call_once (ifindex, &wol, sizeof (wol)) < 0)
 		return FALSE;
 
 	return wol.wolopts != 0;
@@ -849,11 +984,10 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (ethtool_call_ifindex (ifindex, &edata) < 0)
+	if (_ethtool_call_once (ifindex, &edata, sizeof (edata)) < 0)
 		return FALSE;
 
-	if (out_autoneg)
-		*out_autoneg = (edata.autoneg == AUTONEG_ENABLE);
+	NM_SET_OUT (out_autoneg, (edata.autoneg == AUTONEG_ENABLE));
 
 	if (out_speed) {
 		guint32 speed;
@@ -922,6 +1056,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
                                      guint32 speed,
                                      NMPlatformLinkDuplexType duplex)
 {
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
 	struct ethtool_cmd edata = {
 		.cmd = ETHTOOL_GSET,
 	};
@@ -931,7 +1066,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 	                      || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), FALSE);
 
 	/* retrieve first current settings */
-	if (ethtool_call_ifindex (ifindex, &edata) < 0)
+	if (_ethtool_call_handle (&shandle, &edata, sizeof (edata)) < 0)
 		return FALSE;
 
 	/* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API
@@ -987,7 +1122,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 		}
 	}
 
-	return ethtool_call_ifindex (ifindex, &edata) >= 0;
+	return _ethtool_call_handle (&shandle, &edata, sizeof (edata)) >= 0;
 }
 
 gboolean
@@ -995,7 +1130,10 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
                                    NMSettingWiredWakeOnLan wol,
                                    const char *wol_password)
 {
-	struct ethtool_wolinfo wol_info = { };
+	struct ethtool_wolinfo wol_info = {
+		.cmd = ETHTOOL_SWOL,
+		.wolopts = 0,
+	};
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
@@ -1005,9 +1143,6 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 	nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'",
 	            ifindex, (unsigned) wol, wol_password);
 
-	wol_info.cmd = ETHTOOL_SWOL;
-	wol_info.wolopts = 0;
-
 	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
 		wol_info.wolopts |= WAKE_PHY;
 	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
@@ -1029,7 +1164,7 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 		wol_info.wolopts |= WAKE_MAGICSECURE;
 	}
 
-	return ethtool_call_ifindex (ifindex, &wol_info) >= 0;
+	return _ethtool_call_once (ifindex, &wol_info, sizeof (wol_info)) >= 0;
 }
 
 /******************************************************************************
@@ -1039,40 +1174,44 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 gboolean
 nmp_utils_mii_supports_carrier_detect (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
 	int r;
 	struct ifreq ifr;
 	struct mii_ioctl_data *mii;
-	int errsv;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s",
-		              ifindex,
-		              nm_strerror_native (-r));
+	r = _ioctl_call ("mii",
+	                 "SIOCGMIIPHY",
+	                 SIOCGMIIPHY,
+	                 shandle.ifindex,
+	                 &shandle.fd,
+	                 shandle.ifname,
+	                 IOCTL_CALL_DATA_TYPE_NONE,
+	                 NULL,
+	                 0,
+	                 &ifr);
+	if (r < 0)
 		return FALSE;
-	}
-
-	memset (&ifr, 0, sizeof (struct ifreq));
-	memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ);
-
-	if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) {
-		errsv = errno;
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
-		return FALSE;
-	}
 
 	/* If we can read the BMSR register, we assume that the card supports MII link detection */
 	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
 	mii->reg_num = MII_BMSR;
 
-	if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) {
-		errsv = errno;
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
+	r = _ioctl_call ("mii",
+	                 "SIOCGMIIREG",
+	                 SIOCGMIIREG,
+	                 shandle.ifindex,
+	                 &shandle.fd,
+	                 shandle.ifname,
+	                 IOCTL_CALL_DATA_TYPE_IFRU,
+	                 mii,
+	                 sizeof (*mii),
+	                 &ifr);
+	if (r < 0)
 		return FALSE;
-	}
 
+	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
 	nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, shandle.ifname, mii->val_out);
 	return TRUE;
 }
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index f5491679..ae3f51e2 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 1fc0ccb7..6ead15ed 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -34,6 +33,7 @@
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/rtnetlink.h>
+#include <linux/tc_act/tc_mirred.h>
 #include <libudev.h>
 
 #include "nm-utils.h"
@@ -50,12 +50,71 @@
 
 /*****************************************************************************/
 
-G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWADDR_LEN_MAX);
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
 
+/*****************************************************************************/
+
+G_STATIC_ASSERT (sizeof ( ((NMPLinkAddress *) NULL)->data ) == NM_UTILS_HWADDR_LEN_MAX);
+G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->l_address.data ) == NM_UTILS_HWADDR_LEN_MAX);
+G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->l_broadcast.data ) == NM_UTILS_HWADDR_LEN_MAX);
+
+static const char *
+_nmp_link_address_to_string (const NMPLinkAddress *addr,
+                             char buf[static (NM_UTILS_HWADDR_LEN_MAX * 3)])
+{
+	nm_assert (addr);
+
+	if (addr->len > 0) {
+		if (!nm_utils_hwaddr_ntoa_buf (addr->data,
+		                               addr->len,
+		                               TRUE,
+		                               buf,
+		                               NM_UTILS_HWADDR_LEN_MAX * 3)) {
+			buf[0] = '\0';
+			g_return_val_if_reached (buf);
+		}
+	} else
+		buf[0] = '\0';
+
+	return buf;
+}
+
+gconstpointer
+nmp_link_address_get (const NMPLinkAddress *addr, size_t *length)
+{
+	if (   !addr
+	    || addr->len <= 0) {
+		NM_SET_OUT (length, 0);
+		return NULL;
+	}
+
+	if (addr->len > NM_UTILS_HWADDR_LEN_MAX) {
+		NM_SET_OUT (length, 0);
+		g_return_val_if_reached (NULL);
+	}
+
+	NM_SET_OUT (length, addr->len);
+	return addr->data;
+}
+
+GBytes *
+nmp_link_address_get_as_bytes (const NMPLinkAddress *addr)
+{
+	gconstpointer data;
+	size_t length;
+
+	data = nmp_link_address_get (addr, &length);
+
+	return   length > 0
+	       ? g_bytes_new (data, length)
+	       : NULL;
+}
+
+/*****************************************************************************/
+
 #define _NMLOG_DOMAIN           LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME      "platform"
 
@@ -220,7 +279,8 @@ nm_platform_setup (NMPlatform *instance)
 
 	nm_singleton_instance_register ();
 
-	nm_log_dbg (LOGD_CORE, "setup %s singleton (%p, %s)", "NMPlatform", singleton_instance, G_OBJECT_TYPE_NAME (instance));
+	nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
+	            "NMPlatform", NM_HASH_OBFUSCATE_PTR (instance));
 }
 
 /**
@@ -479,6 +539,40 @@ nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const c
 	return klass->sysctl_set (self, pathid, dirfd, path, value);
 }
 
+/**
+ * nm_platform_sysctl_set_async:
+ * @self: platform instance
+ * @pathid: if @dirfd is present, this must be the full path that is looked up
+ * @dirfd: optional file descriptor for parent directory for openat()
+ * @path: absolute option path
+ * @values: NULL-terminated array of strings to be written
+ * @callback: function called on termination
+ * @data: data passed to callback function
+ * @cancellable: to cancel the operation
+ *
+ * This function is intended to be used for writing values to sysctl-style
+ * virtual runtime configuration files. This includes not only /proc/sys
+ * but also for example /sys/class. The function does not block and returns
+ * immediately. The callback is always invoked, and asynchronously. The file
+ * is closed after writing each value and reopened to write the next one so
+ * that the function can be used safely on all /proc and /sys files,
+ * independently of how /proc/sys/kernel/sysctl_writes_strict is configured.
+ */
+void nm_platform_sysctl_set_async (NMPlatform *self,
+                                   const char *pathid,
+                                   int dirfd,
+                                   const char *path,
+                                   const char *const *values,
+                                   NMPlatformAsyncCallback callback,
+                                   gpointer data,
+                                   GCancellable *cancellable)
+{
+	_CHECK_SELF_VOID (self, klass);
+
+	klass->sysctl_set_async (self, pathid, dirfd, path, values, callback, data, cancellable);
+}
+
+
 gboolean
 nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
                                                     const char *iface,
@@ -621,14 +715,14 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self,
 /*****************************************************************************/
 
 char *
-nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_get (NMPlatform *self,
                                 int addr_family,
                                 const char *ifname,
                                 const char *property)
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	return nm_platform_sysctl_get (platform,
+	return nm_platform_sysctl_get (self,
 	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                         buf,
 	                                                                                         ifname,
@@ -636,7 +730,7 @@ nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
 }
 
 gint64
-nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *self,
                                             int addr_family,
                                             const char *ifname,
                                             const char *property,
@@ -647,7 +741,7 @@ nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	return nm_platform_sysctl_get_int_checked (platform,
+	return nm_platform_sysctl_get_int_checked (self,
 	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                                     buf,
 	                                                                                                     ifname,
@@ -659,7 +753,7 @@ nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
 }
 
 gboolean
-nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_set (NMPlatform *self,
                                 int addr_family,
                                 const char *ifname,
                                 const char *property,
@@ -667,7 +761,7 @@ nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	return nm_platform_sysctl_set (platform,
+	return nm_platform_sysctl_set (self,
 	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                         buf,
 	                                                                                         ifname,
@@ -676,7 +770,7 @@ nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
 }
 
 gboolean
-nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *self,
                                       int addr_family,
                                       const char *ifname,
                                       const char *property,
@@ -685,7 +779,7 @@ nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 	char s[64];
 
-	return nm_platform_sysctl_set (platform,
+	return nm_platform_sysctl_set (self,
 	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                         buf,
 	                                                                                         ifname,
@@ -693,6 +787,47 @@ nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
 	                               nm_sprintf_buf (s, "%"G_GINT64_FORMAT, value));
 }
 
+int
+nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (NMPlatform *self,
+                                               const char *ifname,
+                                               gboolean consider_all,
+                                               gboolean *out_due_to_all)
+{
+	int val, val_all;
+
+	NM_SET_OUT (out_due_to_all, FALSE);
+
+	if (!ifname)
+		return -1;
+
+	val = nm_platform_sysctl_ip_conf_get_int_checked (self,
+	                                                  AF_INET,
+	                                                  ifname,
+	                                                  "rp_filter",
+	                                                  10, 0, 2, -1);
+	if (val == -1)
+		return -1;
+
+	/* the effectively used value is the rp_filter sysctl value of MAX(all,ifname).
+	 * Note that this is the numerical MAX(), despite rp_filter "1" being more strict
+	 * than "2". */
+	if (   val < 2
+	    && consider_all
+	    && !nm_streq (ifname, "all")) {
+		val_all = nm_platform_sysctl_ip_conf_get_int_checked (self,
+		                                                      AF_INET,
+		                                                      "all",
+		                                                      "rp_filter",
+		                                                      10, 0, 2, val);
+		if (val_all > val) {
+			val = val_all;
+			NM_SET_OUT (out_due_to_all, TRUE);
+		}
+	}
+
+	return val;
+}
+
 /*****************************************************************************/
 
 static int
@@ -905,14 +1040,15 @@ nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname)
 }
 
 struct _nm_platform_link_get_by_address_data {
-	gconstpointer address;
-	guint8 length;
+	gconstpointer data;
+	guint8 len;
 };
 
 static gboolean
 _nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_platform_link_get_by_address_data *d)
 {
-	return obj->link.addr.len == d->length && !memcmp (obj->link.addr.data, d->address, d->length);
+	return    obj->link.l_address.len == d->len
+	       && !memcmp (obj->link.l_address.data, d->data, d->len);
 }
 
 /**
@@ -932,8 +1068,8 @@ nm_platform_link_get_by_address (NMPlatform *self,
 {
 	const NMPObject *obj;
 	struct _nm_platform_link_get_by_address_data d = {
-		.address = address,
-		.length = length,
+		.data = address,
+		.len  = length,
 	};
 
 	_CHECK_SELF (self, klass, NULL);
@@ -1471,19 +1607,7 @@ nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 	const NMPlatformLink *pllink;
 
 	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);
-	}
-
-	NM_SET_OUT (length, pllink->addr.len);
-	return pllink->addr.data;
+	return nmp_link_address_get (pllink ? &pllink->l_address : NULL, length);
 }
 
 /**
@@ -1551,19 +1675,35 @@ nm_platform_link_supports_sriov (NMPlatform *self, int ifindex)
  * @num_vfs: the number of VFs to create
  * @autoprobe: the new autoprobe-drivers value (pass
  *     %NM_TERNARY_DEFAULT to keep current value)
+ * @callback: called when the operation finishes
+ * @callback_data: data passed to @callback
+ * @cancellable: cancellable to abort the operation
+ *
+ * Sets SR-IOV parameters asynchronously without
+ * blocking the main thread. The callback function is
+ * always invoked, and asynchronously.
  */
-gboolean
-nm_platform_link_set_sriov_params (NMPlatform *self,
-                                   int ifindex,
-                                   guint num_vfs,
-                                   NMTernary autoprobe)
+void
+nm_platform_link_set_sriov_params_async (NMPlatform *self,
+                                         int ifindex,
+                                         guint num_vfs,
+                                         NMTernary autoprobe,
+                                         NMPlatformAsyncCallback callback,
+                                         gpointer callback_data,
+                                         GCancellable *cancellable)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF_VOID (self, klass);
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_if_fail (ifindex > 0);
 
 	_LOG3D ("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe);
-	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
+	klass->link_set_sriov_params_async (self,
+	                                    ifindex,
+	                                    num_vfs,
+	                                    autoprobe,
+	                                    callback,
+	                                    callback_data,
+	                                    cancellable);
 }
 
 gboolean
@@ -2369,9 +2509,10 @@ nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out
 	if (out_parent) {
 		const NMPlatformLink *parent_plink;
 
-		parent_plink = nm_platform_link_get_by_address (self, NM_LINK_TYPE_WPAN,
-		                                                plink->addr.data,
-		                                                plink->addr.len);
+		parent_plink = nm_platform_link_get_by_address (self,
+		                                                NM_LINK_TYPE_WPAN,
+		                                                plink->l_address.data,
+		                                                plink->l_address.len);
 		NM_SET_OUT (out_parent, parent_plink ? parent_plink->ifindex : -1);
 	}
 
@@ -3400,21 +3541,21 @@ nm_platform_ethtool_set_features (NMPlatform *self,
 /*****************************************************************************/
 
 const NMDedupMultiHeadEntry *
-nm_platform_lookup_all (NMPlatform *platform,
+nm_platform_lookup_all (NMPlatform *self,
                         NMPCacheIdType cache_id_type,
                         const NMPObject *obj)
 {
-	return nmp_cache_lookup_all (nm_platform_get_cache (platform),
+	return nmp_cache_lookup_all (nm_platform_get_cache (self),
 	                             cache_id_type,
 	                             obj);
 }
 
 const NMDedupMultiEntry *
-nm_platform_lookup_entry (NMPlatform *platform,
+nm_platform_lookup_entry (NMPlatform *self,
                           NMPCacheIdType cache_id_type,
                           const NMPObject *obj)
 {
-	return nmp_cache_lookup_entry_with_idx_type (nm_platform_get_cache (platform),
+	return nmp_cache_lookup_entry_with_idx_type (nm_platform_get_cache (self),
 	                                             cache_id_type,
 	                                             obj);
 }
@@ -5076,10 +5217,27 @@ nm_platform_qdisc_add (NMPlatform *self,
 	int ifindex = qdisc->ifindex;
 	_CHECK_SELF (self, klass, -NME_BUG);
 
+	/* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
+	 * is undefined. */
+
 	_LOG3D ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
 	return klass->qdisc_add (self, flags, qdisc);
 }
 
+/**
+ * nm_platform_qdisc_sync:
+ * @self: the #NMPlatform instance
+ * @ifindex: the ifindex where to configure the qdiscs.
+ * @known_qdiscs: the list of qdiscs (#NMPObject).
+ *
+ * The function promises not to take any reference to the qdisc
+ * instances from @known_qdiscs, nor to keep them around after
+ * the function returns. This is important, because it allows the
+ * caller to pass NMPlatformQdisc instances which "kind" string
+ * have a limited lifetime.
+ *
+ * Returns: %TRUE on success.
+ */
 gboolean
 nm_platform_qdisc_sync (NMPlatform *self,
                         int ifindex,
@@ -5142,10 +5300,27 @@ nm_platform_tfilter_add (NMPlatform *self,
 	int ifindex = tfilter->ifindex;
 	_CHECK_SELF (self, klass, -NME_BUG);
 
+	/* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
+	 * and tfilter.action.kind is undefined. */
+
 	_LOG3D ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
 	return klass->tfilter_add (self, flags, tfilter);
 }
 
+/**
+ * nm_platform_qdisc_sync:
+ * @self: the #NMPlatform instance
+ * @ifindex: the ifindex where to configure the qdiscs.
+ * @known_tfilters: the list of tfilters (#NMPObject).
+ *
+ * The function promises not to take any reference to the tfilter
+ * instances from @known_tfilters, nor to keep them around after
+ * the function returns. This is important, because it allows the
+ * caller to pass NMPlatformTfilter instances which "kind" string
+ * have a limited lifetime.
+ *
+ * Returns: %TRUE on success.
+ */
 gboolean
 nm_platform_tfilter_sync (NMPlatform *self,
                           int ifindex,
@@ -5268,31 +5443,36 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 {
 	char master[20];
 	char parent[20];
-	GString *str_flags;
+	char str_flags[1 + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 1];
+	char str_highlighted_flags[50];
+	char *s;
+	gsize l;
 	char str_addrmode[30];
-	gs_free char *str_addr = NULL;
+	char str_address[NM_UTILS_HWADDR_LEN_MAX * 3];
+	char str_broadcast[NM_UTILS_HWADDR_LEN_MAX * 3];
 	char str_inet6_token[NM_UTILS_INET_ADDRSTRLEN];
 	const char *str_link_type;
 
 	if (!nm_utils_to_string_buffer_init_null (link, &buf, &len))
 		return buf;
 
-	str_flags = g_string_new (NULL);
+	s = str_highlighted_flags;
+	l = sizeof (str_highlighted_flags);
 	if (NM_FLAGS_HAS (link->n_ifi_flags, IFF_NOARP))
-		g_string_append (str_flags, "NOARP,");
+		nm_utils_strbuf_append_str (&s, &l, "NOARP,");
 	if (NM_FLAGS_HAS (link->n_ifi_flags, IFF_UP))
-		g_string_append (str_flags, "UP");
+		nm_utils_strbuf_append_str (&s, &l, "UP");
 	else
-		g_string_append (str_flags, "DOWN");
+		nm_utils_strbuf_append_str (&s, &l, "DOWN");
 	if (link->connected)
-		g_string_append (str_flags, ",LOWER_UP");
+		nm_utils_strbuf_append_str (&s, &l, ",LOWER_UP");
+	nm_assert (s > str_highlighted_flags && l > 0);
 
 	if (link->n_ifi_flags) {
-		char str_flags_buf[64];
-
-		nm_platform_link_flags2str (link->n_ifi_flags, str_flags_buf, sizeof (str_flags_buf));
-		g_string_append_printf (str_flags, ";%s", str_flags_buf);
-	}
+		str_flags[0] = ';';
+		nm_platform_link_flags2str (link->n_ifi_flags, &str_flags[1], sizeof (str_flags) - 1);
+	} else
+		str_flags[0] = '\0';
 
 	if (link->master)
 		g_snprintf (master, sizeof (master), " master %d", link->master);
@@ -5306,8 +5486,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	else
 		parent[0] = 0;
 
-	if (link->addr.len)
-		str_addr = nm_utils_hwaddr_ntoa (link->addr.data, MIN (link->addr.len, sizeof (link->addr.data)));
+	_nmp_link_address_to_string (&link->l_address, str_address);
+	_nmp_link_address_to_string (&link->l_broadcast, str_broadcast);
 
 	str_link_type = nm_link_type_to_string (link->type);
 
@@ -5315,7 +5495,7 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            "%d: " /* ifindex */
 	            "%s" /* name */
 	            "%s" /* parent */
-	            " <%s>" /* flags */
+	            " <%s%s>" /* flags */
 	            " mtu %d"
 	            "%s" /* master */
 	            " arp %u" /* arptype */
@@ -5323,7 +5503,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            "%s%s" /* kind */
 	            "%s" /* is-in-udev */
 	            "%s%s" /* addr-gen-mode */
-	            "%s%s" /* addr */
+	            "%s%s" /* l_address */
+	            "%s%s" /* l_broadcast */
 	            "%s%s" /* inet6_token */
 	            "%s%s" /* driver */
 	            " rx:%"G_GUINT64_FORMAT",%"G_GUINT64_FORMAT
@@ -5332,7 +5513,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            link->ifindex,
 	            link->name,
 	            parent,
-	            str_flags->str,
+	            str_highlighted_flags,
+	            str_flags,
 	            link->mtu, master,
 	            link->arptype,
 	            str_link_type ?: "???",
@@ -5341,15 +5523,16 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            link->initialized ? " init" : " not-init",
 	            link->inet6_addr_gen_mode_inv ? " addrgenmode " : "",
 	            link->inet6_addr_gen_mode_inv ? nm_platform_link_inet6_addrgenmode2str (_nm_platform_uint8_inv (link->inet6_addr_gen_mode_inv), str_addrmode, sizeof (str_addrmode)) : "",
-	            str_addr ? " addr " : "",
-	            str_addr ?: "",
+	            str_address[0] ? " addr " : "",
+	            str_address[0] ? str_address : "",
+	            str_broadcast[0] ? " brd " : "",
+	            str_broadcast[0] ? str_broadcast : "",
 	            link->inet6_token.id ? " inet6token " : "",
 	            link->inet6_token.id ? nm_utils_inet6_interface_identifier_to_token (link->inet6_token, str_inet6_token) : "",
 	            link->driver ? " driver " : "",
 	            link->driver ?: "",
 	            link->rx_packets, link->rx_bytes,
 	            link->tx_packets, link->tx_bytes);
-	g_string_free (str_flags, TRUE);
 	return buf;
 }
 
@@ -6426,19 +6609,41 @@ const char *
 nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	const char *buf0;
 
 	if (!nm_utils_to_string_buffer_init_null (qdisc, &buf, &len))
 		return buf;
 
-	g_snprintf (buf, len, "%s%s family %d handle %x parent %x info %x",
-	            qdisc->kind,
-	            _to_string_dev (NULL, qdisc->ifindex, str_dev, sizeof (str_dev)),
-	            qdisc->addr_family,
-	            qdisc->handle,
-	            qdisc->parent,
-	            qdisc->info);
+	buf0 = buf;
 
-	return buf;
+	nm_utils_strbuf_append (&buf, &len, "%s%s family %u handle %x parent %x info %x",
+	                        qdisc->kind,
+	                        _to_string_dev (NULL, qdisc->ifindex, str_dev, sizeof (str_dev)),
+	                        qdisc->addr_family,
+	                        qdisc->handle,
+	                        qdisc->parent,
+	                        qdisc->info);
+
+	if (nm_streq0 (qdisc->kind, "fq_codel")) {
+		if (qdisc->fq_codel.limit)
+			nm_utils_strbuf_append (&buf, &len, " limit %u", qdisc->fq_codel.limit);
+		if (qdisc->fq_codel.flows)
+			nm_utils_strbuf_append (&buf, &len, " flows %u", qdisc->fq_codel.flows);
+		if (qdisc->fq_codel.target)
+			nm_utils_strbuf_append (&buf, &len, " target %u", qdisc->fq_codel.target);
+		if (qdisc->fq_codel.interval)
+			nm_utils_strbuf_append (&buf, &len, " interval %u", qdisc->fq_codel.interval);
+		if (qdisc->fq_codel.quantum)
+			nm_utils_strbuf_append (&buf, &len, " quantum %u", qdisc->fq_codel.quantum);
+		if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
+			nm_utils_strbuf_append (&buf, &len, " ce_threshold %u", qdisc->fq_codel.ce_threshold);
+		if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
+			nm_utils_strbuf_append (&buf, &len, " memory_limit %u", qdisc->fq_codel.memory_limit);
+		if (qdisc->fq_codel.ecn)
+			nm_utils_strbuf_append (&buf, &len, " ecn");
+	}
+
+	return buf0;
 }
 
 void
@@ -6451,6 +6656,18 @@ nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h)
 	                     obj->handle,
 	                     obj->parent,
 	                     obj->info);
+	if (nm_streq0 (obj->kind, "fq_codel")) {
+		nm_hash_update_vals (h,
+		                     obj->fq_codel.limit,
+		                     obj->fq_codel.flows,
+		                     obj->fq_codel.target,
+		                     obj->fq_codel.interval,
+		                     obj->fq_codel.quantum,
+		                     obj->fq_codel.ce_threshold,
+		                     obj->fq_codel.memory_limit,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->fq_codel.ecn));
+	}
 }
 
 int
@@ -6464,6 +6681,17 @@ nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b)
 	NM_CMP_FIELD (a, b, handle);
 	NM_CMP_FIELD (a, b, info);
 
+	if (nm_streq0 (a->kind, "fq_codel")) {
+		NM_CMP_FIELD (a, b, fq_codel.limit);
+		NM_CMP_FIELD (a, b, fq_codel.flows);
+		NM_CMP_FIELD (a, b, fq_codel.target);
+		NM_CMP_FIELD (a, b, fq_codel.interval);
+		NM_CMP_FIELD (a, b, fq_codel.quantum);
+		NM_CMP_FIELD (a, b, fq_codel.ce_threshold);
+		NM_CMP_FIELD (a, b, fq_codel.memory_limit);
+		NM_CMP_FIELD_UNSAFE (a, b, fq_codel.ecn);
+	}
+
 	return 0;
 }
 
@@ -6492,11 +6720,18 @@ nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsiz
 			                                                        NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
 			                                                      | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
 			                                                      &t));
+		} else if (nm_streq (tfilter->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			nm_utils_strbuf_append (&p, &l, "%s%s%s%s dev %d",
+			                        tfilter->action.mirred.ingress ? " ingress" : "",
+			                        tfilter->action.mirred.egress ? " egress" : "",
+			                        tfilter->action.mirred.mirror ? " mirror" : "",
+			                        tfilter->action.mirred.redirect ? " redirect" : "",
+			                        tfilter->action.mirred.ifindex);
 		}
 	} else
 		act_buf[0] = '\0';
 
-	g_snprintf (buf, len, "%s%s family %d handle %x parent %x info %x%s",
+	g_snprintf (buf, len, "%s%s family %u handle %x parent %x info %x%s",
 	            tfilter->kind,
 	            _to_string_dev (NULL, tfilter->ifindex, str_dev, sizeof (str_dev)),
 	            tfilter->addr_family,
@@ -6520,8 +6755,17 @@ nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h)
 	                     obj->info);
 	if (obj->action.kind) {
 		nm_hash_update_str (h, obj->action.kind);
-		if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+		if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
 			nm_hash_update_strarr (h, obj->action.simple.sdata);
+		} else if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			nm_hash_update_vals (h,
+			                     obj->action.mirred.ifindex,
+			                     NM_HASH_COMBINE_BOOLS (guint8,
+			                                            obj->action.mirred.ingress,
+			                                            obj->action.mirred.egress,
+			                                            obj->action.mirred.mirror,
+			                                            obj->action.mirred.redirect));
+		}
 	}
 }
 
@@ -6538,8 +6782,15 @@ nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b)
 
 	NM_CMP_FIELD_STR_INTERNED (a, b, action.kind);
 	if (a->action.kind) {
-		if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+		if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
 			NM_CMP_FIELD_STR (a, b, action.simple.sdata);
+		} else if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			NM_CMP_FIELD (a, b, action.mirred.ifindex);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.ingress);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.egress);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.mirror);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.redirect);
+		}
 	}
 
 	return 0;
@@ -6641,7 +6892,8 @@ nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
 	nm_hash_update_str0 (h, obj->kind);
 	nm_hash_update_str0 (h, obj->driver);
 	/* nm_hash_update_mem() also hashes the length obj->addr.len */
-	nm_hash_update_mem (h, obj->addr.data, obj->addr.len);
+	nm_hash_update_mem (h, obj->l_address.data, NM_MIN (obj->l_address.len, sizeof (obj->l_address.data)));
+	nm_hash_update_mem (h, obj->l_broadcast.data, NM_MIN (obj->l_broadcast.len, sizeof (obj->l_broadcast.data)));
 }
 
 int
@@ -6658,12 +6910,15 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 	NM_CMP_FIELD (a, b, mtu);
 	NM_CMP_FIELD_BOOL (a, b, initialized);
 	NM_CMP_FIELD (a, b, arptype);
-	NM_CMP_FIELD (a, b, addr.len);
+	NM_CMP_FIELD (a, b, l_address.len);
+	NM_CMP_FIELD (a, b, l_broadcast.len);
 	NM_CMP_FIELD (a, b, inet6_addr_gen_mode_inv);
 	NM_CMP_FIELD_STR_INTERNED (a, b, kind);
 	NM_CMP_FIELD_STR_INTERNED (a, b, driver);
-	if (a->addr.len)
-		NM_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
+	if (a->l_address.len)
+		NM_CMP_FIELD_MEMCMP_LEN (a, b, l_address.data, a->l_address.len);
+	if (a->l_broadcast.len)
+		NM_CMP_FIELD_MEMCMP_LEN (a, b, l_broadcast.data, a->l_broadcast.len);
 	NM_CMP_FIELD_MEMCMP (a, b, inet6_token);
 	NM_CMP_FIELD (a, b, rx_packets);
 	NM_CMP_FIELD (a, b, rx_bytes);
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 97be8832..248eca44 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -159,6 +158,14 @@ typedef enum {
 	NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL,
 } NMPlatformRoutingRuleCmpType;
 
+typedef struct {
+	guint8 data[20 /* NM_UTILS_HWADDR_LEN_MAX */ ];
+	guint8 len;
+} NMPLinkAddress;
+
+gconstpointer nmp_link_address_get (const NMPLinkAddress *addr, size_t *length);
+GBytes       *nmp_link_address_get_as_bytes (const NMPLinkAddress *addr);
+
 typedef enum {
 
 	/* match-flags are strictly inclusive. That means,
@@ -230,11 +237,11 @@ struct _NMPlatformLink {
 	/* rtnl_link_get_arptype(), ifinfomsg.ifi_type. */
 	guint32 arptype;
 
-	/* rtnl_link_get_addr(), IFLA_ADDRESS */
-	struct {
-		guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */
-		guint8 len;
-	} addr;
+	/* IFLA_ADDRESS */
+	NMPLinkAddress l_address;
+
+	/* IFLA_BROADCAST */
+	NMPLinkAddress l_broadcast;
 
 	/* rtnl_link_inet6_get_token(), IFLA_INET6_TOKEN */
 	NMUtilsIPv6IfaceId inet6_token;
@@ -596,13 +603,48 @@ typedef struct {
 	bool     uid_range_has:1;            /* has(FRA_UID_RANGE) */
 } NMPlatformRoutingRule;
 
+#define NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET   (~((guint32) 0))
+
+#define NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED ((guint32) 0x83126E97u)
+
+G_STATIC_ASSERT (((((guint64) NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) * 1000u) >> 10) == (guint64) INT_MAX);
+
+typedef struct {
+	guint32 limit;
+	guint32 flows;
+	guint32 target;
+	guint32 interval;
+	guint32 quantum;
+	guint32 ce_threshold; /* TCA_FQ_CODEL_CE_THRESHOLD: kernel internally stores this value as
+	                       *   ((val64 * NSEC_PER_USEC) >> CODEL_SHIFT). The default value (in
+	                       *   the domain with this coersion) is CODEL_DISABLED_THRESHOLD (INT_MAX).
+	                       *   That means, "disabled" is expressed on RTM_NEWQDISC netlink API by absence of the
+	                       *   netlink attribute but also as the special value 0x83126E97u
+	                       *   (NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED).
+	                       *   Beware: zero is not the default you must always explicitly set this value. */
+	guint32 memory_limit; /* TCA_FQ_CODEL_MEMORY_LIMIT: note that only values <= 2^31 are accepted by kernel
+	                       *   and kernel defaults to 32MB.
+	                       *   Note that we use the special value NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET
+	                       *   to indicate that no explicit limit is set (when we send a RTM_NEWQDISC request).
+	                       *   This will cause kernel to choose the default (32MB).
+	                       *   Beware: zero is not the default you must always explicitly set this value. */
+	bool ecn:1;
+} NMPlatformQdiscFqCodel;
+
 typedef struct {
 	__NMPlatformObjWithIfindex_COMMON;
+
+	/* beware, kind is embedded in an NMPObject, hence you must
+	 * take care of the lifetime of the string. */
 	const char *kind;
+
 	int addr_family;
 	guint32 handle;
 	guint32 parent;
 	guint32 info;
+	union {
+		NMPlatformQdiscFqCodel fq_codel;
+	};
 } NMPlatformQdisc;
 
 typedef struct {
@@ -610,17 +652,35 @@ typedef struct {
 } NMPlatformActionSimple;
 
 typedef struct {
+	int ifindex;
+	bool egress:1;
+	bool ingress:1;
+	bool mirror:1;
+	bool redirect:1;
+} NMPlatformActionMirred;
+
+typedef struct {
+
+	/* beware, kind is embedded in an NMPObject, hence you must
+	 * take care of the lifetime of the string. */
 	const char *kind;
+
 	union {
 		NMPlatformActionSimple simple;
+		NMPlatformActionMirred mirred;
 	};
 } NMPlatformAction;
 
 #define NM_PLATFORM_ACTION_KIND_SIMPLE "simple"
+#define NM_PLATFORM_ACTION_KIND_MIRRED "mirred"
 
 typedef struct {
 	__NMPlatformObjWithIfindex_COMMON;
+
+	/* beware, kind is embedded in an NMPObject, hence you must
+	 * take care of the lifetime of the string. */
 	const char *kind;
+
 	int addr_family;
 	guint32 handle;
 	guint32 parent;
@@ -842,6 +902,8 @@ typedef enum {
 
 } NMPlatformWireGuardChangePeerFlags;
 
+typedef void (*NMPlatformAsyncCallback) (GError *error, gpointer user_data);
+
 /*****************************************************************************/
 
 typedef enum {
@@ -864,28 +926,28 @@ extern volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPOR
 int _nm_platform_kernel_support_init (NMPlatformKernelSupportType type,
                                       int value);
 
-#define _nm_platform_kernel_support_detected(type) \
-	G_LIKELY (({ \
-		const NMPlatformKernelSupportType _type = (type); \
-		\
-		nm_assert (_NM_INT_NOT_NEGATIVE (_type) && _type < G_N_ELEMENTS (_nm_platform_kernel_support_state)); \
-		\
-		(_nm_platform_kernel_support_state[_type] != 0); \
-	}))
-
-#define nm_platform_kernel_support_get(type) \
-	({ \
-		const NMPlatformKernelSupportType _type = (type); \
-		int _v; \
-		\
-		nm_assert (_NM_INT_NOT_NEGATIVE (_type) && _type < G_N_ELEMENTS (_nm_platform_kernel_support_state)); \
-		\
-		_v = _nm_platform_kernel_support_state[_type]; \
-		if (G_UNLIKELY (_v == 0)) \
-			_v = _nm_platform_kernel_support_init (_type, 0); \
-		\
-		(_v >= 0); \
-	})
+static inline gboolean
+_nm_platform_kernel_support_detected (NMPlatformKernelSupportType type)
+{
+	nm_assert (   _NM_INT_NOT_NEGATIVE (type)
+	           && type < G_N_ELEMENTS (_nm_platform_kernel_support_state));
+
+	return G_LIKELY (_nm_platform_kernel_support_state[type] != 0);
+}
+
+static inline gboolean
+nm_platform_kernel_support_get (NMPlatformKernelSupportType type)
+{
+	int v;
+
+	nm_assert (_NM_INT_NOT_NEGATIVE (type)
+	           && type < G_N_ELEMENTS (_nm_platform_kernel_support_state));
+
+	v = _nm_platform_kernel_support_state[type];
+	if (G_UNLIKELY (v == 0))
+		v = _nm_platform_kernel_support_init (type, 0);
+	return (v >= 0);
+}
 
 /*****************************************************************************/
 
@@ -900,64 +962,75 @@ struct _NMPlatform {
 typedef struct {
 	GObjectClass parent;
 
-	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);
+	gboolean (*sysctl_set) (NMPlatform *self, const char *pathid, int dirfd, const char *path, const char *value);
+	void (*sysctl_set_async)  (NMPlatform *self,
+	                           const char *pathid,
+	                           int dirfd,
+	                           const char *path,
+	                           const char *const *values,
+	                           NMPlatformAsyncCallback callback,
+	                           gpointer data,
+	                           GCancellable *cancellable);
+	char * (*sysctl_get) (NMPlatform *self, const char *pathid, int dirfd, const char *path);
+
+	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
+	void (*process_events) (NMPlatform *self);
 
-	int (*link_add) (NMPlatform *,
+	int (*link_add) (NMPlatform *self,
 	                 const char *name,
 	                 NMLinkType type,
 	                 const char *veth_peer,
 	                 const void *address,
 	                 size_t address_len,
 	                 const NMPlatformLink **out_link);
-
-	gboolean (*link_delete) (NMPlatform *, int ifindex);
-
-	gboolean (*link_refresh) (NMPlatform *, int ifindex);
-
-	gboolean (*link_set_netns) (NMPlatform *, int ifindex, int netns_fd);
-
-	void (*process_events) (NMPlatform *self);
-
-	gboolean (*link_set_up) (NMPlatform *, int ifindex, gboolean *out_no_firmware);
-	gboolean (*link_set_down) (NMPlatform *, int ifindex);
-	gboolean (*link_set_arp) (NMPlatform *, int ifindex);
-	gboolean (*link_set_noarp) (NMPlatform *, int ifindex);
+	gboolean (*link_delete) (NMPlatform *self, int ifindex);
+	gboolean (*link_refresh) (NMPlatform *self, int ifindex);
+	gboolean (*link_set_netns) (NMPlatform *self, int ifindex, int netns_fd);
+	gboolean (*link_set_up) (NMPlatform *self, int ifindex, gboolean *out_no_firmware);
+	gboolean (*link_set_down) (NMPlatform *self, int ifindex);
+	gboolean (*link_set_arp) (NMPlatform *self, int ifindex);
+	gboolean (*link_set_noarp) (NMPlatform *self, int ifindex);
 
 	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
 	struct udev_device *(*link_get_udev_device) (NMPlatform *self, int ifindex);
 
-	int (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
-	gboolean (*link_set_token) (NMPlatform *, int ifindex, NMUtilsIPv6IfaceId iid);
+	int (*link_set_user_ipv6ll_enabled) (NMPlatform *self, int ifindex, gboolean enabled);
+	gboolean (*link_set_token) (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
 
-	gboolean (*link_get_permanent_address) (NMPlatform *,
+	gboolean (*link_get_permanent_address) (NMPlatform *self,
 	                                        int ifindex,
 	                                        guint8 *buf,
 	                                        size_t *length);
-	int (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
-	int (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
-	gboolean (*link_set_name) (NMPlatform *, int ifindex, const char *name);
-	gboolean (*link_set_sriov_params) (NMPlatform *, int ifindex, guint num_vfs, int autoprobe);
+	int (*link_set_address) (NMPlatform *self, int ifindex, gconstpointer address, size_t length);
+	int (*link_set_mtu) (NMPlatform *self, int ifindex, guint32 mtu);
+	gboolean (*link_set_name) (NMPlatform *self, int ifindex, const char *name);
+	void (*link_set_sriov_params_async) (NMPlatform *self,
+	                                     int ifindex,
+	                                     guint num_vfs,
+	                                     int autoprobe,
+	                                     NMPlatformAsyncCallback callback,
+	                                     gpointer callback_data,
+	                                     GCancellable *cancellable);
 	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 	gboolean (*link_set_bridge_vlans) (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans);
 
-	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
-	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
-	gboolean (*link_get_wake_on_lan) (NMPlatform *, int ifindex);
-	gboolean (*link_get_driver_info) (NMPlatform *,
+	char *   (*link_get_physical_port_id) (NMPlatform *self, int ifindex);
+	guint    (*link_get_dev_id) (NMPlatform *self, int ifindex);
+	gboolean (*link_get_wake_on_lan) (NMPlatform *self, int ifindex);
+	gboolean (*link_get_driver_info) (NMPlatform *self,
 	                                  int ifindex,
 	                                  char **out_driver_name,
 	                                  char **out_driver_version,
 	                                  char **out_fw_version);
 
-	gboolean (*link_supports_carrier_detect) (NMPlatform *, int ifindex);
-	gboolean (*link_supports_vlans) (NMPlatform *, int ifindex);
-	gboolean (*link_supports_sriov) (NMPlatform *, int ifindex);
+	gboolean (*link_supports_carrier_detect) (NMPlatform *self, int ifindex);
+	gboolean (*link_supports_vlans) (NMPlatform *self, int ifindex);
+	gboolean (*link_supports_sriov) (NMPlatform *self, int ifindex);
 
-	gboolean (*link_enslave) (NMPlatform *, int master, int slave);
-	gboolean (*link_release) (NMPlatform *, int master, int slave);
+	gboolean (*link_enslave) (NMPlatform *self, int master, int slave);
+	gboolean (*link_release) (NMPlatform *self, int master, int slave);
 
-	gboolean (*link_can_assume) (NMPlatform *, int ifindex);
+	gboolean (*link_can_assume) (NMPlatform *self, int ifindex);
 
 	int (*link_wireguard_change) (NMPlatform *self,
 	                              int ifindex,
@@ -967,7 +1040,7 @@ typedef struct {
 	                              guint peers_len,
 	                              NMPlatformWireGuardChangeFlags change_flags);
 
-	gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
+	gboolean (*vlan_add) (NMPlatform *self, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
 	                              int ifindex,
 	                              NMVlanFlags flags_mask,
@@ -978,81 +1051,79 @@ typedef struct {
 	                              gboolean egress_reset_all,
 	                              const NMVlanQosMapping *egress_map,
 	                              gsize n_egress_map);
-	gboolean (*link_vxlan_add) (NMPlatform *,
+	gboolean (*link_vxlan_add) (NMPlatform *self,
 	                            const char *name,
 	                            const NMPlatformLnkVxlan *props,
 	                            const NMPlatformLink **out_link);
-	gboolean (*link_gre_add) (NMPlatform *,
+	gboolean (*link_gre_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkGre *props,
 	                          const NMPlatformLink **out_link);
-	gboolean (*link_ip6tnl_add) (NMPlatform *,
+	gboolean (*link_ip6tnl_add) (NMPlatform *self,
 	                             const char *name,
 	                             const NMPlatformLnkIp6Tnl *props,
 	                             const NMPlatformLink **out_link);
-	gboolean (*link_ip6gre_add) (NMPlatform *,
+	gboolean (*link_ip6gre_add) (NMPlatform *self,
 	                             const char *name,
 	                             const NMPlatformLnkIp6Tnl *props,
 	                             const NMPlatformLink **out_link);
-	gboolean (*link_ipip_add) (NMPlatform *,
+	gboolean (*link_ipip_add) (NMPlatform *self,
 	                           const char *name,
 	                           const NMPlatformLnkIpIp *props,
 	                           const NMPlatformLink **out_link);
-	gboolean (*link_macsec_add) (NMPlatform *,
+	gboolean (*link_macsec_add) (NMPlatform *self,
 	                             const char *name,
 	                             int parent,
 	                             const NMPlatformLnkMacsec *props,
 	                             const NMPlatformLink **out_link);
-	gboolean (*link_macvlan_add) (NMPlatform *,
+	gboolean (*link_macvlan_add) (NMPlatform *self,
 	                              const char *name,
 	                              int parent,
 	                              const NMPlatformLnkMacvlan *props,
 	                              const NMPlatformLink **out_link);
-	gboolean (*link_sit_add) (NMPlatform *,
+	gboolean (*link_sit_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkSit *props,
 	                          const NMPlatformLink **out_link);
-
-	gboolean (*link_tun_add) (NMPlatform *platform,
+	gboolean (*link_tun_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkTun *props,
 	                          const NMPlatformLink **out_link,
 	                          int *out_fd);
-
-	gboolean (*link_6lowpan_add) (NMPlatform *platform,
+	gboolean (*link_6lowpan_add) (NMPlatform *self,
 	                              const char *name,
 	                              int parent,
 	                              const NMPlatformLink **out_link);
 
-	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    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
-	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
-	guint32     (*wifi_get_frequency)    (NMPlatform *, int ifindex);
-	int         (*wifi_get_quality)      (NMPlatform *, int ifindex);
-	guint32     (*wifi_get_rate)         (NMPlatform *, int ifindex);
-	NM80211Mode (*wifi_get_mode)         (NMPlatform *, int ifindex);
-	void        (*wifi_set_mode)         (NMPlatform *, int ifindex, NM80211Mode mode);
-	void        (*wifi_set_powersave)    (NMPlatform *, int ifindex, guint32 powersave);
-	guint32     (*wifi_find_frequency)   (NMPlatform *, int ifindex, const guint32 *freqs);
-	void        (*wifi_indicate_addressing_running) (NMPlatform *, int ifindex, gboolean running);
-	NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan) (NMPlatform *, int ifindex);
-	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *, int ifindex, NMSettingWirelessWakeOnWLan wowl);
-
-	guint32     (*mesh_get_channel)      (NMPlatform *, int ifindex);
-	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
-	gboolean    (*mesh_set_ssid)         (NMPlatform *, int ifindex, const guint8 *ssid, gsize len);
-
-	guint16     (*wpan_get_pan_id)       (NMPlatform *, int ifindex);
-	gboolean    (*wpan_set_pan_id)       (NMPlatform *, int ifindex, guint16 pan_id);
-	guint16     (*wpan_get_short_addr)   (NMPlatform *, int ifindex);
-	gboolean    (*wpan_set_short_addr)   (NMPlatform *, int ifindex, guint16 short_addr);
-	gboolean    (*wpan_set_channel)      (NMPlatform *, int ifindex, guint8 page, guint8 channel);
-
-	gboolean (*object_delete) (NMPlatform *, const NMPObject *obj);
-
-	gboolean (*ip4_address_add) (NMPlatform *,
+	gboolean (*infiniband_partition_add) (NMPlatform *self, int parent, int p_key, const NMPlatformLink **out_link);
+	gboolean (*infiniband_partition_delete) (NMPlatform *self, int parent, int p_key);
+
+	gboolean    (*wifi_get_capabilities) (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
+	gboolean    (*wifi_get_bssid)        (NMPlatform *self, int ifindex, guint8 *bssid);
+	guint32     (*wifi_get_frequency)    (NMPlatform *self, int ifindex);
+	int         (*wifi_get_quality)      (NMPlatform *self, int ifindex);
+	guint32     (*wifi_get_rate)         (NMPlatform *self, int ifindex);
+	NM80211Mode (*wifi_get_mode)         (NMPlatform *self, int ifindex);
+	void        (*wifi_set_mode)         (NMPlatform *self, int ifindex, NM80211Mode mode);
+	void        (*wifi_set_powersave)    (NMPlatform *self, int ifindex, guint32 powersave);
+	guint32     (*wifi_find_frequency)   (NMPlatform *self, int ifindex, const guint32 *freqs);
+	void        (*wifi_indicate_addressing_running) (NMPlatform *self, int ifindex, gboolean running);
+	NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan) (NMPlatform *self, int ifindex);
+	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl);
+
+	guint32     (*mesh_get_channel)      (NMPlatform *self, int ifindex);
+	gboolean    (*mesh_set_channel)      (NMPlatform *self, int ifindex, guint32 channel);
+	gboolean    (*mesh_set_ssid)         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
+
+	guint16     (*wpan_get_pan_id)       (NMPlatform *self, int ifindex);
+	gboolean    (*wpan_set_pan_id)       (NMPlatform *self, int ifindex, guint16 pan_id);
+	guint16     (*wpan_get_short_addr)   (NMPlatform *self, int ifindex);
+	gboolean    (*wpan_set_short_addr)   (NMPlatform *self, int ifindex, guint16 short_addr);
+	gboolean    (*wpan_set_channel)      (NMPlatform *self, int ifindex, guint8 page, guint8 channel);
+
+	gboolean (*object_delete) (NMPlatform *self, const NMPObject *obj);
+
+	gboolean (*ip4_address_add) (NMPlatform *self,
 	                             int ifindex,
 	                             in_addr_t address,
 	                             guint8 plen,
@@ -1061,7 +1132,7 @@ typedef struct {
 	                             guint32 preferred_lft,
 	                             guint32 flags,
 	                             const char *label);
-	gboolean (*ip6_address_add) (NMPlatform *,
+	gboolean (*ip6_address_add) (NMPlatform *self,
 	                             int ifindex,
 	                             struct in6_addr address,
 	                             guint8 plen,
@@ -1069,10 +1140,10 @@ typedef struct {
 	                             guint32 lifetime,
 	                             guint32 preferred_lft,
 	                             guint32 flags);
-	gboolean (*ip4_address_delete) (NMPlatform *, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
-	gboolean (*ip6_address_delete) (NMPlatform *, int ifindex, struct in6_addr address, guint8 plen);
+	gboolean (*ip4_address_delete) (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
+	gboolean (*ip6_address_delete) (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
 
-	int (*ip_route_add) (NMPlatform *,
+	int (*ip_route_add) (NMPlatform *self,
 	                     NMPNlmFlags flags,
 	                     int addr_family,
 	                     const NMPlatformIPRoute *route);
@@ -1215,7 +1286,7 @@ gboolean nm_platform_get_use_udev (NMPlatform *self);
 gboolean nm_platform_get_log_with_ptr (NMPlatform *self);
 
 NMPNetns *nm_platform_netns_get (NMPlatform *self);
-gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
+gboolean nm_platform_netns_push (NMPlatform *self, NMPNetns **netns);
 
 const char *nm_link_type_to_string (NMLinkType link_type);
 
@@ -1241,16 +1312,24 @@ const char *nm_link_type_to_string (NMLinkType link_type);
 
 int nm_platform_sysctl_open_netdir (NMPlatform *self, int ifindex, char *out_ifname);
 gboolean nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const char *path, const char *value);
+void nm_platform_sysctl_set_async (NMPlatform *self,
+                                   const char *pathid,
+                                   int dirfd,
+                                   const char *path,
+                                   const char *const *values,
+                                   NMPlatformAsyncCallback callback,
+                                   gpointer data,
+                                   GCancellable *cancellable);
 char *nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, const char *path);
 gint32 nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, const char *path, gint32 fallback);
 gint64 nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int dirfd, const char *path, guint base, gint64 min, gint64 max, gint64 fallback);
 
-char *nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+char *nm_platform_sysctl_ip_conf_get (NMPlatform *self,
                                       int addr_family,
                                       const char *ifname,
                                       const char *property);
 
-gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *self,
                                                    int addr_family,
                                                    const char *ifname,
                                                    const char *property,
@@ -1259,13 +1338,13 @@ gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
                                                    gint64 max,
                                                    gint64 fallback);
 
-gboolean nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+gboolean nm_platform_sysctl_ip_conf_set (NMPlatform *self,
                                          int addr_family,
                                          const char *ifname,
                                          const char *property,
                                          const char *value);
 
-gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *self,
                                                int addr_family,
                                                const char *ifname,
                                                const char *property,
@@ -1274,6 +1353,10 @@ gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
 gboolean nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
                                                              const char *iface,
                                                              int value);
+int nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (NMPlatform *platform,
+                                                   const char *iface,
+                                                   gboolean consider_all,
+                                                   gboolean *out_due_to_all);
 
 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);
@@ -1298,7 +1381,7 @@ gboolean nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd
 
 struct _NMDedupMultiHeadEntry;
 struct _NMPLookup;
-const struct _NMDedupMultiHeadEntry *nm_platform_lookup (NMPlatform *platform,
+const struct _NMDedupMultiHeadEntry *nm_platform_lookup (NMPlatform *self,
                                                          const struct _NMPLookup *lookup);
 
 gboolean nm_platform_lookup_predicate_routes_main (const NMPObject *obj,
@@ -1306,7 +1389,7 @@ gboolean nm_platform_lookup_predicate_routes_main (const NMPObject *obj,
 gboolean nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *obj,
                                                                       gpointer user_data);
 
-GPtrArray *nm_platform_lookup_clone (NMPlatform *platform,
+GPtrArray *nm_platform_lookup_clone (NMPlatform *self,
                                      const struct _NMPLookup *lookup,
                                      NMPObjectPredicateFunc predicate,
                                      gpointer user_data);
@@ -1325,18 +1408,6 @@ 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);
@@ -1368,7 +1439,15 @@ gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex,
 int nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
 int nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
 gboolean nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name);
-gboolean nm_platform_link_set_sriov_params (NMPlatform *self, int ifindex, guint num_vfs, int autoprobe);
+
+void nm_platform_link_set_sriov_params_async (NMPlatform *self,
+                                              int ifindex,
+                                              guint num_vfs,
+                                              int autoprobe,
+                                              NMPlatformAsyncCallback callback,
+                                              gpointer callback_data,
+                                              GCancellable *cancellable);
+
 gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 gboolean nm_platform_link_set_bridge_vlans (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans);
 
@@ -1466,11 +1545,11 @@ guint32     nm_platform_mesh_get_channel      (NMPlatform *self, int ifindex);
 gboolean    nm_platform_mesh_set_channel      (NMPlatform *self, int ifindex, guint32 channel);
 gboolean    nm_platform_mesh_set_ssid         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
 
-guint16     nm_platform_wpan_get_pan_id       (NMPlatform *platform, int ifindex);
-gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *platform, int ifindex, guint16 pan_id);
-guint16     nm_platform_wpan_get_short_addr   (NMPlatform *platform, int ifindex);
-gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *platform, int ifindex, guint16 short_addr);
-gboolean    nm_platform_wpan_set_channel      (NMPlatform *platform, int ifindex, guint8 page, guint8 channel);
+guint16     nm_platform_wpan_get_pan_id       (NMPlatform *self, int ifindex);
+gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *self, int ifindex, guint16 pan_id);
+guint16     nm_platform_wpan_get_short_addr   (NMPlatform *self, int ifindex);
+gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *self, int ifindex, guint16 short_addr);
+gboolean    nm_platform_wpan_set_channel      (NMPlatform *self, int ifindex, guint8 page, guint8 channel);
 
 void                   nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);
 const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr);
@@ -1704,6 +1783,8 @@ void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, N
 void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
 void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
 
+#define NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN ((gsize) 162)
+
 const char *nm_platform_link_flags2str (unsigned flags, char *buf, gsize len);
 const char *nm_platform_link_inet6_addrgenmode2str (guint8 mode, char *buf, gsize len);
 const char *nm_platform_addr_flags2str (unsigned flags, char *buf, gsize len);
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index f34dc83d..b33d86fb 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h
index 55a4b95f..3c0ca74b 100644
--- a/src/platform/nmp-netns.h
+++ b/src/platform/nmp-netns.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 0d20100c..5cbf9428 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -398,7 +397,7 @@ _idx_obj_part (const DedupMultiIdxType *idx_type,
 		if (h) {
 			nm_hash_update_vals (h,
 			                     idx_type->cache_id_type,
-			                     NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex);
+			                     obj_a->obj_with_ifindex.ifindex);
 		}
 		return 1;
 
@@ -787,7 +786,7 @@ _nmp_object_new_from_class (const NMPClass *klass)
 }
 
 NMPObject *
-nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plobj)
+nmp_object_new (NMPObjectType obj_type, gconstpointer plobj)
 {
 	const NMPClass *klass = nmp_class_from_type (obj_type);
 	NMPObject *obj;
@@ -2147,9 +2146,12 @@ nmp_lookup_init_object_by_addr_family (NMPLookup *lookup,
 	NMPObject *o;
 
 	nm_assert (lookup);
-	nm_assert_addr_family (addr_family);
 	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_ROUTING_RULE));
 
+	if (addr_family == AF_UNSPEC)
+		return nmp_lookup_init_obj_type (lookup, obj_type);
+
+	nm_assert_addr_family (addr_family);
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
 	NMP_OBJECT_CAST_ROUTING_RULE (o)->addr_family = addr_family;
 	lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY;
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 3ce9db36..cea5f958 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -535,101 +534,27 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMPObjectType obj_type)
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->obj_with_ifindex : NULL; \
 	})
 
-#define NMP_OBJECT_CAST_LINK(obj) \
+#define _NMP_OBJECT_CAST(obj, field, ...) \
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_LINK); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->link : NULL; \
+		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), __VA_ARGS__)); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->field : NULL; \
 	})
 
-#define NMP_OBJECT_CAST_IP_ADDRESS(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip_address : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IPX_ADDRESS(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ipx_address : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IP4_ADDRESS(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip4_address : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IP6_ADDRESS(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP6_ADDRESS); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_address : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IPX_ROUTE(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ipx_route : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IP_ROUTE(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip_route : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IP4_ROUTE(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP4_ROUTE); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip4_route : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_IP6_ROUTE(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP6_ROUTE); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_route : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_ROUTING_RULE(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_ROUTING_RULE); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->routing_rule : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_QDISC(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_QDISC); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->qdisc : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_TFILTER(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_TFILTER); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->tfilter : NULL; \
-	})
+#define NMP_OBJECT_CAST_LINK(obj)          _NMP_OBJECT_CAST (obj, link,          NMP_OBJECT_TYPE_LINK)
+#define NMP_OBJECT_CAST_IP_ADDRESS(obj)    _NMP_OBJECT_CAST (obj, ip_address,    NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)
+#define NMP_OBJECT_CAST_IPX_ADDRESS(obj)   _NMP_OBJECT_CAST (obj, ipx_address,   NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)
+#define NMP_OBJECT_CAST_IP4_ADDRESS(obj)   _NMP_OBJECT_CAST (obj, ip4_address,   NMP_OBJECT_TYPE_IP4_ADDRESS)
+#define NMP_OBJECT_CAST_IP6_ADDRESS(obj)   _NMP_OBJECT_CAST (obj, ip6_address,   NMP_OBJECT_TYPE_IP6_ADDRESS)
+#define NMP_OBJECT_CAST_IP_ROUTE(obj)      _NMP_OBJECT_CAST (obj, ip_route,      NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
+#define NMP_OBJECT_CAST_IPX_ROUTE(obj)     _NMP_OBJECT_CAST (obj, ipx_route,     NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
+#define NMP_OBJECT_CAST_IP4_ROUTE(obj)     _NMP_OBJECT_CAST (obj, ip4_route,     NMP_OBJECT_TYPE_IP4_ROUTE)
+#define NMP_OBJECT_CAST_IP6_ROUTE(obj)     _NMP_OBJECT_CAST (obj, ip6_route,     NMP_OBJECT_TYPE_IP6_ROUTE)
+#define NMP_OBJECT_CAST_ROUTING_RULE(obj)  _NMP_OBJECT_CAST (obj, routing_rule,  NMP_OBJECT_TYPE_ROUTING_RULE)
+#define NMP_OBJECT_CAST_QDISC(obj)         _NMP_OBJECT_CAST (obj, qdisc,         NMP_OBJECT_TYPE_QDISC)
+#define NMP_OBJECT_CAST_TFILTER(obj)       _NMP_OBJECT_CAST (obj, tfilter,       NMP_OBJECT_TYPE_TFILTER)
+#define NMP_OBJECT_CAST_LNK_WIREGUARD(obj) _NMP_OBJECT_CAST (obj, lnk_wireguard, NMP_OBJECT_TYPE_LNK_WIREGUARD)
 
 static inline const NMPObject *
 nmp_object_ref (const NMPObject *obj)
@@ -674,7 +599,7 @@ nmp_object_unref (const NMPObject *obj)
 		_changed; \
 	})
 
-NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob);
+NMPObject *nmp_object_new (NMPObjectType obj_type, gconstpointer plobj);
 NMPObject *nmp_object_new_link (int ifindex);
 
 const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj);
@@ -1059,4 +984,15 @@ nm_platform_lookup_ip6_route_by_weak_id (NMPlatform *platform,
 	return nm_platform_lookup (platform, &lookup);
 }
 
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_object_by_addr_family (NMPlatform *platform,
+                                          NMPObjectType obj_type,
+                                          int addr_family)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_object_by_addr_family (&lookup, obj_type, addr_family);
+	return nm_platform_lookup (platform, &lookup);
+}
+
 #endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/nmp-rules-manager.c b/src/platform/nmp-rules-manager.c
index 970afcde..f982f04c 100644
--- a/src/platform/nmp-rules-manager.c
+++ b/src/platform/nmp-rules-manager.c
@@ -81,7 +81,7 @@ typedef struct {
 	 * But it will not remove or add them otherwise.
 	 *
 	 * Otherwise, the track_priority_val goes together with track_priority_present.
-	 * In case of one rule being tracked multile times (with different priorities),
+	 * In case of one rule being tracked multiple times (with different priorities),
 	 * the one with higher priority wins. See _rules_obj_get_best_data().
 	 * Then, the winning present state either enforces that the rule is present
 	 * or absent.
@@ -99,6 +99,17 @@ typedef enum {
 	CONFIG_STATE_NONE          = 0,
 	CONFIG_STATE_ADDED_BY_US   = 1,
 	CONFIG_STATE_REMOVED_BY_US = 2,
+
+	/* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE).
+	 *
+	 * Maybe we would only need to track whether we touched the rule at all. But we
+	 * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US)
+	 * or did we remove it (CONFIG_STATE_REMOVED_BY_US)?
+	 * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively
+	 * add/remove it, but whenever we are about to undo the add/remove, we need to do it.
+	 * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally
+	 * force the state next time when necessary. */
+	CONFIG_STATE_OWNED_BY_US   = 3,
 } ConfigState;
 
 typedef struct {
@@ -111,8 +122,10 @@ typedef struct {
 	 * This makes NMPRulesManager stateful (beyond the configuration that indicates
 	 * which rules are tracked).
 	 * After a restart, NetworkManager would no longer remember which rules were added
-	 * by us. That would need to be fixed by persisting the state and reloading it after
-	 * restart. */
+	 * by us.
+	 *
+	 * That is partially fixed by NetworkManager taking over the rules that it
+	 * actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */
 	ConfigState config_state;
 } RulesObjData;
 
@@ -121,6 +134,15 @@ typedef struct {
 	CList user_tag_lst_head;
 } RulesUserTagData;
 
+/*****************************************************************************/
+
+static void _rules_data_untrack (NMPRulesManager *self,
+                                 RulesData *rules_data,
+                                 gboolean remove_user_tag_data,
+                                 gboolean make_owned_by_us);
+
+/*****************************************************************************/
+
 static void
 _rules_data_assert (const RulesData *rules_data, gboolean linked)
 {
@@ -278,11 +300,31 @@ _rules_data_lookup (GHashTable *by_data,
 	return g_hash_table_lookup (by_data, &rules_data_needle);
 }
 
+/**
+ * nmp_rules_manager_track:
+ * @self: the #NMPRulesManager instance
+ * @routing_rule: the #NMPlatformRoutingRule to track or untrack
+ * @track_priority: the priority for tracking the rule. Note that
+ *   negative values indicate a forced absence of the rule. Priorities
+ *   are compared with their absolute values (with higher absolute
+ *   value being more important). For example, if you track the same
+ *   rule twice, once with priority -5 and +10, then the rule is
+ *   present (because the positive number is more important).
+ *   The special value 0 indicates weakly-tracked rules.
+ * @user_tag: the tag associated with tracking this rule. The same tag
+ *   must be used to untrack the rule later.
+ * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag
+ *   for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(),
+ *   because it enforces ownership of the now tracked rule. On the other hand,
+ *   a plain nmp_rules_manager_untrack() merely forgets about the tracking.
+ *   The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG.
+ */
 void
 nmp_rules_manager_track (NMPRulesManager *self,
                          const NMPlatformRoutingRule *routing_rule,
                          gint32 track_priority,
-                         gconstpointer user_tag)
+                         gconstpointer user_tag,
+                         gconstpointer user_tag_untrack)
 {
 	NMPObject obj_stack;
 	const NMPObject *p_obj_stack;
@@ -359,6 +401,17 @@ nmp_rules_manager_track (NMPRulesManager *self,
 		}
 	}
 
+	if (user_tag_untrack) {
+		if (user_tag != user_tag_untrack) {
+			RulesData *rules_data_untrack;
+
+			rules_data_untrack = _rules_data_lookup (self->by_data, p_obj_stack, user_tag_untrack);
+			if (rules_data_untrack)
+				_rules_data_untrack (self, rules_data_untrack, FALSE, TRUE);
+		} else
+			nm_assert_not_reached ();
+	}
+
 	_rules_data_assert (rules_data, TRUE);
 
 	if (changed) {
@@ -377,7 +430,8 @@ nmp_rules_manager_track (NMPRulesManager *self,
 static void
 _rules_data_untrack (NMPRulesManager *self,
                      RulesData *rules_data,
-                     gboolean remove_user_tag_data)
+                     gboolean remove_user_tag_data,
+                     gboolean make_owned_by_us)
 {
 	RulesObjData *obj_data;
 
@@ -401,15 +455,22 @@ _rules_data_untrack (NMPRulesManager *self,
 #endif
 
 	nm_assert (!c_list_is_empty (&rules_data->user_tag_lst));
-	if (   remove_user_tag_data
-	    && c_list_length_is (&rules_data->user_tag_lst, 1))
-		g_hash_table_remove (self->by_user_tag, &rules_data->user_tag);
 
 	obj_data = g_hash_table_lookup (self->by_obj, &rules_data->obj);
 	nm_assert (obj_data);
 	nm_assert (c_list_contains (&obj_data->obj_lst_head, &rules_data->obj_lst));
 	nm_assert (obj_data == g_hash_table_lookup (self->by_obj, &rules_data->obj));
 
+	if (make_owned_by_us) {
+		if (obj_data->config_state == CONFIG_STATE_NONE) {
+			/* we need to mark this entry that it requires a touch on the next
+			 * sync. */
+			obj_data->config_state = CONFIG_STATE_OWNED_BY_US;
+		}
+	} else if (   remove_user_tag_data
+	           && c_list_length_is (&rules_data->user_tag_lst, 1))
+		g_hash_table_remove (self->by_user_tag, &rules_data->user_tag);
+
 	/* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry
 	 * around for the next sync -- so that we can undo what we did earlier. */
 	if (   obj_data->config_state == CONFIG_STATE_NONE
@@ -440,7 +501,7 @@ nmp_rules_manager_untrack (NMPRulesManager *self,
 
 	rules_data = _rules_data_lookup (self->by_data, p_obj_stack, user_tag);
 	if (rules_data)
-		_rules_data_untrack (self, rules_data, TRUE);
+		_rules_data_untrack (self, rules_data, TRUE, FALSE);
 }
 
 void
@@ -486,7 +547,7 @@ nmp_rules_manager_untrack_all (NMPRulesManager *self,
 	c_list_for_each_entry_safe (rules_data, rules_data_safe, &user_tag_data->user_tag_lst_head, user_tag_lst) {
 		if (   all
 		    || rules_data->dirty)
-			_rules_data_untrack (self, rules_data, FALSE);
+			_rules_data_untrack (self, rules_data, FALSE, FALSE);
 	}
 	if (c_list_is_empty (&user_tag_data->user_tag_lst_head))
 		g_hash_table_remove (self->by_user_tag, user_tag_data);
@@ -525,11 +586,17 @@ nmp_rules_manager_sync (NMPRulesManager *self,
 
 			rd_best = _rules_obj_get_best_data (obj_data);
 			if (rd_best) {
-				if (rd_best->track_priority_present)
+				if (rd_best->track_priority_present) {
+					if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
+						obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
 					continue;
+				}
 				if (rd_best->track_priority_val == 0) {
-					if (obj_data->config_state != CONFIG_STATE_ADDED_BY_US)
+					if (!NM_IN_SET (obj_data->config_state, CONFIG_STATE_ADDED_BY_US,
+					                                        CONFIG_STATE_OWNED_BY_US)) {
+						obj_data->config_state = CONFIG_STATE_NONE;
 						continue;
+					}
 					obj_data->config_state = CONFIG_STATE_NONE;
 				}
 			}
@@ -563,11 +630,17 @@ nmp_rules_manager_sync (NMPRulesManager *self,
 			continue;
 		}
 
-		if (!rd_best->track_priority_present)
+		if (!rd_best->track_priority_present) {
+			if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
+				obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
 			continue;
+		}
 		if (rd_best->track_priority_val == 0) {
-			if (obj_data->config_state != CONFIG_STATE_REMOVED_BY_US)
+			if (!NM_IN_SET (obj_data->config_state, CONFIG_STATE_REMOVED_BY_US,
+			                                        CONFIG_STATE_OWNED_BY_US)) {
+				obj_data->config_state = CONFIG_STATE_NONE;
 				continue;
+			}
 			obj_data->config_state = CONFIG_STATE_NONE;
 		}
 
@@ -610,7 +683,7 @@ nmp_rules_manager_track_from_platform (NMPRulesManager *self,
 		    && rr->addr_family != addr_family)
 			continue;
 
-		nmp_rules_manager_track (self, rr, tracking_priority, user_tag);
+		nmp_rules_manager_track (self, rr, tracking_priority, user_tag, NULL);
 	}
 }
 
@@ -638,7 +711,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 		nmp_rules_manager_track (self,
 		                         &((NMPlatformRoutingRule) {
 		                             .addr_family = AF_INET,
@@ -648,7 +722,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 		nmp_rules_manager_track (self,
 		                         &((NMPlatformRoutingRule) {
 		                             .addr_family = AF_INET,
@@ -658,7 +733,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 	}
 	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
 		nmp_rules_manager_track (self,
@@ -670,7 +746,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 		nmp_rules_manager_track (self,
 		                         &((NMPlatformRoutingRule) {
 		                             .addr_family = AF_INET6,
@@ -680,7 +757,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 	}
 }
 
diff --git a/src/platform/nmp-rules-manager.h b/src/platform/nmp-rules-manager.h
index 310c7971..645df5c2 100644
--- a/src/platform/nmp-rules-manager.h
+++ b/src/platform/nmp-rules-manager.h
@@ -22,6 +22,8 @@
 
 /*****************************************************************************/
 
+#define NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_rules_manager_new)
+
 typedef struct _NMPRulesManager NMPRulesManager;
 
 NMPRulesManager *nmp_rules_manager_new (NMPlatform *platform);
@@ -35,7 +37,8 @@ NM_AUTO_DEFINE_FCN0 (NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_mana
 void nmp_rules_manager_track (NMPRulesManager *self,
                               const NMPlatformRoutingRule *routing_rule,
                               gint32 track_priority,
-                              gconstpointer user_tag);
+                              gconstpointer user_tag,
+                              gconstpointer user_tag_untrack);
 
 void nmp_rules_manager_track_default (NMPRulesManager *self,
                                       int addr_family,
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index 8086a46c..aaee8964 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -1,23 +1,22 @@
 test_units = [
-  ['test-link-fake',     'test-link.c',       test_nm_dep_fake,  default_test_timeout],
-  ['test-link-linux',    'test-link.c',       test_nm_dep_linux, 900],
-  ['test-address-fake',  'test-address.c',    test_nm_dep_fake,  default_test_timeout],
-  ['test-address-linux', 'test-address.c',    test_nm_dep_linux, default_test_timeout],
-  ['test-general',       'test-general.c',    test_nm_dep,       default_test_timeout],
-  ['test-nmp-object',    'test-nmp-object.c', test_nm_dep,       default_test_timeout],
-  ['test-route-fake',    'test-route.c',      test_nm_dep_fake,  default_test_timeout],
-  ['test-route-linux',   'test-route.c',      test_nm_dep_linux, default_test_timeout],
-  ['test-cleanup-fake',  'test-cleanup.c',    test_nm_dep_fake,  default_test_timeout],
-  ['test-cleanup-linux', 'test-cleanup.c',    test_nm_dep_linux, default_test_timeout],
+  [ 'test-address-fake',     'test-address.c',          test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-address-linux',    'test-address.c',          test_nm_dep_linux, default_test_timeout ],
+  [ 'test-cleanup-fake',     'test-cleanup.c',          test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-cleanup-linux',    'test-cleanup.c',          test_nm_dep_linux, default_test_timeout ],
+  [ 'test-link-fake',        'test-link.c',             test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-link-linux',       'test-link.c',             test_nm_dep_linux, 900                  ],
+  [ 'test-nmp-object',       'test-nmp-object.c',       test_nm_dep,       default_test_timeout ],
+  [ 'test-platform-general', 'test-platform-general.c', test_nm_dep,       default_test_timeout ],
+  [ 'test-route-fake',       'test-route.c',            test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-route-linux',      'test-route.c',            test_nm_dep_linux, default_test_timeout ],
 ]
 
 foreach test_unit: test_units
   exe = executable(
-    'platform-' + test_unit[0],
+    test_unit[0],
     test_unit[1],
     dependencies: test_unit[2],
   )
-
   test(
     'platform/' + test_unit[0],
     test_script,
@@ -26,10 +25,8 @@ foreach test_unit: test_units
   )
 endforeach
 
-test = 'monitor'
-
 executable(
-  test,
-  test + '.c',
+  'monitor',
+  'monitor.c',
   dependencies: test_nm_dep,
 )
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index fff5967b..6a5e0e16 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * 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
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index d84a806d..3a3e9009 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * 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
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 6c73a63e..70d64c15 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * 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
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 7ebda9ff..cd0bef45 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * 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
@@ -679,7 +678,7 @@ nmtstp_run_command_check_external_global (void)
 {
 	if (!nmtstp_is_root_test ())
 		return FALSE;
-	switch (nmtst_get_rand_int () % 3) {
+	switch (nmtst_get_rand_uint32 () % 3) {
 	case 0:
 		return -1;
 	case 1:
@@ -699,7 +698,7 @@ nmtstp_run_command_check_external (int external_command)
 	}
 	if (!nmtstp_is_root_test ())
 		return FALSE;
-	return (nmtst_get_rand_int () % 2) == 0;
+	return (nmtst_get_rand_uint32 () % 2) == 0;
 }
 
 /*****************************************************************************/
@@ -843,7 +842,7 @@ _ip_address_add (NMPlatform *platform,
 			g_assert (flags == 0);
 
 			if (   peer_address->addr4 != address->addr4
-			    || nmtst_get_rand_int () % 2) {
+			    || nmtst_get_rand_uint32 () % 2) {
 				/* If the peer is the same as the local address, we can omit it. The result should be identical */
 				nm_sprintf_buf (s_peer, " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
 			} else
@@ -1994,7 +1993,7 @@ nmtstp_netns_select_random (NMPlatform **platforms, gsize n_platforms, NMPNetns
 	for (i = 0; i < n_platforms; i++)
 		g_assert (NM_IS_PLATFORM (platforms[i]));
 
-	i = nmtst_get_rand_int () % (n_platforms + 1);
+	i = nmtst_get_rand_uint32 () % (n_platforms + 1);
 	if (i == 0)
 		return;
 	g_assert (nm_platform_netns_push (platforms[i - 1], netns));
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index a0650cc1..5926dac1 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * 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
@@ -241,7 +240,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	/* Set master up */
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, master, NULL));
 	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, master));
-	accept_signals (master_changed, 1, 2);
+	accept_signals (master_changed, 1, 3);
 
 	g_signal_handlers_disconnect_by_func (NM_PLATFORM_GET, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg1);
 	g_signal_handlers_disconnect_by_func (NM_PLATFORM_GET, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg2);
@@ -287,7 +286,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, master));
 	accept_signals (link_changed, 1, 3);
 	/* NM running, can cause additional change of addrgenmode */
-	accept_signals (master_changed, 0, 2);
+	accept_signals (master_changed, 0, 3);
 
 	/* Enslave again
 	 *
@@ -329,7 +328,7 @@ test_slave (int master, int type, SignalData *master_changed)
 		ensure_no_signal (link_changed);
 		accept_signal (link_removed);
 	}
-	accept_signals (master_changed, 0, 2);
+	accept_signals (master_changed, 0, 3);
 
 	ensure_no_signal (master_changed);
 
@@ -515,8 +514,8 @@ test_bridge_addr (void)
 	link = *plink;
 	g_assert_cmpstr (link.name, ==, DEVICE_NAME);
 
-	g_assert_cmpint (link.addr.len, ==, sizeof (addr));
-	g_assert (!memcmp (link.addr.data, addr, sizeof (addr)));
+	g_assert_cmpint (link.l_address.len, ==, sizeof (addr));
+	g_assert (!memcmp (link.l_address.data, addr, sizeof (addr)));
 
 	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 	g_assert (plink);
@@ -538,8 +537,8 @@ test_bridge_addr (void)
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 	}
 
-	g_assert_cmpint (plink->addr.len, ==, sizeof (addr));
-	g_assert (!memcmp (plink->addr.data, addr, sizeof (addr)));
+	g_assert_cmpint (plink->l_address.len, ==, sizeof (addr));
+	g_assert (!memcmp (plink->l_address.data, addr, sizeof (addr)));
 
 	nmtstp_link_delete (NULL, -1, link.ifindex, link.name, TRUE);
 }
@@ -2100,7 +2099,7 @@ test_create_many_links_do (guint n_devices)
 	char name[64];
 	const NMPlatformLink *pllink;
 	gs_unref_array GArray *ifindexes = g_array_sized_new (FALSE, FALSE, sizeof (int), n_devices);
-	const int EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
+	const int EX = ((int) (nmtst_get_rand_uint32 () % 4)) - 1;
 
 	g_assert (EX >= -1 && EX <= 2);
 
@@ -2459,17 +2458,17 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 		NMPlatform *p = (k == 0 ? platform_1 : platform_2);
 		const char *id = (k == 0 ? "a" : "b");
 
-		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+		for (i = 0, j = nmtst_get_rand_uint32 () % 5; i < j; i++)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-a-%s-%02d", id, i));
 
 		_ADD_DUMMY (p, "dummy1_");
 
-		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+		for (i = 0, j = nmtst_get_rand_uint32 () % 5; i < j; i++)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-b-%s-%02d", id, i));
 
 		_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "dummy2%s", id));
 
-		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+		for (i = 0, j = nmtst_get_rand_uint32 () % 5; i < j; i++)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i));
 	}
 
@@ -2491,17 +2490,17 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 		NMPlatform *pl;
 		const char *path;
 
-		j = nmtst_get_rand_int () % 2;
+		j = nmtst_get_rand_uint32 () % 2;
 
-		if (nmtst_get_rand_int () % 2) {
+		if (nmtst_get_rand_uint32 () % 2) {
 			pl = platform_1;
-			if (nmtst_get_rand_int () % 2)
+			if (nmtst_get_rand_uint32 () % 2)
 				path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
 			else
 				path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6";
 		} else {
 			pl = platform_2;
-			if (nmtst_get_rand_int () % 2)
+			if (nmtst_get_rand_uint32 () % 2)
 				path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
 			else
 				path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6";
@@ -2654,7 +2653,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 	pl[2].platform = platform_2 = _test_netns_create_platform ();
 
 	pl_base = &pl[0];
-	i = nmtst_get_rand_int () % (G_N_ELEMENTS (pl) + 1);
+	i = nmtst_get_rand_uint32 () % (G_N_ELEMENTS (pl) + 1);
 	if (i < G_N_ELEMENTS (pl)) {
 		pl_base = &pl[i];
 		g_assert (nm_platform_netns_push (pl[i].platform, &netns_pop));
@@ -2674,7 +2673,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 		g_ptr_array_add (device_names, tmp);
 		pl[i].sysctl_path = tmp;
 
-		pl[i].sysctl_value = nmtst_get_rand_int () % 2 ? "1" : "0";
+		pl[i].sysctl_value = nmtst_get_rand_uint32 () % 2 ? "1" : "0";
 
 		_ADD_DUMMY (pl[i].platform, pl[i].device_name);
 
@@ -2689,16 +2688,16 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 		pl[i].ns_mnt = tmp;
 	}
 
-	nstack = nmtst_get_rand_int () % (G_N_ELEMENTS (stack) + 1);
+	nstack = nmtst_get_rand_uint32 () % (G_N_ELEMENTS (stack) + 1);
 	for (i = 0; i < nstack; i++) {
-		stack[i].pl = &pl[nmtst_get_rand_int () % G_N_ELEMENTS (pl)];
-		stack[i].ns_types = ns_types_list[nmtst_get_rand_int () % G_N_ELEMENTS (ns_types_list)];
+		stack[i].pl = &pl[nmtst_get_rand_uint32 () % G_N_ELEMENTS (pl)];
+		stack[i].ns_types = ns_types_list[nmtst_get_rand_uint32 () % G_N_ELEMENTS (ns_types_list)];
 
 		nmp_netns_push_type (nm_platform_netns_get (stack[i].pl->platform), stack[i].ns_types);
 	}
 
 	/* pop some again. */
-	for (i = nmtst_get_rand_int () % (nstack + 1); i > 0; i--) {
+	for (i = nmtst_get_rand_uint32 () % (nstack + 1); i > 0; i--) {
 		g_assert (nstack > 0);
 		nstack--;
 		nmp_netns_pop (nm_platform_netns_get (stack[nstack].pl->platform));
@@ -2765,9 +2764,9 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 static void
 test_netns_bind_to_path (gpointer fixture, gconstpointer test_data)
 {
-#define P_VAR_RUN                "/var/run"
-#define P_VAR_RUN_NETNS          "/var/run/netns"
-#define P_VAR_RUN_NETNS_BINDNAME "/var/run/netns/"P_NETNS_BINDNAME
+#define P_VAR_RUN                "/run"
+#define P_VAR_RUN_NETNS          "/run/netns"
+#define P_VAR_RUN_NETNS_BINDNAME "/run/netns/"P_NETNS_BINDNAME
 #define P_NETNS_BINDNAME         "nmtst-iproute2-netns"
 	gs_unref_object NMPlatform *platform_0 = NULL;
 	gs_unref_object NMPlatform *platform_1 = NULL;
@@ -2789,7 +2788,7 @@ test_netns_bind_to_path (gpointer fixture, gconstpointer test_data)
 	g_assert_cmpint (mount ("tmpfs", P_VAR_RUN, "tmpfs", MS_NOATIME | MS_NODEV | MS_NOSUID, "mode=0755,size=32K"), ==, 0);
 	g_assert_cmpint (mkdir (P_VAR_RUN_NETNS, 755), ==, 0);
 
-	i = (nmtst_get_rand_int () % 2) + 1;
+	i = (nmtst_get_rand_uint32 () % 2) + 1;
 	netns = nm_platform_netns_get (platforms[i]);
 
 	_ADD_DUMMY (platforms[i], "dummy2b");
@@ -2832,9 +2831,9 @@ test_sysctl_rename (void)
 	ifindex[0] = nmtstp_link_dummy_add (PL, -1, IFNAME[0])->ifindex;
 	ifindex[1] = nmtstp_link_dummy_add (PL, -1, IFNAME[1])->ifindex;
 
-	s = (nmtst_get_rand_int () % 2) ? NULL : ifname_buf;
+	s = (nmtst_get_rand_uint32 () % 2) ? NULL : ifname_buf;
 
-	if (nmtst_get_rand_int () % 2) {
+	if (nmtst_get_rand_uint32 () % 2) {
 		/* bring the platform cache out of sync */
 		nmtstp_run_command_check ("ip link set %s name %s", IFNAME[0], IFNAME[2]);
 		nm_platform_process_events (PL);
@@ -2847,7 +2846,7 @@ test_sysctl_rename (void)
 	}
 
 	/* open dirfd for IFNAME[0] */
-	i = nmtst_get_rand_int () % (2 + G_N_ELEMENTS (IFNAME));
+	i = nmtst_get_rand_uint32 () % (2 + G_N_ELEMENTS (IFNAME));
 	if (i == 0) {
 		dirfd = nm_platform_sysctl_open_netdir (PL,
 		                                        ifindex[0],
@@ -2866,7 +2865,7 @@ test_sysctl_rename (void)
 		g_assert_cmpstr (s, ==, IFNAME[0]);
 
 	/* possibly rename the interfaces. */
-	switch (nmtst_get_rand_int () % 4) {
+	switch (nmtst_get_rand_uint32 () % 4) {
 	case 0:
 		break;
 	case 1:
@@ -2879,11 +2878,11 @@ test_sysctl_rename (void)
 	}
 
 	/* possibly, resync platform cache (should make no difference). */
-	if (nmtst_get_rand_int () % 2)
+	if (nmtst_get_rand_uint32 () % 2)
 		nm_platform_process_events (PL);
 
 	/* check that we still read the same file. */
-	switch (nmtst_get_rand_int () % 2) {
+	switch (nmtst_get_rand_uint32 () % 2) {
 	case 0: {
 		gs_free char *c = NULL;
 
@@ -2930,7 +2929,7 @@ test_sysctl_netns_switch (void)
 	platforms[0] = platform_0 = nm_linux_platform_new (TRUE, TRUE);
 	platforms[1] = platform_1 = _test_netns_create_platform ();
 	platforms[2] = platform_2 = _test_netns_create_platform ();
-	PL = platforms[nmtst_get_rand_int () % 3];
+	PL = platforms[nmtst_get_rand_uint32 () % 3];
 
 	nmtstp_netns_select_random (platforms, G_N_ELEMENTS (platforms), &netns_pop_1);
 
@@ -2938,7 +2937,7 @@ test_sysctl_netns_switch (void)
 
 	nmtstp_netns_select_random (platforms, G_N_ELEMENTS (platforms), &netns_pop_2);
 
-	s = (nmtst_get_rand_int () % 2) ? NULL : ifname_buf;
+	s = (nmtst_get_rand_uint32 () % 2) ? NULL : ifname_buf;
 	dirfd = nm_platform_sysctl_open_netdir (PL,
 	                                        ifindex,
 	                                        s);
@@ -3015,6 +3014,92 @@ test_sysctl_netns_switch (void)
 	nmtstp_link_delete (PL, FALSE, ifindex, NULL, TRUE);
 }
 
+static void
+sysctl_set_async_cb_assert_success (GError *error, gpointer data)
+{
+	g_assert_no_error (error);
+	g_main_loop_quit (data);
+}
+
+static void
+sysctl_set_async_cb_assert_failure (GError *error, gpointer data)
+{
+	g_assert (error);
+	g_main_loop_quit (data);
+}
+
+static void
+test_sysctl_set_async (void)
+{
+	NMPlatform *const PL = NM_PLATFORM_GET;
+	const char *const IFNAME = "nm-dummy-0";
+	const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/rp_filter";
+	gs_free GMainLoop *loop = NULL;
+	gs_unref_object GCancellable *cancellable = NULL;
+	int ifindex;
+
+	ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex;
+	loop = g_main_loop_new (NULL, FALSE);
+	cancellable = g_cancellable_new ();
+
+	nm_platform_sysctl_set_async (PL,
+	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
+	                              (const char *[]) { "2", NULL},
+	                              sysctl_set_async_cb_assert_success,
+	                              loop,
+	                              cancellable);
+
+	if (!nmtst_main_loop_run (loop, 1000))
+		g_assert_not_reached ();
+
+	g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1),
+	                 ==,
+	                 2);
+
+	nm_platform_sysctl_set_async (PL,
+	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
+	                              (const char *[]) { "2", "0", "1", "0", "1", NULL},
+	                              sysctl_set_async_cb_assert_success,
+	                              loop,
+	                              cancellable);
+
+	if (!nmtst_main_loop_run (loop, 2000))
+		g_assert_not_reached ();
+
+	g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1),
+	                 ==,
+	                 1);
+
+	nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE);
+}
+
+static void
+test_sysctl_set_async_fail (void)
+{
+	NMPlatform *const PL = NM_PLATFORM_GET;
+	const char *const IFNAME = "nm-dummy-0";
+	const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/does-not-exist";
+	gs_free GMainLoop *loop = NULL;
+	gs_unref_object GCancellable *cancellable = NULL;
+	int ifindex;
+
+	ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex;
+	loop = g_main_loop_new (NULL, FALSE);
+	cancellable = g_cancellable_new ();
+
+	nm_platform_sysctl_set_async (PL,
+	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
+	                              (const char *[]) { "2", NULL},
+	                              sysctl_set_async_cb_assert_failure,
+	                              loop,
+	                              cancellable);
+
+	if (!nmtst_main_loop_run (loop, 1000))
+		g_assert_not_reached ();
+
+	nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE);
+}
+
 /*****************************************************************************/
 
 static gpointer
@@ -3238,6 +3323,8 @@ _nmtstp_setup_tests (void)
 
 		g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
 		g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
+		g_test_add_func ("/general/sysctl/set-async", test_sysctl_set_async);
+		g_test_add_func ("/general/sysctl/set-async-fail", test_sysctl_set_async_fail);
 
 		g_test_add_func ("/link/ethtool/features/get", test_ethtool_features_get);
 	}
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index e2019e96..08bde437 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -267,7 +266,7 @@ test_cache_link (void)
 	struct udev_device *udev_device_3 = g_list_nth_data (global.udev_devices, 0);
 	NMPCacheOpsType ops_type;
 	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL;
-	gboolean use_udev = nmtst_get_rand_int () % 2;
+	gboolean use_udev = nmtst_get_rand_uint32 () % 2;
 
 	multi_idx = nm_dedup_multi_index_new ();
 
@@ -503,7 +502,7 @@ test_cache_qdisc (void)
 	nm_auto_nmpobj NMPObject *obj2 = nmp_object_new (NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_2);
 
 	multi_idx = nm_dedup_multi_index_new ();
-	cache = nmp_cache_new (multi_idx, nmtst_get_rand_int () % 2);
+	cache = nmp_cache_new (multi_idx, nmtst_get_rand_uint32 () % 2);
 
 	g_assert (nmp_cache_lookup_obj (cache, obj1a) == NULL);
 
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-platform-general.c
index eebc15d8..2b723810 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-platform-general.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -52,6 +51,31 @@ test_link_get_all (void)
 
 /*****************************************************************************/
 
+static void
+test_nm_platform_link_flags2str (void)
+{
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		char buf[NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 100];
+		const char *s;
+		const guint flags = ((i == 0) ? ~0u : nmtst_get_rand_uint ());
+		gsize l;
+
+		s = nm_platform_link_flags2str (flags, buf, sizeof (buf));
+		g_assert (s);
+
+		l = strlen (s);
+		if (l > NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN)
+			g_error ("nm_platform_link_flags2str(%x) produced a longer output than %zu chars: \"%s\"", flags, NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, s);
+		if (   flags == ~0u
+		    && l != NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN)
+			g_error ("nm_platform_link_flags2str(%x) is expected to produce %zu chars, but produced %zu: \"%s\"", flags, NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, l, s);
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -61,7 +85,7 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/general/init_linux_platform", test_init_linux_platform);
 	g_test_add_func ("/general/link_get_all", test_link_get_all);
+	g_test_add_func ("/general/nm_platform_link_flags2str", test_nm_platform_link_flags2str);
 
 	return g_test_run ();
 }
-
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index e6ffe1ae..44bfbc58 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * 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
@@ -445,7 +444,7 @@ test_ip4_route_get (void)
 	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
 	                                   AF_INET,
 	                                   &a,
-	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   nmtst_get_rand_uint32 () % 2 ? 0 : ifindex,
 	                                   &route);
 
 	g_assert (NMTST_NM_ERR_SUCCESS (result));
@@ -607,7 +606,7 @@ test_ip6_route_get (void)
 	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
 	                                   AF_INET6,
 	                                   a,
-	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   nmtst_get_rand_uint32 () % 2 ? 0 : ifindex,
 	                                   &route);
 
 	g_assert (NMTST_NM_ERR_SUCCESS (result));
@@ -815,9 +814,9 @@ test_ip (gconstpointer test_data)
 
 		if (   order_len == 0
 		    || (   order_len < G_N_ELEMENTS (order_idx)
-		        && nmtst_get_rand_int () % 2)) {
+		        && nmtst_get_rand_uint32 () % 2)) {
 again_find_idx:
-			idx = nmtst_get_rand_int () % G_N_ELEMENTS (iface_data);
+			idx = nmtst_get_rand_uint32 () % G_N_ELEMENTS (iface_data);
 			for (i = 0; i < order_len; i++) {
 				if (order_idx[i] == idx)
 					goto again_find_idx;
@@ -827,7 +826,7 @@ again_find_idx:
 			r->ifindex = iface_data[idx].ifindex;
 			g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r)));
 		} else {
-			i = nmtst_get_rand_int () % order_len;
+			i = nmtst_get_rand_uint32 () % order_len;
 			idx = order_idx[i];
 			for (i++; i < order_len; i++)
 				order_idx[i - 1] = order_idx[i];
@@ -1129,17 +1128,17 @@ _rule_create_random (NMPlatform *platform)
 
 	addr_size = nm_utils_addr_family_to_size (rr->addr_family);
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 50)
 		rr->priority = 10000 + ((~p) % 20u);
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 40)
 		nm_sprintf_buf (rr->iifname, "t-iif-%u", (~p) % 20);
 	else if ((p % 1000u) < 80)
 		nm_sprintf_buf (rr->iifname, "%s", DEVICE_NAME);
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 40)
 		nm_sprintf_buf (rr->oifname, "t-oif-%d", (~p) % 20);
 	else if ((p % 1000u) < 80)
@@ -1149,14 +1148,14 @@ _rule_create_random (NMPlatform *platform)
 		NMIPAddr *p_addr = i ? &rr->src     : &rr->dst;
 		guint8 *p_len    = i ? &rr->src_len : &rr->dst_len;
 
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		if ((p % 1000u) < 100) {
 			/* if we set src_len/dst_len to zero, the src/dst is actually ignored.
 			 *
 			 * For fuzzying, still set the address. It shall have no further effect.
 			 * */
 			*p_len = (~p) % (addr_size * 8 + 1);
-			p = nmtst_get_rand_int ();
+			p = nmtst_get_rand_uint32 ();
 			if ((p % 3u) == 0) {
 				if (rr->addr_family == AF_INET)
 					p_addr->addr4 = nmtst_inet4_from_string (nm_sprintf_buf (saddr, "192.192.5.%u", (~p) % 256u));
@@ -1167,12 +1166,12 @@ _rule_create_random (NMPlatform *platform)
 		}
 	}
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 50)
 		rr->tun_id = 10000 + ((~p) % 20);
 
 again_action:
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 500)
 		rr->action = FR_ACT_UNSPEC;
 	else if ((p % 1000u) < 750)
@@ -1180,22 +1179,22 @@ again_action:
 	else
 		rr->action = (~p) % 0x100u;
 
-	rr->priority = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->priority = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (   rr->action == FR_ACT_GOTO
 	    && rr->priority == G_MAXINT32)
 		goto again_action;
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 10000u) < 100)
 		rr->goto_target = rr->priority + 1;
 	else
-		rr->goto_target = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+		rr->goto_target = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 	if (   rr->action == FR_ACT_GOTO
 	    && rr->goto_target <= rr->priority)
 		goto again_action;
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 25) {
 		if (_rule_check_kernel_support (platform, FRA_L3MDEV)) {
 			rr->l3mdev = TRUE;
@@ -1205,7 +1204,7 @@ again_action:
 
 again_table:
 	if (!rr->l3mdev) {
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		if ((p % 1000u) < 700)
 			rr->table = RT_TABLE_UNSPEC;
 		else if ((p % 1000u) < 850)
@@ -1217,33 +1216,33 @@ again_table:
 			goto again_table;
 	}
 
-	rr->fwmark = _rr_rand_choose_u32 (nmtst_get_rand_int ());
-	rr->fwmask = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->fwmark = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
+	rr->fwmask = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
-	rr->flow = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->flow = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_PROTOCOL))
-		rr->protocol = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+		rr->protocol = _rr_rand_choose_u8 (nmtst_get_rand_uint32 ());
 
 #define IPTOS_TOS_MASK 0x1E
 
 again_tos:
-	rr->tos = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+	rr->tos = _rr_rand_choose_u8 (nmtst_get_rand_uint32 ());
 	if (   rr->addr_family == AF_INET
 	    && rr->tos & ~IPTOS_TOS_MASK)
 		goto again_tos;
 
 	if (_rule_check_kernel_support (platform, FRA_IP_PROTO))
-		rr->ip_proto = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+		rr->ip_proto = _rr_rand_choose_u8 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_PREFIXLEN))
-		rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+		rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_IFGROUP))
-		rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+		rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_UID_RANGE)) {
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		rr->uid_range_has = (p % 10000u) < 200;
 	}
 
@@ -1265,10 +1264,10 @@ again_uid_range:
 		if (!_rule_check_kernel_support (platform, attribute))
 			continue;
 
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		if ((p % 10000u) < 300) {
 			while (range->start == 0) {
-				p = p ^ nmtst_get_rand_int ();
+				p = p ^ nmtst_get_rand_uint32 ();
 				range->start = nmtst_rand_select (1u, 0xFFFEu, ((p      ) % 0xFFFEu) + 1);
 				range->end   = nmtst_rand_select (1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start);
 				if (range->end < range->start)
@@ -1277,7 +1276,7 @@ again_uid_range:
 		}
 	}
 
-	p = nmtst_get_rand_int () % 1000u;
+	p = nmtst_get_rand_uint32 () % 1000u;
 	if (p < 100)
 		rr->flags |= FIB_RULE_INVERT;
 
@@ -1345,8 +1344,7 @@ _rule_fuzzy_equal (const NMPObject *obj,
 			rr_co.tos = 0;
 		if (rr->ip_proto == 0)
 			rr_co.ip_proto = 0;
-		if (rr->suppress_prefixlen_inverse == 0)
-			rr_co.suppress_prefixlen_inverse = 0;
+		rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse;
 		if (rr->suppress_ifgroup_inverse == 0)
 			rr_co.suppress_ifgroup_inverse = 0;
 		if (!rr->uid_range_has)
@@ -1483,10 +1481,10 @@ test_rule (gconstpointer test_data)
 
 	if (TEST_IDX != 1) {
 		nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
-		g_ptr_array_set_size (objs, nmtst_get_rand_int () % (objs->len + 1));
+		g_ptr_array_set_size (objs, nmtst_get_rand_uint32 () % (objs->len + 1));
 	}
 
-	n = (TEST_IDX != 1) ? nmtst_get_rand_int () % 50u : 0u;
+	n = (TEST_IDX != 1) ? nmtst_get_rand_uint32 () % 50u : 0u;
 	for (i = 0; i < n; i++) {
 		nm_auto_nmpobj const NMPObject *o = NULL;
 		guint try = 0;
@@ -1536,16 +1534,18 @@ again:
 			nmp_rules_manager_track (rules_manager,
 			                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 			                         1,
-			                         USER_TAG_1);
+			                         USER_TAG_1,
+			                         NULL);
 			if (nmtst_get_rand_bool ()) {
 				/* this has no effect, because a negative priority (of same absolute value)
 				 * has lower priority than the positive priority above. */
 				nmp_rules_manager_track (rules_manager,
 				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 				                         -1,
-				                         USER_TAG_2);
+				                         USER_TAG_2,
+				                         NULL);
 			}
-			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+			if (nmtst_get_rand_uint32 () % objs_sync->len == 0) {
 				nmp_rules_manager_sync (rules_manager, FALSE);
 				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1);
 			}
@@ -1555,7 +1555,7 @@ again:
 		g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len);
 
 		for (i = 0; i < objs_sync->len; i++) {
-			switch (nmtst_get_rand_int () % 3) {
+			switch (nmtst_get_rand_uint32 () % 3) {
 			case 0:
 				nmp_rules_manager_untrack (rules_manager,
 				                           NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
@@ -1568,16 +1568,18 @@ again:
 				nmp_rules_manager_track (rules_manager,
 				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 				                         -1,
-				                         USER_TAG_1);
+				                         USER_TAG_1,
+				                         NULL);
 				break;
 			case 2:
 				nmp_rules_manager_track (rules_manager,
 				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 				                         -2,
-				                         USER_TAG_2);
+				                         USER_TAG_2,
+				                         NULL);
 				break;
 			}
-			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+			if (nmtst_get_rand_uint32 () % objs_sync->len == 0) {
 				nmp_rules_manager_sync (rules_manager, FALSE);
 				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len - i - 1);
 			}
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index 4f7ede97..164dada4 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -103,7 +102,7 @@ _nl80211_alloc_msg (int id, int ifindex, int phy, guint32 cmd, guint32 flags)
 	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	return NULL;
+	g_return_val_if_reached (NULL);
 }
 
 static struct nl_msg *
@@ -200,6 +199,9 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 	case NL80211_IFTYPE_STATION:
 		info->mode = NM_802_11_MODE_INFRA;
 		break;
+	case NL80211_IFTYPE_MESH_POINT:
+		info->mode = NM_802_11_MODE_MESH;
+		break;
 	}
 
 	return NL_SKIP;
@@ -242,6 +244,9 @@ wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 	case NM_802_11_MODE_AP:
 		NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP);
 		break;
+	case NM_802_11_MODE_MESH:
+		NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT);
+		break;
 	default:
 		g_assert_not_reached ();
 	}
@@ -250,7 +255,7 @@ wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -267,7 +272,7 @@ wifi_nl80211_set_powersave (NMWifiUtils *data, guint32 powersave)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static int
@@ -365,7 +370,7 @@ wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wo
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 /* @divisor: pass what value @xbm should be divided by to get dBm */
@@ -642,7 +647,7 @@ nl80211_get_ap_info (NMWifiUtilsNl80211 *self,
 	return;
 
 nla_put_failure:
-	return;
+	g_return_if_reached ();
 }
 
 static guint32
@@ -695,7 +700,7 @@ wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 struct nl80211_device_info {
@@ -893,10 +898,11 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 		int i;
 
 		nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) {
-			if (nla_type (nl_mode) == NL80211_IFTYPE_AP)
-				info->caps |= NM_WIFI_DEVICE_CAP_AP;
-			else if (nla_type (nl_mode) == NL80211_IFTYPE_ADHOC)
-				info->caps |= NM_WIFI_DEVICE_CAP_ADHOC;
+			switch (nla_type (nl_mode)) {
+			case NL80211_IFTYPE_AP:         info->caps |= NM_WIFI_DEVICE_CAP_AP;    break;
+			case NL80211_IFTYPE_ADHOC:      info->caps |= NM_WIFI_DEVICE_CAP_ADHOC; break;
+			case NL80211_IFTYPE_MESH_POINT: info->caps |= NM_WIFI_DEVICE_CAP_MESH;  break;
+			}
 		}
 	}
 
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.h b/src/platform/wifi/nm-wifi-utils-nl80211.h
index 27f67697..a4c9ca55 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.h
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/wifi/nm-wifi-utils-private.h b/src/platform/wifi/nm-wifi-utils-private.h
index bcbc1c51..4cae22e5 100644
--- a/src/platform/wifi/nm-wifi-utils-private.h
+++ b/src/platform/wifi/nm-wifi-utils-private.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/wifi/nm-wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index 3aa1720a..d19cf8a5 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/wifi/nm-wifi-utils-wext.h b/src/platform/wifi/nm-wifi-utils-wext.h
index 44b11afb..70cae966 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.h
+++ b/src/platform/wifi/nm-wifi-utils-wext.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/platform/wifi/nm-wifi-utils.c
index 96071faa..1e42db6a 100644
--- a/src/platform/wifi/nm-wifi-utils.c
+++ b/src/platform/wifi/nm-wifi-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -90,7 +89,8 @@ nm_wifi_utils_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 	g_return_val_if_fail (data != NULL, FALSE);
 	g_return_val_if_fail (   (mode == NM_802_11_MODE_INFRA)
 	                      || (mode == NM_802_11_MODE_AP)
-	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
+	                      || (mode == NM_802_11_MODE_ADHOC)
+	                      || (mode == NM_802_11_MODE_MESH), FALSE);
 
 	klass = NM_WIFI_UTILS_GET_CLASS (data);
 
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/platform/wifi/nm-wifi-utils.h
index 36148b5a..2e89053b 100644
--- a/src/platform/wifi/nm-wifi-utils.h
+++ b/src/platform/wifi/nm-wifi-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
index b7a51e9b..0afc2a4d 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -92,7 +92,7 @@ _nl802154_alloc_msg (int id, int ifindex, guint32 cmd, guint32 flags)
 	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	return NULL;
+	g_return_val_if_reached (NULL);
 }
 
 static struct nl_msg *
@@ -217,7 +217,7 @@ nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 guint16
@@ -244,7 +244,7 @@ nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 gboolean
@@ -262,7 +262,7 @@ nm_wpan_utils_set_channel (NMWpanUtils *self, guint8 page, guint8 channel)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 /*****************************************************************************/