about summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-01-25 11:26:25 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-01-25 11:26:25 +0100
commitf54a568880bf47c2ea54c9b30d291156b34d6539 (patch)
treeaab9bb44b306da7651e8caf38bbab5cfeeb81b7d /src/platform
parente8ac7dacdcf4b38addef4f8ac6ab038f9d29582c (diff)
parentbbae86d3d2997a853ca0365e8eb7a3ca7489ee09 (diff)
Update upstream source from tag 'upstream/1.15.2'
Update to upstream version '1.15.2'
with Debian dir b4be4b7cfce96679a31cb9d423107789761e3b49
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/nm-fake-platform.c33
-rw-r--r--src/platform/nm-linux-platform.c644
-rw-r--r--src/platform/nm-netlink.c262
-rw-r--r--src/platform/nm-netlink.h83
-rw-r--r--src/platform/nm-platform-utils.c12
-rw-r--r--src/platform/nm-platform.c1130
-rw-r--r--src/platform/nm-platform.h333
-rw-r--r--src/platform/nmp-object.c253
-rw-r--r--src/platform/nmp-object.h48
-rw-r--r--src/platform/tests/meson.build2
-rw-r--r--src/platform/tests/test-address.c2
-rw-r--r--src/platform/tests/test-cleanup.c5
-rw-r--r--src/platform/tests/test-common.c115
-rw-r--r--src/platform/tests/test-common.h18
-rw-r--r--src/platform/tests/test-link.c354
-rw-r--r--src/platform/tests/test-route.c14
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c197
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c6
-rw-r--r--src/platform/wifi/nm-wifi-utils.h2
-rw-r--r--src/platform/wpan/nm-wpan-utils.c71
-rw-r--r--src/platform/wpan/nm-wpan-utils.h2
21 files changed, 2214 insertions, 1372 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 82f9e2fb..814d9cea 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -26,6 +26,7 @@
 #include <unistd.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
+#include <linux/if.h>
 #include <linux/rtnetlink.h>
 
 #include "nm-utils.h"
@@ -194,7 +195,7 @@ link_add_prepare (NMPlatform *platform,
 {
 	gboolean connected;
 
-	/* we must clear the driver, because platform cache want's to set it */
+	/* we must clear the driver, because platform cache wants to set it */
 	g_assert (obj_tmp->link.driver == g_intern_string (obj_tmp->link.driver));
 	obj_tmp->link.driver = NULL;
 
@@ -282,7 +283,7 @@ link_add_pre (NMPlatform *platform,
 	return device;
 }
 
-static gboolean
+static int
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
@@ -334,7 +335,7 @@ link_add (NMPlatform *platform,
 	if (veth_peer)
 		link_changed (platform, device_veth, cache_op_veth, NULL);
 
-	return TRUE;
+	return 0;
 }
 
 static NMFakePlatformLink *
@@ -562,7 +563,7 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 	return TRUE;
 }
 
-static NMPlatformError
+static int
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
@@ -571,10 +572,10 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 	if (   len == 0
 	    || len > NM_UTILS_HWADDR_LEN_MAX
 	    || !addr)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	if (!device)
-		return NM_PLATFORM_ERROR_EXISTS;
+		return -NME_PL_EXISTS;
 
 	obj_tmp = nmp_object_clone (device->obj, FALSE);
 	obj_tmp->link.addr.len = len;
@@ -582,10 +583,10 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 	memcpy (obj_tmp->link.addr.data, addr, len);
 
 	link_set_obj (platform, device, obj_tmp);
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
-static NMPlatformError
+static int
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
@@ -593,13 +594,13 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 
 	if (!device) {
 		_LOGE ("failure changing link: netlink error (No such device)");
-		return NM_PLATFORM_ERROR_EXISTS;
+		return -NME_PL_EXISTS;
 	}
 
 	obj_tmp = nmp_object_clone (device->obj, FALSE);
 	obj_tmp->link.mtu = mtu;
 	link_set_obj (platform, device, obj_tmp);
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
 static const char *
@@ -1186,7 +1187,7 @@ object_delete (NMPlatform *platform, const NMPObject *obj)
 	return ipx_route_delete (platform, AF_UNSPEC, -1, obj);
 }
 
-static NMPlatformError
+static int
 ip_route_add (NMPlatform *platform,
               NMPNlmFlags flags,
               int addr_family,
@@ -1266,14 +1267,16 @@ ip_route_add (NMPlatform *platform,
 			}
 		}
 		if (!has_route_to_gw) {
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 			if (addr_family == AF_INET) {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet4_ntop (r4->network, NULL), r->plen, r->metric);
+				             r->ifindex, nm_utils_inet4_ntop (r4->network, sbuf), r->plen, r->metric);
 			} else {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet6_ntop (&r6->network, NULL), r->plen, r->metric);
+				             r->ifindex, nm_utils_inet6_ntop (&r6->network, sbuf), r->plen, r->metric);
 			}
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+			return -NME_UNSPEC;
 		}
 	}
 
@@ -1335,7 +1338,7 @@ ip_route_add (NMPlatform *platform,
 		}
 	}
 
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
 /*****************************************************************************/
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index e73d5d8c..85214ec8 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -44,6 +44,7 @@
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
+#include "nm-utils/nm-errno.h"
 #include "nm-utils/nm-secret-utils.h"
 #include "nm-netlink.h"
 #include "nm-core-utils.h"
@@ -186,6 +187,12 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 #define WG_CMD_GET_DEVICE 0
 #define WG_CMD_SET_DEVICE 1
 
+#define WGDEVICE_F_REPLACE_PEERS               ((guint32) (1U << 0))
+
+#define WGPEER_F_REMOVE_ME                     ((guint32) (1U << 0))
+#define WGPEER_F_REPLACE_ALLOWEDIPS            ((guint32) (1U << 1))
+
+
 #define WGDEVICE_A_UNSPEC                      0
 #define WGDEVICE_A_IFINDEX                     1
 #define WGDEVICE_A_IFNAME                      2
@@ -447,7 +454,7 @@ G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
         if (nm_logging_enabled (__level, __domain)) { \
             int __errsv = (errsv); \
             \
-            /* The %m format specifier (GNU extension) would alread allow you to specify the error
+            /* The %m format specifier (GNU extension) would already allow you to specify the error
              * message conveniently (and nm_log would get that right too). But we don't want to depend
              * on that, so instead append the message at the end.
              * Currently users are expected not to use %m in the format string. */ \
@@ -474,14 +481,14 @@ static struct nl_sock *_genl_sock (NMLinuxPlatform *platform);
 
 /*****************************************************************************/
 
-static NMPlatformError
-wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result)
+static int
+wait_for_nl_response_to_nmerr (WaitForNlResponseResult seq_result)
 {
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-		return NM_PLATFORM_ERROR_SUCCESS;
+		return 0;
 	if (seq_result < 0)
-		return (NMPlatformError) seq_result;
-	return NM_PLATFORM_ERROR_NETLINK;
+		return (int) seq_result;
+	return -NME_PL_NETLINK;
 }
 
 static const char *
@@ -1942,9 +1949,10 @@ _wireguard_update_from_allowed_ips_nla (NMPWireGuardAllowedIP *allowed_ip,
 
 	_check_addr_or_return_val (tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE);
 
-	memset (allowed_ip, 0, sizeof (NMPWireGuardAllowedIP));
+	*allowed_ip = (NMPWireGuardAllowedIP) {
+		.family = family,
+	};
 
-	allowed_ip->family = family;
 	nm_assert ((int) allowed_ip->family == family);
 
 	if (tb[WGALLOWEDIP_A_IPADDR])
@@ -2006,30 +2014,11 @@ _wireguard_update_from_peers_nla (CList *peers,
 			nm_explicit_bzero (nla_data (tb[WGPEER_A_PRESHARED_KEY]),
 			                   nla_len (tb[WGPEER_A_PRESHARED_KEY]));
 		}
-		if (tb[WGPEER_A_ENDPOINT]) {
-			const struct sockaddr *addr = nla_data (tb[WGPEER_A_ENDPOINT]);
-			unsigned short family;
-
-			G_STATIC_ASSERT (sizeof (addr->sa_family) == sizeof (family));
-			memcpy (&family, &addr->sa_family, sizeof (addr->sa_family));
-
-			if (   family == AF_INET
-			    && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in)) {
-				const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
-
-				peer_c->data.endpoint_family = AF_INET;
-				peer_c->data.endpoint_port = unaligned_read_be16 (&addr4->sin_port);
-				peer_c->data.endpoint_addr.addr4 = unaligned_read_ne32 (&addr4->sin_addr.s_addr);
-				memcpy (&peer_c->data.endpoint_addr.addr4, &addr4->sin_addr.s_addr, 4);
-			} else if (   family == AF_INET6
-			           && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in6)) {
-				const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
-
-				peer_c->data.endpoint_family = AF_INET6;
-				peer_c->data.endpoint_port = unaligned_read_be16 (&addr6->sin6_port);
-				memcpy (&peer_c->data.endpoint_addr.addr6, &addr6->sin6_addr, 16);
-			}
-		}
+
+		nm_sock_addr_union_cpy_untrusted (&peer_c->data.endpoint,
+		                                  tb[WGPEER_A_ENDPOINT] ? nla_data (tb[WGPEER_A_ENDPOINT]) : NULL,
+		                                  tb[WGPEER_A_ENDPOINT] ? nla_len (tb[WGPEER_A_ENDPOINT])  : 0);
+
 		if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
 			peer_c->data.persistent_keepalive_interval = nla_get_u64 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
 		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
@@ -2157,9 +2146,9 @@ _wireguard_get_device_cb (struct nl_msg *msg, void *arg)
 
 static const NMPObject *
 _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
-                     struct nl_sock *genl,
-                     int wireguard_family_id,
-                     int ifindex)
+                      struct nl_sock *genl,
+                      int wireguard_family_id,
+                      int ifindex)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	NMPObject *obj = NULL;
@@ -2175,6 +2164,8 @@ _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
 	nm_assert (wireguard_family_id >= 0);
 	nm_assert (ifindex > 0);
 
+	_LOGT ("wireguard: fetching infomation for ifindex %d (genl-id %d)...", ifindex, wireguard_family_id);
+
 	msg = nlmsg_alloc ();
 
 	if (!genlmsg_put (msg,
@@ -2227,7 +2218,7 @@ _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
 	 * there. The realloc/resize of the GArray is fine there. However,
 	 * while we build the GArray, we don't yet have the final pointers.
 	 * Hence, while constructing, we track the indexes with peer->_construct_idx_*
-	 * fields. These indexes must be convered to actual pointers blow.
+	 * fields. These indexes must be converted to actual pointers blow.
 	 *
 	 * This is all done during parsing. In the final NMPObjectLnkWireGuard we
 	 * don't want the CList anymore and repackage the NMPObject tightly. The
@@ -2283,6 +2274,336 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
+static int
+_wireguard_get_family_id (NMPlatform *platform, int ifindex_try)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	int wireguard_family_id = -1;
+
+	if (ifindex_try > 0) {
+		const NMPlatformLink *plink;
+
+		if (nm_platform_link_get_lnk_wireguard (platform, ifindex_try, &plink))
+			wireguard_family_id = NMP_OBJECT_UP_CAST (plink)->_link.wireguard_family_id;
+	}
+	if (wireguard_family_id < 0)
+		wireguard_family_id = genl_ctrl_resolve (priv->genl, "wireguard");
+	return wireguard_family_id;
+}
+
+static const NMPObject *
+_wireguard_refresh_link (NMPlatform *platform,
+                         int wireguard_family_id,
+                         int ifindex)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *lnk_new = NULL;
+	NMPCacheOpsType cache_op;
+	const NMPObject *plink = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+
+	nm_assert (wireguard_family_id >= 0);
+	nm_assert (ifindex > 0);
+
+	nm_platform_process_events (platform);
+
+	plink = nm_platform_link_get_obj (platform, ifindex, TRUE);
+
+	if (   !plink
+	    || plink->link.type != NM_LINK_TYPE_WIREGUARD) {
+		nm_platform_link_refresh (platform, ifindex);
+		plink = nm_platform_link_get_obj (platform, ifindex, TRUE);
+		if (   !plink
+		    || plink->link.type != NM_LINK_TYPE_WIREGUARD)
+			return NULL;
+		if (NMP_OBJECT_GET_TYPE (plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
+			lnk_new = nmp_object_ref (plink->_link.netlink.lnk);
+	} else {
+		lnk_new = _wireguard_read_info (platform,
+		                                priv->genl,
+		                                wireguard_family_id,
+		                                ifindex);
+		if (!lnk_new) {
+			if (NMP_OBJECT_GET_TYPE (plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
+				lnk_new = nmp_object_ref (plink->_link.netlink.lnk);
+		} else if (nmp_object_equal (plink->_link.netlink.lnk, lnk_new)) {
+			nmp_object_unref (lnk_new);
+			lnk_new = nmp_object_ref (plink->_link.netlink.lnk);
+		}
+	}
+
+	if (   plink->_link.wireguard_family_id == wireguard_family_id
+	    && plink->_link.netlink.lnk == lnk_new)
+		return plink;
+
+	/* we use nmp_cache_update_netlink() to re-inject the new object into the cache.
+	 * For that, we need to clone it, and tweak it so that it's suitable. It's a bit
+	 * of a hack, in particular that we need to clear driver and udev-device. */
+	obj = nmp_object_clone (plink, FALSE);
+	obj->_link.wireguard_family_id = wireguard_family_id;
+	nmp_object_unref (obj->_link.netlink.lnk);
+	obj->_link.netlink.lnk = g_steal_pointer (&lnk_new);
+	obj->link.driver = NULL;
+	nm_clear_pointer (&obj->_link.udev.device, udev_device_unref);
+
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     obj,
+	                                     FALSE,
+	                                     &obj_old,
+	                                     &obj_new);
+	nm_assert (NM_IN_SET (cache_op, NMP_CACHE_OPS_UPDATED));
+	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+		cache_on_change (platform, cache_op, obj_old, obj_new);
+		nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+	}
+
+	nm_assert (   !obj_new
+	           || (   NMP_OBJECT_GET_TYPE (obj_new) == NMP_OBJECT_TYPE_LINK
+	               && obj_new->link.type == NM_LINK_TYPE_WIREGUARD
+	               && (   !obj_new->_link.netlink.lnk
+	                   || NMP_OBJECT_GET_TYPE (obj_new->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)));
+	return obj_new;
+}
+
+static int
+_wireguard_create_change_nlmsgs (NMPlatform *platform,
+                                 int ifindex,
+                                 int wireguard_family_id,
+                                 const NMPlatformLnkWireGuard *lnk_wireguard,
+                                 const NMPWireGuardPeer *peers,
+                                 guint peers_len,
+                                 gboolean replace_peers,
+                                 GPtrArray **out_msgs)
+{
+	gs_unref_ptrarray GPtrArray *msgs = NULL;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const guint IDX_NIL = G_MAXUINT;
+	guint idx_peer_curr;
+	guint idx_allowed_ips_curr;
+	struct nlattr *nest_peers;
+	struct nlattr *nest_curr_peer;
+	struct nlattr *nest_allowed_ips;
+	struct nlattr *nest_curr_allowed_ip;
+
+#define _nla_nest_end(msg, nest_start) \
+	G_STMT_START { \
+		if (nla_nest_end ((msg), (nest_start)) < 0) \
+			g_return_val_if_reached (-NME_BUG); \
+	} G_STMT_END
+
+	/* Adapted from LGPL-2.1+ code [1].
+	 *
+	 * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */
+
+	idx_peer_curr = IDX_NIL;
+	idx_allowed_ips_curr = IDX_NIL;
+
+	/* TODO: for the moment, we always reset all peers and allowed-ips (WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REPLACE_ALLOWEDIPS).
+	 * The platform API should be extended to also support partial updates. In particular, configuring the same configuration
+	 * multiple times, should not clear and re-add all settings, but rather sync the existing settings with the desired configuration. */
+
+again:
+
+	msg = nlmsg_alloc ();
+	if (!genlmsg_put (msg,
+	                  NL_AUTO_PORT,
+	                  NL_AUTO_SEQ,
+	                  wireguard_family_id,
+	                  0,
+	                  NLM_F_REQUEST,
+	                  WG_CMD_SET_DEVICE,
+	                  1))
+		g_return_val_if_reached (-NME_BUG);
+
+	NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
+
+	if (idx_peer_curr == IDX_NIL) {
+		NLA_PUT (msg, WGDEVICE_A_PRIVATE_KEY, sizeof (lnk_wireguard->private_key), lnk_wireguard->private_key);
+		NLA_PUT_U16 (msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port);
+		NLA_PUT_U32 (msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark);
+
+		NLA_PUT_U32 (msg, WGDEVICE_A_FLAGS,
+		             replace_peers ? WGDEVICE_F_REPLACE_PEERS : ((guint32) 0u));
+	}
+
+	if (peers_len == 0)
+		goto send;
+
+	nest_curr_peer = NULL;
+	nest_allowed_ips = NULL;
+	nest_curr_allowed_ip = NULL;
+
+	nest_peers = nla_nest_start (msg, WGDEVICE_A_PEERS);
+	if (!nest_peers)
+		g_return_val_if_reached (-NME_BUG);
+
+	if (idx_peer_curr == IDX_NIL)
+		idx_peer_curr = 0;
+	for (; idx_peer_curr < peers_len; idx_peer_curr++) {
+		const NMPWireGuardPeer *p = &peers[idx_peer_curr];
+
+		nest_curr_peer = nla_nest_start (msg, 0);
+		if (!nest_curr_peer)
+			goto toobig_peers;
+
+		if (nla_put (msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0)
+			goto toobig_peers;
+
+		if (idx_allowed_ips_curr == IDX_NIL) {
+
+			if (nla_put (msg, WGPEER_A_PRESHARED_KEY, sizeof (p->preshared_key), p->preshared_key) < 0)
+				goto toobig_peers;
+
+			if (nla_put_uint16 (msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, p->persistent_keepalive_interval) < 0)
+				goto toobig_peers;
+
+			if (nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0)
+				goto toobig_peers;
+
+			if (NM_IN_SET (p->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
+				if (nla_put (msg,
+				             WGPEER_A_ENDPOINT,
+				               p->endpoint.sa.sa_family == AF_INET
+				             ? sizeof (p->endpoint.in)
+				             : sizeof (p->endpoint.in6),
+				             &p->endpoint) < 0)
+					goto toobig_peers;
+			} else
+				nm_assert (p->endpoint.sa.sa_family == AF_UNSPEC);
+		}
+
+		if (p->allowed_ips_len > 0) {
+			if (idx_allowed_ips_curr == IDX_NIL)
+				idx_allowed_ips_curr = 0;
+
+			nest_allowed_ips = nla_nest_start (msg, WGPEER_A_ALLOWEDIPS);
+			if (!nest_allowed_ips)
+				goto toobig_allowedips;
+
+			for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) {
+				const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr];
+
+				nest_curr_allowed_ip = nla_nest_start (msg, 0);
+				if (!nest_curr_allowed_ip)
+					goto toobig_allowedips;
+
+				g_return_val_if_fail (NM_IN_SET (aip->family, AF_INET, AF_INET6), -NME_BUG);
+
+				if (nla_put_uint16 (msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0)
+					goto toobig_allowedips;
+				if (nla_put (msg,
+				             WGALLOWEDIP_A_IPADDR,
+				             nm_utils_addr_family_to_size (aip->family),
+				             &aip->addr) < 0)
+					goto toobig_allowedips;
+				if (nla_put_uint8 (msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0)
+					goto toobig_allowedips;
+
+				_nla_nest_end (msg, nest_curr_allowed_ip);
+				nest_curr_allowed_ip = NULL;
+			}
+			idx_allowed_ips_curr = IDX_NIL;
+
+			_nla_nest_end (msg, nest_allowed_ips);
+			nest_allowed_ips = NULL;
+		}
+
+		_nla_nest_end (msg, nest_curr_peer);
+		nest_curr_peer = NULL;
+	}
+
+	_nla_nest_end (msg, nest_peers);
+	goto send;
+
+toobig_allowedips:
+	if (nest_curr_allowed_ip)
+		nla_nest_cancel (msg, nest_curr_allowed_ip);
+	if (nest_allowed_ips)
+		nla_nest_cancel (msg, nest_allowed_ips);
+	_nla_nest_end (msg, nest_curr_peer);
+	_nla_nest_end (msg, nest_peers);
+	goto send;
+
+toobig_peers:
+	if (nest_curr_peer)
+		nla_nest_cancel (msg, nest_curr_peer);
+	_nla_nest_end (msg, nest_peers);
+	goto send;
+
+send:
+	if (!msgs)
+		msgs = g_ptr_array_new_with_free_func ((GDestroyNotify) nlmsg_free);
+	g_ptr_array_add (msgs, g_steal_pointer (&msg));
+
+	if (   idx_peer_curr != IDX_NIL
+	    && idx_peer_curr < peers_len)
+		goto again;
+
+	NM_SET_OUT (out_msgs, g_steal_pointer (&msgs));
+	return 0;
+
+nla_put_failure:
+	g_return_val_if_reached (-NME_BUG);
+
+#undef _nla_nest_end
+}
+
+static int
+link_wireguard_change (NMPlatform *platform,
+                       int ifindex,
+                       const NMPlatformLnkWireGuard *lnk_wireguard,
+                       const NMPWireGuardPeer *peers,
+                       guint peers_len,
+                       gboolean replace_peers)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	gs_unref_ptrarray GPtrArray *msgs = NULL;
+	int wireguard_family_id;
+	guint i;
+	int r;
+
+	wireguard_family_id = _wireguard_get_family_id (platform, ifindex);
+	if (wireguard_family_id < 0)
+		return -NME_PL_NO_FIRMWARE;
+
+	r = _wireguard_create_change_nlmsgs (platform,
+	                                     ifindex,
+	                                     wireguard_family_id,
+	                                     lnk_wireguard,
+	                                     peers,
+	                                     peers_len,
+	                                     replace_peers,
+	                                     &msgs);
+	if (r < 0) {
+		_LOGW ("wireguard: set-device, cannot construct netlink message: %s", nm_strerror (r));
+		return r;
+	}
+
+	for (i = 0; i < msgs->len; i++) {
+		r = nl_send_auto (priv->genl, msgs->pdata[i]);
+		if (r < 0) {
+			_LOGW ("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror (r));
+			return r;
+		}
+
+		do {
+			r = nl_recvmsgs (priv->genl, NULL);
+		} while (r == -EAGAIN);
+		if (r < 0) {
+			_LOGW ("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror (r));
+			return r;
+		}
+
+		_LOGT ("wireguard: set-device, message #%u sent and confirmed", i);
+	}
+
+	_wireguard_refresh_link (platform, wireguard_family_id, ifindex);
+
+	return 0;
+}
+
 /*****************************************************************************/
 
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
@@ -4745,7 +5066,7 @@ _nl_send_nlmsg (NMPlatform *platform,
 
 	nle = nl_send_auto (priv->nlh, nlmsg);
 	if (nle < 0) {
-		_LOGD ("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nl_geterror (nle), nle);
+		_LOGD ("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror (nle), nle);
 		return nle;
 	}
 
@@ -4791,7 +5112,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 		if (nle < 0) {
 			_LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
 			       ifindex, name ?: "",
-			       nl_geterror (nle), -nle);
+			       nm_strerror (nle), -nle);
 			return;
 		}
 	}
@@ -5121,7 +5442,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 
 /*****************************************************************************/
 
-static gboolean
+static int
 do_add_link_with_lookup (NMPlatform *platform,
                          NMLinkType link_type,
                          const char *name,
@@ -5142,9 +5463,9 @@ do_add_link_with_lookup (NMPlatform *platform,
 		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
 		       name,
 		       nm_link_type_to_string (link_type),
-		       nl_geterror (nle), -nle);
+		       nm_strerror (nle), -nle);
 		NM_SET_OUT (out_link, NULL);
-		return FALSE;
+		return nle;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -5164,10 +5485,10 @@ do_add_link_with_lookup (NMPlatform *platform,
 		*out_link = NMP_OBJECT_CAST_LINK (obj);
 	}
 
-	return seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+	return wait_for_nl_response_to_nmerr (seq_result);
 }
 
-static NMPlatformError
+static int
 do_add_addrroute (NMPlatform *platform,
                   const NMPObject *obj_id,
                   struct nl_msg *nlmsg,
@@ -5189,8 +5510,8 @@ do_add_addrroute (NMPlatform *platform,
 		_LOGE ("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-		       nl_geterror (nle), -nle);
-		return NM_PLATFORM_ERROR_NETLINK;
+		       nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -5219,7 +5540,7 @@ do_add_addrroute (NMPlatform *platform,
 			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
 	}
 
-	return wait_for_nl_response_to_plerr (seq_result);
+	return wait_for_nl_response_to_nmerr (seq_result);
 }
 
 static gboolean
@@ -5239,7 +5560,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 		_LOGE ("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-		       nl_geterror (nle), -nle);
+		       nm_strerror (nle), -nle);
 		return FALSE;
 	}
 
@@ -5287,7 +5608,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	return success;
 }
 
-static NMPlatformError
+static int
 do_change_link (NMPlatform *platform,
                 ChangeLinkType change_link_type,
                 int ifindex,
@@ -5299,7 +5620,7 @@ do_change_link (NMPlatform *platform,
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	gs_free char *errmsg = NULL;
 	char s_buf[256];
-	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
+	int result = 0;
 	NMLogLevel log_level = LOGL_DEBUG;
 	const char *log_result = "failure";
 	const char *log_detail = "";
@@ -5317,7 +5638,7 @@ retry:
 	if (nle < 0) {
 		log_level = LOGL_ERR;
 		log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)",
-		                                   nl_geterror (nle), -nle);
+		                                   nm_strerror (nle), -nle);
 		log_detail = log_detail_free;
 		goto out;
 	}
@@ -5342,11 +5663,11 @@ retry:
 		/* */
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
 		log_detail = ", firmware not found";
-		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+		result = -NME_PL_NO_FIRMWARE;
 	} else if (   NM_IN_SET (-((int) seq_result), ERANGE)
 	           && change_link_type == CHANGE_LINK_TYPE_SET_MTU) {
 		log_detail = ", setting MTU to requested size is not possible";
-		result = NM_PLATFORM_ERROR_CANT_SET_MTU;
+		result = -NME_PL_CANT_SET_MTU;
 	} 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))
@@ -5356,16 +5677,16 @@ retry:
 		 * If the MAC address is as expected, assume success? */
 		log_result = "success";
 		log_detail = " (assume success changing address)";
-		result = NM_PLATFORM_ERROR_SUCCESS;
+		result = 0;
 	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
 		log_level = LOGL_DEBUG;
-		result = NM_PLATFORM_ERROR_NOT_FOUND;
+		result = -NME_PL_NOT_FOUND;
 	} else if (-((int) seq_result) == EAFNOSUPPORT) {
 		log_level = LOGL_DEBUG;
-		result = NM_PLATFORM_ERROR_OPNOTSUPP;
+		result = -NME_PL_OPNOTSUPP;
 	} else {
 		log_level = LOGL_WARN;
-		result = NM_PLATFORM_ERROR_UNSPECIFIED;
+		result = -NME_UNSPEC;
 	}
 
 out:
@@ -5378,7 +5699,7 @@ out:
 	return result;
 }
 
-static gboolean
+static int
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
@@ -5408,17 +5729,17 @@ link_add (NMPlatform *platform,
 	                          0,
 	                          0);
 	if (!nlmsg)
-		return FALSE;
+		return -NME_UNSPEC;
 
 	if (address && address_len)
 		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
 	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, veth_peer))
-		return FALSE;
+		return -NME_UNSPEC;
 
 	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
 nla_put_failure:
-	g_return_val_if_reached (FALSE);
+	g_return_val_if_reached (-NME_BUG);
 }
 
 static gboolean
@@ -5473,13 +5794,13 @@ link_set_netns (NMPlatform *platform,
 		return FALSE;
 
 	NLA_PUT (nlmsg, IFLA_NET_NS_FD, 4, &netns_fd);
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static NMPlatformError
+static int
 link_change_flags (NMPlatform *platform,
                    int ifindex,
                    unsigned flags_mask,
@@ -5502,37 +5823,36 @@ link_change_flags (NMPlatform *platform,
 	                          flags_mask,
 	                          flags_set);
 	if (!nlmsg)
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
+		return -NME_UNSPEC;
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
 
 static gboolean
 link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
-	NMPlatformError plerr;
+	int r;
 
-	plerr = link_change_flags (platform, ifindex, IFF_UP, IFF_UP);
-	if (out_no_firmware)
-		*out_no_firmware = plerr == NM_PLATFORM_ERROR_NO_FIRMWARE;
-	return plerr == NM_PLATFORM_ERROR_SUCCESS;
+	r = link_change_flags (platform, ifindex, IFF_UP, IFF_UP);
+	NM_SET_OUT (out_no_firmware, (r == -NME_PL_NO_FIRMWARE));
+	return r >= 0;
 }
 
 static gboolean
 link_set_down (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, 0) == NM_PLATFORM_ERROR_SUCCESS;
+	return (link_change_flags (platform, ifindex, IFF_UP, 0) >= 0);
 }
 
 static gboolean
 link_set_arp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, 0) == NM_PLATFORM_ERROR_SUCCESS;
+	return (link_change_flags (platform, ifindex, IFF_NOARP, 0) >= 0);
 }
 
 static gboolean
 link_set_noarp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) == NM_PLATFORM_ERROR_SUCCESS;
+	return (link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) >= 0);
 }
 
 static const char *
@@ -5547,7 +5867,7 @@ link_get_udi (NMPlatform *platform, int ifindex)
 	return udev_device_get_syspath (obj->_link.udev.device);
 }
 
-static NMPlatformError
+static int
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5559,7 +5879,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 
 	if (!_support_user_ipv6ll_get ()) {
 		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
-		return NM_PLATFORM_ERROR_OPNOTSUPP;
+		return -NME_PL_OPNOTSUPP;
 	}
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
@@ -5570,7 +5890,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	                          0);
 	if (   !nlmsg
 	    || !_nl_msg_new_link_set_afspec (nlmsg, mode, NULL))
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
@@ -5579,15 +5899,16 @@ static gboolean
 link_set_token (NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_LOGD ("link: change %d: token: set IPv6 address generation token to %s",
-	       ifindex, nm_utils_inet6_interface_identifier_to_token (iid, NULL));
+	       ifindex, nm_utils_inet6_interface_identifier_to_token (iid, sbuf));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK, 0, ifindex, NULL, 0, 0);
 	if (!nlmsg || !_nl_msg_new_link_set_afspec (nlmsg, -1, &iid))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 }
 
 static gboolean
@@ -5647,7 +5968,7 @@ link_supports_sriov (NMPlatform *platform, int ifindex)
 	return total > 0;
 }
 
-static NMPlatformError
+static int
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5659,7 +5980,7 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	};
 
 	if (!address || !length)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
@@ -5668,16 +5989,16 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	                          0,
 	                          0);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+		g_return_val_if_reached (-NME_BUG);
 
 	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
 
 	return do_change_link (platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d);
 nla_put_failure:
-	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+	g_return_val_if_reached (-NME_BUG);
 }
 
-static NMPlatformError
+static int
 link_set_name (NMPlatform *platform, int ifindex, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5689,11 +6010,11 @@ link_set_name (NMPlatform *platform, int ifindex, const char *name)
 	                          0,
 	                          0);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+		g_return_val_if_reached (-NME_BUG);
 
 	NLA_PUT (nlmsg, IFLA_IFNAME, strlen (name) + 1, name);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5712,7 +6033,7 @@ link_get_permanent_address (NMPlatform *platform,
 	return nmp_utils_ethtool_get_permanent_address (ifindex, buf, length);
 }
 
-static NMPlatformError
+static int
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5737,12 +6058,13 @@ static gboolean
 link_set_sriov_params (NMPlatform *platform,
                        int ifindex,
                        guint num_vfs,
-                       int autoprobe)
+                       NMTernary autoprobe)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	nm_auto_close int dirfd = -1;
-	gboolean current_autoprobe;
-	guint total, current_num;
+	int current_autoprobe;
+	guint total;
+	gint64 current_num;
 	char ifname[IFNAMSIZ];
 	char buf[64];
 
@@ -5775,14 +6097,14 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                  NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                                            ifname,
 	                                                                            "device/sriov_numvfs"),
-	                                                  10, 0, G_MAXUINT, 0);
+	                                                  10, 0, G_MAXUINT, -1);
 	current_autoprobe = nm_platform_sysctl_get_int_checked (platform,
 	                                                        NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                                                  ifname,
 	                                                                                  "device/sriov_drivers_autoprobe"),
-	                                                        10, 0, G_MAXUINT, 0);
+	                                                        10, 0, 1, -1);
 	if (   current_num == num_vfs
-	    && (autoprobe == -1 || current_autoprobe == autoprobe))
+	    && (autoprobe == NM_TERNARY_DEFAULT || current_autoprobe == autoprobe))
 		return TRUE;
 
 	if (current_num != 0) {
@@ -5800,14 +6122,14 @@ link_set_sriov_params (NMPlatform *platform,
 	if (num_vfs == 0)
 		return TRUE;
 
-	if (   autoprobe >= 0
+	if (   NM_IN_SET (autoprobe, NM_TERNARY_TRUE, NM_TERNARY_FALSE)
 	    && current_autoprobe != autoprobe
 	    && !nm_platform_sysctl_set (NM_PLATFORM_GET,
 	                                NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                          ifname,
 	                                                          "device/sriov_drivers_autoprobe"),
-	                                nm_sprintf_buf (buf, "%d", autoprobe))) {
-		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", autoprobe, strerror (errno));
+	                                nm_sprintf_buf (buf, "%d", (int) autoprobe))) {
+		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, strerror (errno));
 		return FALSE;
 	}
 
@@ -5837,7 +6159,7 @@ link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const
 	                          0,
 	                          0);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+		g_return_val_if_reached (-NME_BUG);
 
 	if (!(list = nla_nest_start (nlmsg, IFLA_VFINFO_LIST)))
 		goto nla_put_failure;
@@ -5913,7 +6235,7 @@ link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const
 	}
 	nla_nest_end (nlmsg, list);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5981,7 +6303,7 @@ vlan_add (NMPlatform *platform,
 	                                         0))
 		return FALSE;
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6028,9 +6350,9 @@ link_gre_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6086,7 +6408,7 @@ link_ip6tnl_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6146,9 +6468,9 @@ link_ip6gre_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6191,7 +6513,7 @@ link_ipip_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6246,9 +6568,9 @@ link_macsec_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                               NM_LINK_TYPE_MACSEC,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 NM_LINK_TYPE_MACSEC,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6289,9 +6611,9 @@ link_macvlan_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6334,7 +6656,7 @@ link_sit_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6460,7 +6782,7 @@ link_vxlan_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6492,9 +6814,9 @@ link_6lowpan_add (NMPlatform *platform,
 
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                NM_LINK_TYPE_6LOWPAN,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 NM_LINK_TYPE_6LOWPAN,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6641,7 +6963,7 @@ link_vlan_change (NMPlatform *platform,
 	                                            new_n_egress_map))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 }
 
 static gboolean
@@ -6661,7 +6983,7 @@ link_enslave (NMPlatform *platform, int master, int slave)
 
 	NLA_PUT_U32 (nlmsg, IFLA_MASTER, master);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6947,6 +7269,13 @@ wpan_set_short_addr (NMPlatform *platform, int ifindex, guint16 short_addr)
 	return nm_wpan_utils_set_short_addr (wpan_data, short_addr);
 }
 
+static gboolean
+wpan_set_channel (NMPlatform *platform, int ifindex, guint8 page, guint8 channel)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, FALSE);
+	return nm_wpan_utils_set_channel (wpan_data, page, channel);
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -7022,7 +7351,7 @@ ip4_address_add (NMPlatform *platform,
 	                             label);
 
 	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
-	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_add_addrroute (platform, &obj_id, nlmsg, FALSE) >= 0);
 }
 
 static gboolean
@@ -7052,7 +7381,7 @@ ip6_address_add (NMPlatform *platform,
 	                             NULL);
 
 	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
-	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_add_addrroute (platform, &obj_id, nlmsg, FALSE) >= 0);
 }
 
 static gboolean
@@ -7107,7 +7436,7 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, gui
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 ip_route_add (NMPlatform *platform,
               NMPNlmFlags flags,
               int addr_family,
@@ -7131,7 +7460,7 @@ ip_route_add (NMPlatform *platform,
 
 	nlmsg = _nl_msg_new_route (RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 	return do_add_addrroute (platform,
 	                         &obj,
 	                         nlmsg,
@@ -7170,7 +7499,7 @@ object_delete (NMPlatform *platform,
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 ip_route_get (NMPlatform *platform,
               int addr_family,
               gconstpointer address,
@@ -7220,7 +7549,7 @@ ip_route_get (NMPlatform *platform,
 		if (nle < 0) {
 			_LOGE ("get-route: failure sending netlink request \"%s\" (%d)",
 			       g_strerror (-nle), -nle);
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+			return -NME_UNSPEC;
 		}
 
 		delayed_action_handle_all (platform, FALSE);
@@ -7232,24 +7561,24 @@ ip_route_get (NMPlatform *platform,
 
 	if (seq_result < 0) {
 		/* negative seq_result is an errno from kernel. Map it to negative
-		 * NMPlatformError (which are also errno). */
-		return (NMPlatformError) seq_result;
+		 * int (which are also errno). */
+		return (int) seq_result;
 	}
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
 		if (route) {
 			NM_SET_OUT (out_route, g_steal_pointer (&route));
-			return NM_PLATFORM_ERROR_SUCCESS;
+			return 0;
 		}
 		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
 	}
 
-	return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return -NME_UNSPEC;
 }
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 qdisc_add (NMPlatform *platform,
            NMPNlmFlags flags,
            const NMPlatformQdisc *qdisc)
@@ -7267,8 +7596,8 @@ qdisc_add (NMPlatform *platform,
 	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-qdisc: failed sending netlink request \"%s\" (%d)",
-		      nl_geterror (nle), -nle);
-		return NM_PLATFORM_ERROR_NETLINK;
+		      nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -7282,14 +7611,14 @@ qdisc_add (NMPlatform *platform,
 	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-		return NM_PLATFORM_ERROR_SUCCESS;
+		return 0;
 
-	return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return -NME_UNSPEC;
 }
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 tfilter_add (NMPlatform *platform,
              NMPNlmFlags flags,
              const NMPlatformTfilter *tfilter)
@@ -7307,8 +7636,8 @@ tfilter_add (NMPlatform *platform,
 	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-tfilter: failed sending netlink request \"%s\" (%d)",
-		      nl_geterror (nle), -nle);
-		return NM_PLATFORM_ERROR_NETLINK;
+		      nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -7322,9 +7651,9 @@ tfilter_add (NMPlatform *platform,
 	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-		return NM_PLATFORM_ERROR_SUCCESS;
+		return 0;
 
-	return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return -NME_UNSPEC;
 }
 
 /*****************************************************************************/
@@ -7357,17 +7686,17 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 	struct nlmsghdr *hdr;
 	WaitForNlResponseResult seq_result;
 	struct sockaddr_nl nla = {0};
-	nm_auto_free struct ucred *creds = NULL;
+	struct ucred creds;
+	gboolean creds_has;
 	nm_auto_free unsigned char *buf = NULL;
 
 continue_reading:
 	g_clear_pointer (&buf, free);
-	g_clear_pointer (&creds, free);
-	n = nl_recv (sk, &nla, &buf, &creds);
+	n = nl_recv (sk, &nla, &buf, &creds, &creds_has);
 
 	if (n <= 0) {
 
-		if (n == -NLE_MSG_TRUNC) {
+		if (n == -NME_NL_MSG_TRUNC) {
 			int buf_size;
 
 			/* the message receive buffer was too small. We lost one message, which
@@ -7400,11 +7729,11 @@ continue_reading:
 		nlmsg_set_proto (msg, NETLINK_ROUTE);
 		nlmsg_set_src (msg, &nla);
 
-		if (!creds || creds->pid) {
-			if (creds)
-				_LOGT ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid);
-			else
+		if (!creds_has || creds.pid) {
+			if (!creds_has)
 				_LOGT ("netlink: recvmsg: received message without credentials");
+			else
+				_LOGT ("netlink: recvmsg: received non-kernel message (pid %d)", creds.pid);
 			err = 0;
 			goto stop;
 		}
@@ -7412,8 +7741,7 @@ continue_reading:
 		_LOGt ("netlink: recvmsg: new message %s",
 		       nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 
-		if (creds)
-			nlmsg_set_creds (msg, creds);
+		nlmsg_set_creds (msg, &creds);
 
 		if (hdr->nlmsg_flags & NLM_F_MULTI)
 			multipart = TRUE;
@@ -7450,7 +7778,7 @@ continue_reading:
 			/* Data got lost, report back to user. The default action is to
 			 * quit parsing. The user may overrule this action by retuning
 			 * NL_SKIP or NL_PROCEED (dangerous) */
-			err = -NLE_MSG_OVERFLOW;
+			err = -NME_NL_MSG_OVERFLOW;
 			abort_parsing = TRUE;
 		} else if (hdr->nlmsg_type == NLMSG_ERROR) {
 			/* Message carries a nlmsgerr */
@@ -7461,7 +7789,7 @@ continue_reading:
 				 * is to stop parsing. The user may overrule
 				 * this action by returning NL_SKIP or
 				 * NL_PROCEED (dangerous) */
-				err = -NLE_MSG_TRUNC;
+				err = -NME_NL_MSG_TRUNC;
 				abort_parsing = TRUE;
 			} else if (e->error) {
 				int errsv = e->error > 0 ? e->error : -e->error;
@@ -7538,7 +7866,7 @@ stop:
 	}
 
 	if (interrupted)
-		return -NLE_DUMP_INTR;
+		return -NME_NL_DUMP_INTR;
 	return err;
 }
 
@@ -7575,16 +7903,16 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 				switch (nle) {
 				case -EAGAIN:
 					goto after_read;
-				case -NLE_DUMP_INTR:
-					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+				case -NME_NL_DUMP_INTR:
+					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nm_strerror (nle), nle);
 					break;
-				case -NLE_MSG_TRUNC:
+				case -NME_NL_MSG_TRUNC:
 				case -ENOBUFS:
 					_LOGI ("netlink: read: %s. Need to resynchronize platform cache",
 					       ({
 					            const char *_reason = "unknown";
 					            switch (nle) {
-					            case -NLE_MSG_TRUNC: _reason = "message truncated";       break;
+					            case -NME_NL_MSG_TRUNC: _reason = "message truncated";       break;
 					            case -ENOBUFS:       _reason = "too many netlink events"; break;
 					            }
 					            _reason;
@@ -7604,7 +7932,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 					                         NULL);
 					break;
 				default:
-					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nm_strerror (nle), nle);
 					break;
 				}
 			}
@@ -7832,7 +8160,7 @@ constructed (GObject *_object)
 	nle = nl_connect (priv->genl, NETLINK_GENERIC);
 	if (nle) {
 		_LOGE ("unable to connect the generic netlink socket \"%s\" (%d)",
-		       nl_geterror (nle), -nle);
+		       nm_strerror (nle), -nle);
 		nl_socket_free (priv->genl);
 		priv->genl = NULL;
 	}
@@ -7858,7 +8186,7 @@ constructed (GObject *_object)
 		_LOGD ("could not enable extended acks on netlink socket");
 
 	/* explicitly set the msg buffer size and disable MSG_PEEK.
-	 * If we later encounter NLE_MSG_TRUNC, we will adjust the buffer size. */
+	 * If we later encounter NME_NL_MSG_TRUNC, we will adjust the buffer size. */
 	nl_socket_disable_msg_peek (priv->nlh);
 	nle = nl_socket_set_msg_buf_size (priv->nlh, 32 * 1024);
 	g_assert (!nle);
@@ -8041,6 +8369,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->vlan_add = vlan_add;
 	platform_class->link_vlan_change = link_vlan_change;
+	platform_class->link_wireguard_change = link_wireguard_change;
 	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
@@ -8067,6 +8396,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wpan_set_pan_id = wpan_set_pan_id;
 	platform_class->wpan_get_short_addr = wpan_get_short_addr;
 	platform_class->wpan_set_short_addr = wpan_set_short_addr;
+	platform_class->wpan_set_channel = wpan_set_channel;
 
 	platform_class->link_gre_add = link_gre_add;
 	platform_class->link_ip6tnl_add = link_ip6tnl_add;
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
index 3e2ad911..6abc1c3a 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -25,6 +25,8 @@
 #include <unistd.h>
 #include <fcntl.h>
 
+#include "nm-utils/nm-errno.h"
+
 /*****************************************************************************/
 
 #ifndef SOL_NETLINK
@@ -42,16 +44,14 @@
 #define NETLINK_EXT_ACK         11
 #endif
 
-#define NL_MSG_CRED_PRESENT 1
-
 struct nl_msg {
 	int                     nm_protocol;
-	int                     nm_flags;
 	struct sockaddr_nl      nm_src;
 	struct sockaddr_nl      nm_dst;
 	struct ucred            nm_creds;
 	struct nlmsghdr *       nm_nlh;
 	size_t                  nm_size;
+	bool                    nm_creds_has:1;
 };
 
 struct nl_sock {
@@ -67,38 +67,6 @@ struct nl_sock {
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, int,
-	NM_UTILS_LOOKUP_DEFAULT (NULL),
-	NM_UTILS_LOOKUP_ITEM (NLE_UNSPEC,          "NLE_UNSPEC"),
-	NM_UTILS_LOOKUP_ITEM (NLE_BUG,             "NLE_BUG"),
-	NM_UTILS_LOOKUP_ITEM (NLE_NATIVE_ERRNO,    "NLE_NATIVE_ERRNO"),
-
-	NM_UTILS_LOOKUP_ITEM (NLE_ATTRSIZE,        "NLE_ATTRSIZE"),
-	NM_UTILS_LOOKUP_ITEM (NLE_BAD_SOCK,        "NLE_BAD_SOCK"),
-	NM_UTILS_LOOKUP_ITEM (NLE_DUMP_INTR,       "NLE_DUMP_INTR"),
-	NM_UTILS_LOOKUP_ITEM (NLE_MSG_OVERFLOW,    "NLE_MSG_OVERFLOW"),
-	NM_UTILS_LOOKUP_ITEM (NLE_MSG_TOOSHORT,    "NLE_MSG_TOOSHORT"),
-	NM_UTILS_LOOKUP_ITEM (NLE_MSG_TRUNC,       "NLE_MSG_TRUNC"),
-	NM_UTILS_LOOKUP_ITEM (NLE_SEQ_MISMATCH,    "NLE_SEQ_MISMATCH"),
-)
-
-const char *
-nl_geterror (int nlerr)
-{
-	const char *s;
-
-	nlerr = nl_errno (nlerr);
-
-	if (nlerr >= _NLE_BASE) {
-		s = _geterror (nlerr);
-		if (s)
-			return s;
-	}
-	return g_strerror (nlerr);
-}
-
-/*****************************************************************************/
-
 NM_UTILS_ENUM2STR_DEFINE (nl_nlmsgtype2str, int,
 	NM_UTILS_ENUM2STR (NLMSG_NOOP,    "NOOP"),
 	NM_UTILS_ENUM2STR (NLMSG_ERROR,   "ERROR"),
@@ -285,20 +253,6 @@ nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
 
 /*****************************************************************************/
 
-static int
-get_default_page_size (void)
-{
-	static int val = 0;
-	int v;
-
-	if (G_UNLIKELY (val == 0)) {
-		v = getpagesize ();
-		g_assert (v > 0);
-		val = v;
-	}
-	return val;
-}
-
 struct nl_msg *
 nlmsg_alloc_size (size_t len)
 {
@@ -328,7 +282,7 @@ nlmsg_alloc_size (size_t len)
 struct nl_msg *
 nlmsg_alloc (void)
 {
-	return nlmsg_alloc_size (get_default_page_size ());
+	return nlmsg_alloc_size (nm_utils_getpagesize ());
 }
 
 struct nl_msg *
@@ -385,7 +339,7 @@ nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
              int maxtype, const struct nla_policy *policy)
 {
 	if (!nlmsg_valid_hdr (nlh, hdrlen))
-		return -NLE_MSG_TOOSHORT;
+		return -NME_NL_MSG_TOOSHORT;
 
 	return nla_parse (tb, maxtype, nlmsg_attrdata (nlh, hdrlen),
 	                  nlmsg_attrlen (nlh, hdrlen), policy);
@@ -465,7 +419,7 @@ nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data)
 	nla = nla_reserve (msg, attrtype, datalen);
 	if (!nla) {
 		if (datalen < 0)
-			g_return_val_if_reached (-NLE_BUG);
+			g_return_val_if_reached (-NME_BUG);
 
 		return -ENOMEM;
 	}
@@ -531,7 +485,7 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 		nla_nest_cancel (msg, start);
 
 		/* Return error only if nlattr size was exceeded */
-		return (len == NLA_HDRLEN) ? 0 : -NLE_ATTRSIZE;
+		return (len == NLA_HDRLEN) ? 0 : -NME_NL_ATTRSIZE;
 	}
 
 	start->nla_len = len;
@@ -539,13 +493,13 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 	pad = NLMSG_ALIGN (msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
 	if (pad > 0) {
 		/*
-		 * Data inside attribute does not end at a alignment boundry.
+		 * Data inside attribute does not end at a alignment boundary.
 		 * Pad accordingly and accoun for the additional space in
 		 * the message. nlmsg_reserve() may never fail in this situation,
 		 * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
 		 */
 		if (!nlmsg_reserve (msg, pad, 0))
-			g_return_val_if_reached (-NLE_BUG);
+			g_return_val_if_reached (-NME_BUG);
 	}
 
 	return 0;
@@ -580,7 +534,7 @@ validate_nla (const struct nlattr *nla, int maxtype,
 	pt = &policy[type];
 
 	if (pt->type > NLA_TYPE_MAX)
-		g_return_val_if_reached (-NLE_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	if (pt->minlen)
 		minlen = pt->minlen;
@@ -588,15 +542,15 @@ validate_nla (const struct nlattr *nla, int maxtype,
 		minlen = nla_attr_minlen[pt->type];
 
 	if (nla_len (nla) < minlen)
-		return -NLE_UNSPEC;
+		return -NME_UNSPEC;
 
 	if (pt->maxlen && nla_len (nla) > pt->maxlen)
-		return -NLE_UNSPEC;
+		return -NME_UNSPEC;
 
 	if (pt->type == NLA_STRING) {
 		const char *data = nla_data (nla);
 		if (data[nla_len (nla) - 1] != '\0')
-			return -NLE_UNSPEC;
+			return -NME_UNSPEC;
 	}
 
 	return 0;
@@ -607,7 +561,7 @@ nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
            const struct nla_policy *policy)
 {
 	struct nlattr *nla;
-	int rem, nlerr;
+	int rem, nmerr;
 
 	memset (tb, 0, sizeof (struct nlattr *) * (maxtype + 1));
 
@@ -618,17 +572,17 @@ nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
 			continue;
 
 		if (policy) {
-			nlerr = validate_nla (nla, maxtype, policy);
-			if (nlerr < 0)
+			nmerr = validate_nla (nla, maxtype, policy);
+			if (nmerr < 0)
 				goto errout;
 		}
 
 		tb[type] = nla;
 	}
 
-	nlerr = 0;
+	nmerr = 0;
 errout:
-	return nlerr;
+	return nmerr;
 }
 
 /*****************************************************************************/
@@ -654,7 +608,7 @@ nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr)
 struct ucred *
 nlmsg_get_creds (struct nl_msg *msg)
 {
-	if (msg->nm_flags & NL_MSG_CRED_PRESENT)
+	if (msg->nm_creds_has)
 		return &msg->nm_creds;
 	return NULL;
 }
@@ -662,8 +616,11 @@ nlmsg_get_creds (struct nl_msg *msg)
 void
 nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds)
 {
-	memcpy (&msg->nm_creds, creds, sizeof (*creds));
-	msg->nm_flags |= NL_MSG_CRED_PRESENT;
+	if (creds) {
+		memcpy (&msg->nm_creds, creds, sizeof (*creds));
+		msg->nm_creds_has = TRUE;
+	} else
+		msg->nm_creds_has = FALSE;
 }
 
 /*****************************************************************************/
@@ -754,7 +711,7 @@ genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
 	struct genlmsghdr *ghdr;
 
 	if (!genlmsg_valid_hdr (nlh, hdrlen))
-		return -NLE_MSG_TOOSHORT;
+		return -NME_NL_MSG_TOOSHORT;
 
 	ghdr = nlmsg_data (nlh);
 	return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen),
@@ -791,7 +748,7 @@ int
 genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	int nlerr;
+	int nmerr;
 	gint32 response_data = -1;
 	const struct nl_cb cb = {
 		.valid_cb = _genl_parse_getfamily,
@@ -804,25 +761,25 @@ genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 	                  0, 0, CTRL_CMD_GETFAMILY, 1))
 		return -ENOMEM;
 
-	nlerr = nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name);
+	if (nmerr < 0)
+		return nmerr;
 
-	nlerr = nl_send_auto (sk, msg);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nl_send_auto (sk, msg);
+	if (nmerr < 0)
+		return nmerr;
 
-	nlerr = nl_recvmsgs (sk, &cb);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nl_recvmsgs (sk, &cb);
+	if (nmerr < 0)
+		return nmerr;
 
 	/* If search was successful, request may be ACKed after data */
-	nlerr = nl_wait_for_ack (sk, NULL);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nl_wait_for_ack (sk, NULL);
+	if (nmerr < 0)
+		return nmerr;
 
 	if (response_data < 0)
-		return -NLE_UNSPEC;
+		return -NME_UNSPEC;
 
 	return response_data;
 }
@@ -879,12 +836,12 @@ nl_socket_set_passcred (struct nl_sock *sk, int state)
 	int err;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_PASSCRED,
 	                  &state, sizeof (state));
 	if (err < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	if (state)
 		sk->s_flags |= NL_SOCK_PASSCRED;
@@ -912,10 +869,10 @@ int
 nl_socket_set_nonblocking (const struct nl_sock *sk)
 {
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	if (fcntl (sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	return 0;
 }
@@ -932,18 +889,18 @@ nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf)
 		txbuf = 32768;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_SNDBUF,
 	                  &txbuf, sizeof (txbuf));
 	if (err < 0) {
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 	}
 
 	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_RCVBUF,
 	                  &rxbuf, sizeof (rxbuf));
 	if (err < 0) {
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 	}
 
 	return 0;
@@ -956,14 +913,14 @@ nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
 	va_list ap;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	va_start (ap, group);
 
 	while (group != 0) {
 		if (group < 0) {
 			va_end (ap);
-			g_return_val_if_reached (-NLE_BUG);
+			g_return_val_if_reached (-NME_BUG);
 		}
 
 		err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
@@ -972,7 +929,7 @@ nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
 			int errsv = errno;
 
 			va_end (ap);
-			return -nl_syserr2nlerr (errsv);
+			return -nm_errno_from_native (errsv);
 		}
 
 		group = va_arg (ap, int);
@@ -989,12 +946,12 @@ nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable)
 	int err, val;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	val = !!enable;
 	err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof (val));
 	if (err < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	return 0;
 }
@@ -1008,21 +965,21 @@ void nl_socket_disable_msg_peek (struct nl_sock *sk)
 int
 nl_connect (struct nl_sock *sk, int protocol)
 {
-	int err, nlerr;
+	int err, nmerr;
 	socklen_t addrlen;
 	struct sockaddr_nl local = { 0 };
 
 	if (sk->s_fd != -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	sk->s_fd = socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
 	if (sk->s_fd < 0) {
-		nlerr = -nl_syserr2nlerr (errno);
+		nmerr = -nm_errno_from_native (errno);
 		goto errout;
 	}
 
-	nlerr = nl_socket_set_buffer_size (sk, 0, 0);
-	if (nlerr < 0)
+	nmerr = nl_socket_set_buffer_size (sk, 0, 0);
+	if (nmerr < 0)
 		goto errout;
 
 	nm_assert (sk->s_local.nl_pid == 0);
@@ -1030,7 +987,7 @@ nl_connect (struct nl_sock *sk, int protocol)
 	err = bind (sk->s_fd, (struct sockaddr*) &sk->s_local,
 	            sizeof (sk->s_local));
 	if (err != 0) {
-		nlerr = -nl_syserr2nlerr (errno);
+		nmerr = -nm_errno_from_native (errno);
 		goto errout;
 	}
 
@@ -1038,17 +995,17 @@ nl_connect (struct nl_sock *sk, int protocol)
 	err = getsockname (sk->s_fd, (struct sockaddr *) &local,
 	                   &addrlen);
 	if (err < 0) {
-		nlerr = -nl_syserr2nlerr (errno);
+		nmerr = -nm_errno_from_native (errno);
 		goto errout;
 	}
 
 	if (addrlen != sizeof (local)) {
-		nlerr = -NLE_UNSPEC;
+		nmerr = -NME_UNSPEC;
 		goto errout;
 	}
 
 	if (local.nl_family != AF_NETLINK) {
-		nlerr = -NLE_UNSPEC;
+		nmerr = -NME_UNSPEC;
 		goto errout;
 	}
 
@@ -1062,7 +1019,7 @@ errout:
 		close (sk->s_fd);
 		sk->s_fd = -1;
 	}
-	return nlerr;
+	return nmerr;
 }
 
 /*****************************************************************************/
@@ -1098,22 +1055,22 @@ nl_wait_for_ack (struct nl_sock *sk,
 do { \
 	const struct nl_cb *_cb = (cb); \
 	\
-	if (_cb->type##_cb) { \
+	if (_cb && _cb->type##_cb) { \
 		/* the returned value here must be either a negative
 		 * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \
-		nlerr = _cb->type##_cb ((msg), _cb->type##_arg); \
-		switch (nlerr) { \
+		nmerr = _cb->type##_cb ((msg), _cb->type##_arg); \
+		switch (nmerr) { \
 		case NL_OK: \
-			nlerr = 0; \
+			nmerr = 0; \
 			break; \
 		case NL_SKIP: \
 			goto skip; \
 		case NL_STOP: \
 			goto stop; \
 		default: \
-			if (nlerr >= 0) { \
+			if (nmerr >= 0) { \
 				nm_assert_not_reached (); \
-				nlerr = -NLE_BUG; \
+				nmerr = -NME_BUG; \
 			} \
 			goto out; \
 		} \
@@ -1123,14 +1080,15 @@ do { \
 int
 nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb)
 {
-	int n, nlerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	int n, nmerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
 	gs_free unsigned char *buf = NULL;
 	struct nlmsghdr *hdr;
 	struct sockaddr_nl nla = { 0 };
-	gs_free struct ucred *creds = NULL;
+	struct ucred creds;
+	gboolean creds_has;
 
 continue_reading:
-	n = nl_recv (sk, &nla, &buf, &creds);
+	n = nl_recv (sk, &nla, &buf, &creds, &creds_has);
 	if (n <= 0)
 		return n;
 
@@ -1142,15 +1100,14 @@ continue_reading:
 
 		nlmsg_set_proto (msg, sk->s_proto);
 		nlmsg_set_src (msg, &nla);
-		if (creds)
-			nlmsg_set_creds (msg, creds);
+		nlmsg_set_creds (msg, creds_has ? &creds : NULL);
 
 		nrecv++;
 
 		/* Only do sequence checking if auto-ack mode is enabled */
 		if (! (sk->s_flags & NL_NO_AUTO_ACK)) {
 			if (hdr->nlmsg_seq != sk->s_seq_expect) {
-				nlerr = -NLE_SEQ_MISMATCH;
+				nmerr = -NME_NL_SEQ_MISMATCH;
 				goto out;
 			}
 		}
@@ -1196,7 +1153,7 @@ continue_reading:
 		 * quit parsing. The user may overrule this action by retuning
 		 * NL_SKIP or NL_PROCEED (dangerous) */
 		else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
-			nlerr = -NLE_MSG_OVERFLOW;
+			nmerr = -NME_NL_MSG_OVERFLOW;
 			goto out;
 		}
 
@@ -1209,27 +1166,27 @@ continue_reading:
 				 * is to stop parsing. The user may overrule
 				 * this action by returning NL_SKIP or
 				 * NL_PROCEED (dangerous) */
-				nlerr = -NLE_MSG_TRUNC;
+				nmerr = -NME_NL_MSG_TRUNC;
 				goto out;
 			}
 			if (e->error) {
 				/* Error message reported back from kernel. */
-				if (cb->err_cb) {
+				if (cb && cb->err_cb) {
 					/* the returned value here must be either a negative
 					 * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */
-					nlerr = cb->err_cb (&nla, e,
+					nmerr = cb->err_cb (&nla, e,
 					                    cb->err_arg);
-					if (nlerr < 0)
+					if (nmerr < 0)
 						goto out;
-					else if (nlerr == NL_SKIP)
+					else if (nmerr == NL_SKIP)
 						goto skip;
-					else if (nlerr == NL_STOP) {
-						nlerr = -nl_syserr2nlerr (e->error);
+					else if (nmerr == NL_STOP) {
+						nmerr = -nm_errno_from_native (e->error);
 						goto out;
 					}
-					nm_assert (nlerr == NL_OK);
+					nm_assert (nmerr == NL_OK);
 				} else {
-					nlerr = -nl_syserr2nlerr (e->error);
+					nmerr = -nm_errno_from_native (e->error);
 					goto out;
 				}
 			} else
@@ -1241,27 +1198,26 @@ continue_reading:
 			NL_CB_CALL (cb, valid, msg);
 		}
 skip:
-		nlerr = 0;
+		nmerr = 0;
 		hdr = nlmsg_next (hdr, &n);
 	}
 
 	if (multipart) {
 		/* Multipart message not yet complete, continue reading */
-		nm_clear_g_free (&creds);
 		nm_clear_g_free (&buf);
 
 		goto continue_reading;
 	}
 
 stop:
-	nlerr = 0;
+	nmerr = 0;
 
 out:
 	if (interrupted)
-		nlerr = -NLE_DUMP_INTR;
+		nmerr = -NME_NL_DUMP_INTR;
 
-	nm_assert (nlerr <= 0);
-	return nlerr ?: nrecv;
+	nm_assert (nmerr <= 0);
+	return nmerr ?: nrecv;
 }
 
 int
@@ -1270,13 +1226,13 @@ nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
 	int ret;
 
 	if (sk->s_fd < 0)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	nlmsg_set_src (msg, &sk->s_local);
 
 	ret = sendmsg (sk->s_fd, hdr, 0);
 	if (ret < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	return ret;
 }
@@ -1359,12 +1315,14 @@ int nl_send_auto (struct nl_sock *sk, struct nl_msg *msg)
 }
 
 int
-nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
-         unsigned char **buf, struct ucred **creds)
+nl_recv (struct nl_sock *sk,
+         struct sockaddr_nl *nla,
+         unsigned char **buf,
+         struct ucred *out_creds,
+         gboolean *out_creds_has)
 {
 	ssize_t n;
 	int flags = 0;
-	static int page_size = 0;
 	struct iovec iov;
 	struct msghdr msg = {
 		.msg_name = (void *) nla,
@@ -1372,25 +1330,24 @@ nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
 		.msg_iov = &iov,
 		.msg_iovlen = 1,
 	};
-	gs_free struct ucred* tmpcreds = NULL;
+	struct ucred tmpcreds;
+	gboolean tmpcreds_has = FALSE;
 	int retval;
 
 	nm_assert (nla);
 	nm_assert (buf && !*buf);
-	nm_assert (!creds || !*creds);
+	nm_assert (!out_creds_has == !out_creds);
 
 	if (   (sk->s_flags & NL_MSG_PEEK)
 	    || (   !(sk->s_flags & NL_MSG_PEEK_EXPLICIT)
 	        && sk->s_bufsize == 0))
 		flags |= MSG_PEEK | MSG_TRUNC;
 
-	if (page_size == 0)
-		page_size = getpagesize () * 4;
-
-	iov.iov_len = sk->s_bufsize ?: page_size;
+	iov.iov_len =    sk->s_bufsize
+	              ?: (((size_t) nm_utils_getpagesize ()) * 4u);
 	iov.iov_base = g_malloc (iov.iov_len);
 
-	if (   creds
+	if (   out_creds
 	    && (sk->s_flags & NL_SOCK_PASSCRED)) {
 		msg.msg_controllen = CMSG_SPACE (sizeof (struct ucred));
 		msg.msg_control = g_malloc (msg.msg_controllen);
@@ -1407,13 +1364,13 @@ retry:
 		if (errno == EINTR)
 			goto retry;
 
-		retval = -nl_syserr2nlerr (errno);
+		retval = -nm_errno_from_native (errno);
 		goto abort;
 	}
 
 	if (msg.msg_flags & MSG_CTRUNC) {
 		if (msg.msg_controllen == 0) {
-			retval = -NLE_MSG_TRUNC;
+			retval = -NME_NL_MSG_TRUNC;
 			goto abort;
 		}
 
@@ -1426,7 +1383,7 @@ retry:
 	    || (msg.msg_flags & MSG_TRUNC)) {
 		/* respond with error to an incomplete message */
 		if (flags == 0) {
-			retval = -NLE_MSG_TRUNC;
+			retval = -NME_NL_MSG_TRUNC;
 			goto abort;
 		}
 
@@ -1446,11 +1403,11 @@ retry:
 	}
 
 	if (msg.msg_namelen != sizeof (struct sockaddr_nl)) {
-		retval =  -NLE_UNSPEC;
+		retval =  -NME_UNSPEC;
 		goto abort;
 	}
 
-	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+	if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
 		struct cmsghdr *cmsg;
 
 		for (cmsg = CMSG_FIRSTHDR (&msg); cmsg; cmsg = CMSG_NXTHDR (&msg, cmsg)) {
@@ -1458,7 +1415,8 @@ retry:
 				continue;
 			if (cmsg->cmsg_type != SCM_CREDENTIALS)
 				continue;
-			tmpcreds = nm_memdup (CMSG_DATA (cmsg), sizeof (*tmpcreds));
+			memcpy (&tmpcreds, CMSG_DATA (cmsg), sizeof (tmpcreds));
+			tmpcreds_has = TRUE;
 			break;
 		}
 	}
@@ -1474,6 +1432,8 @@ abort:
 	}
 
 	*buf = iov.iov_base;
-	NM_SET_OUT (creds, g_steal_pointer (&tmpcreds));
+	if (out_creds && tmpcreds_has)
+		*out_creds = tmpcreds;
+	NM_SET_OUT (out_creds_has, tmpcreds_has);
 	return retval;
 }
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index d5df7ab9..84bbe27b 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -26,20 +26,6 @@
 #include <linux/genetlink.h>
 
 /*****************************************************************************/
-#define _NLE_BASE               100000
-#define NLE_UNSPEC              (_NLE_BASE +  0)
-#define NLE_BUG                 (_NLE_BASE +  1)
-#define NLE_NATIVE_ERRNO        (_NLE_BASE +  2)
-#define NLE_SEQ_MISMATCH        (_NLE_BASE +  3)
-#define NLE_MSG_TRUNC           (_NLE_BASE +  4)
-#define NLE_MSG_TOOSHORT        (_NLE_BASE +  5)
-#define NLE_DUMP_INTR           (_NLE_BASE +  6)
-#define NLE_ATTRSIZE            (_NLE_BASE +  7)
-#define NLE_BAD_SOCK            (_NLE_BASE +  8)
-#define NLE_NOADDR              (_NLE_BASE +  9)
-#define NLE_MSG_OVERFLOW        (_NLE_BASE + 10)
-
-#define _NLE_BASE_END           (_NLE_BASE + 11)
 
 #define NLMSGERR_ATTR_UNUSED            0
 #define NLMSGERR_ATTR_MSG               1
@@ -51,50 +37,6 @@
 #define NLM_F_ACK_TLVS                  0x200
 #endif
 
-static inline int
-nl_errno (int nlerr)
-{
-	/* Normalizes an netlink error to be positive. Various API returns negative
-	 * error codes, and this function converts the negative value to its
-	 * positive.
-	 *
-	 * It's very similar to nm_errno(), but not exactly. The difference is that
-	 * nm_errno() is for plain errno, while nl_errno() is for netlink error numbers.
-	 * Yes, netlink error number are ~almost~ the same as errno, except that a particular
-	 * range (_NLE_BASE, _NLE_BASE_END) is reserved. The difference between the two
-	 * functions is only how G_MININT is mapped.
-	 *
-	 * See also nl_syserr2nlerr() below. */
-	return nlerr >= 0
-	       ? nlerr
-	       : ((nlerr == G_MININT) ? NLE_BUG : -nlerr);
-}
-
-static inline int
-nl_syserr2nlerr (int errsv)
-{
-	/* this maps a native errno to a (always non-negative) netlink error number.
-	 *
-	 * Note that netlink error numbers are embedded into the range of regular
-	 * errno. The only difference is, that netlink error numbers reserve a
-	 * range (_NLE_BASE, _NLE_BASE_END) for their own purpose.
-	 *
-	 * That means, converting an errno to netlink error number means in
-	 * most cases just returning itself (negative values are normalized
-	 * to be positive). Only values G_MININT and [_NLE_BASE, _NLE_BASE_END]
-	 * are coerced to the special value NLE_NATIVE_ERRNO, as they cannot
-	 * otherwise be represented in netlink error number domain. */
-	if (errsv == G_MININT)
-		return NLE_NATIVE_ERRNO;
-	if (errsv < 0)
-		errsv = -errsv;
-	return (errsv >= _NLE_BASE && errsv < _NLE_BASE_END)
-	       ? NLE_NATIVE_ERRNO
-	       : errsv;
-}
-
-const char *nl_geterror (int nlerr);
-
 /*****************************************************************************/
 
 /* Basic attribute data types */
@@ -245,6 +187,24 @@ nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
 	return nla_put(msg, attrtype, strlen(str) + 1, str);
 }
 
+static inline int
+nla_put_uint8 (struct nl_msg *msg, int attrtype, uint8_t val)
+{
+	return nla_put (msg, attrtype, sizeof (val), &val);
+}
+
+static inline int
+nla_put_uint16 (struct nl_msg *msg, int attrtype, uint16_t val)
+{
+	return nla_put (msg, attrtype, sizeof (val), &val);
+}
+
+static inline int
+nla_put_uint32 (struct nl_msg *msg, int attrtype, uint32_t val)
+{
+	return nla_put (msg, attrtype, sizeof (val), &val);
+}
+
 #define NLA_PUT(msg, attrtype, attrlen, data) \
 	do { \
 		if (nla_put(msg, attrtype, attrlen, data) < 0) \
@@ -474,8 +434,11 @@ int nl_socket_add_memberships (struct nl_sock *sk, int group, ...);
 
 int nl_connect (struct nl_sock *sk, int protocol);
 
-int nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
-             unsigned char **buf, struct ucred **creds);
+int nl_recv (struct nl_sock *sk,
+             struct sockaddr_nl *nla,
+             unsigned char **buf,
+             struct ucred *out_creds,
+             gboolean *out_creds_has);
 
 int nl_send (struct nl_sock *sk, struct nl_msg *msg);
 
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 216b1547..772d4667 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -29,6 +29,7 @@
 #include <linux/ethtool.h>
 #include <linux/sockios.h>
 #include <linux/mii.h>
+#include <linux/if.h>
 #include <linux/version.h>
 #include <linux/rtnetlink.h>
 #include <fcntl.h>
@@ -664,7 +665,7 @@ nmp_utils_ethtool_set_features (int ifindex,
 	              "set-features",
 	              success
 	                ? "successfully setting features"
-	                : "at least some of the features were not successfuly set");
+	                : "at least some of the features were not successfully set");
 	return success;
 }
 
@@ -1247,7 +1248,7 @@ nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize
  * @ifindex: the ifindex for which to open "/sys/class/net/%s"
  * @ifname_guess: (allow-none): optional argument, if present used as initial
  *   guess as the current name for @ifindex. If guessed right,
- *   it saves an addtional if_indextoname() call.
+ *   it saves an additional if_indextoname() call.
  * @out_ifname: (allow-none): if present, must be at least IFNAMSIZ
  *   characters. On success, this will contain the actual ifname
  *   found while opening the directory.
@@ -1276,7 +1277,6 @@ nmp_utils_sysctl_open_netdir (int ifindex,
 	for (try_count = 0; try_count < 10; try_count++, ifname = NULL) {
 		nm_auto_close int fd_dir = -1;
 		nm_auto_close int fd_ifindex = -1;
-		int fd;
 
 		if (!ifname) {
 			ifname = nmp_utils_if_indextoname (ifindex, ifname_buf);
@@ -1309,15 +1309,13 @@ nmp_utils_sysctl_open_netdir (int ifindex,
 			continue;
 		fd_buf[nn] = '\0';
 
-		if (ifindex != _nm_utils_ascii_str_to_int64 (fd_buf, 10, 1, G_MAXINT, -1))
+		if (ifindex != (int) _nm_utils_ascii_str_to_int64 (fd_buf, 10, 1, G_MAXINT, -1))
 			continue;
 
 		if (out_ifname)
 			strcpy (out_ifname, ifname);
 
-		fd = fd_dir;
-		fd_dir = -1;
-		return fd;
+		return nm_steal_fd (&fd_dir);
 	}
 
 	return -1;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index c73631a9..4bb31b17 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -31,6 +31,7 @@
 #include <netdb.h>
 #include <string.h>
 #include <linux/ip.h>
+#include <linux/if.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/rtnetlink.h>
@@ -40,6 +41,7 @@
 #include "nm-core-internal.h"
 #include "nm-utils/nm-dedup-multi.h"
 #include "nm-utils/nm-udev-utils.h"
+#include "nm-utils/nm-errno.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
@@ -57,22 +59,48 @@ G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OF
 
 #define _NMLOG_DOMAIN           LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME      "platform"
+
+
+#define NMLOG_COMMON(level, name, ...) \
+        char __prefix[32]; \
+        const char *__p_prefix = _NMLOG_PREFIX_NAME; \
+        const NMPlatform *const __self = (self); \
+        const char *__name = name; \
+        \
+        if (__self && NM_PLATFORM_GET_PRIVATE (__self)->log_with_ptr) { \
+            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
+            __p_prefix = __prefix; \
+        } \
+        _nm_log (__level, _NMLOG_DOMAIN, 0, __name, NULL, \
+                 "%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                 __p_prefix, \
+                 NM_PRINT_FMT_QUOTED (__name, "(", __name, ") ", "") \
+                 _NM_UTILS_MACRO_REST (__VA_ARGS__));
+
 #define _NMLOG(level, ...) \
     G_STMT_START { \
         const NMLogLevel __level = (level); \
         \
         if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
-            char __prefix[32]; \
-            const char *__p_prefix = _NMLOG_PREFIX_NAME; \
-            const NMPlatform *const __self = (self); \
-            \
-            if (__self && NM_PLATFORM_GET_PRIVATE (__self)->log_with_ptr) { \
-                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-                __p_prefix = __prefix; \
-            } \
-            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
-                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+            NMLOG_COMMON(level, NULL, __VA_ARGS__);  \
+        } \
+    } G_STMT_END
+
+#define _NMLOG2(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            NMLOG_COMMON(level, name, __VA_ARGS__);  \
+        } \
+    } G_STMT_END
+
+#define _NMLOG3(level, ...)  \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            NMLOG_COMMON(level, ifindex > 0 ? nm_platform_link_get_name (self, ifindex) : NULL, __VA_ARGS__);  \
         } \
     } G_STMT_END
 
@@ -228,58 +256,6 @@ nm_platform_get_multi_idx (NMPlatform *self)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nm_platform_error_to_string, NMPlatformError,
-	NM_UTILS_LOOKUP_DEFAULT (NULL),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_SUCCESS,     "success"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_BUG,         "bug"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_UNSPECIFIED, "unspecified"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_FOUND,   "not-found"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_EXISTS,      "exists"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_WRONG_TYPE,  "wrong-type"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_SLAVE,   "not-slave"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NO_FIRMWARE, "no-firmware"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_OPNOTSUPP,   "not-supported"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NETLINK,     "netlink"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_CANT_SET_MTU, "cant-set-mtu"),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_PLATFORM_ERROR_MININT),
-);
-
-/**
- * nm_platform_error_to_string:
- * @error_code: the error code to stringify.
- * @buf: (allow-none): buffer
- * @buf_len: size of buffer
- *
- * Returns: A string representation of the error.
- * For negative numbers, this function interprets
- * the code as -errno.
- * For invalid (positive) numbers it returns NULL.
- */
-const char *
-nm_platform_error_to_string (NMPlatformError error_code, char *buf, gsize buf_len)
-{
-	const char *s;
-
-	if (error_code < 0) {
-		int errsv = -((int) error_code);
-
-		nm_utils_to_string_buffer_init (&buf, &buf_len);
-		g_snprintf (buf, buf_len, "%s (%d)", g_strerror (errsv), errsv);
-	} else {
-		s = _nm_platform_error_to_string (error_code);
-		if (s) {
-			if (!buf)
-				return s;
-			g_strlcpy (buf, s, buf_len);
-		} else {
-			nm_utils_to_string_buffer_init (&buf, &buf_len);
-			g_snprintf (buf, buf_len, "(%d)", (int) error_code);
-		}
-	}
-
-	return buf;
-}
-
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_ADD,     "add"),
@@ -449,7 +425,9 @@ nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const c
 }
 
 gboolean
-nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value)
+nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
+                                                    const char *iface,
+                                                    int value)
 {
 	const char *path;
 	gint64 cur;
@@ -543,7 +521,14 @@ nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, c
  * (inclusive) or @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)
+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 *value = NULL;
 	gint32 ret;
@@ -570,6 +555,81 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int di
 
 /*****************************************************************************/
 
+char *
+nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+                                int addr_family,
+                                const char *ifname,
+                                const char *property)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	return nm_platform_sysctl_get (platform,
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                         buf,
+	                                                                                         ifname,
+	                                                                                         property)));
+}
+
+gint64
+nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+                                            int addr_family,
+                                            const char *ifname,
+                                            const char *property,
+                                            guint base,
+                                            gint64 min,
+                                            gint64 max,
+                                            gint64 fallback)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	return nm_platform_sysctl_get_int_checked (platform,
+	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                                     buf,
+	                                                                                                     ifname,
+	                                                                                                     property)),
+	                                           base,
+	                                           min,
+	                                           max,
+	                                           fallback);
+}
+
+gboolean
+nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+                                int addr_family,
+                                const char *ifname,
+                                const char *property,
+                                const char *value)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	return nm_platform_sysctl_set (platform,
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                         buf,
+	                                                                                         ifname,
+	                                                                                         property)),
+	                               value);
+}
+
+gboolean
+nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+                                      int addr_family,
+                                      const char *ifname,
+                                      const char *property,
+                                      gint64 value)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	char s[64];
+
+	return nm_platform_sysctl_set (platform,
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                         buf,
+	                                                                                         ifname,
+	                                                                                         property)),
+	                               nm_sprintf_buf (s, "%"G_GINT64_FORMAT, value));
+}
+
+/*****************************************************************************/
+
 static int
 _link_get_all_presort (gconstpointer  p_a,
                        gconstpointer  p_b,
@@ -727,6 +787,8 @@ nm_platform_link_get_obj (NMPlatform *self,
 {
 	const NMPObject *obj_cache;
 
+	_CHECK_SELF (self, klass, NULL);
+
 	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (self), ifindex);
 	if (   !obj_cache
 	    || (   visible_only
@@ -752,15 +814,7 @@ nm_platform_link_get_obj (NMPlatform *self,
 const NMPlatformLink *
 nm_platform_link_get (NMPlatform *self, int ifindex)
 {
-	const NMPObject *obj;
-
-	_CHECK_SELF (self, klass, NULL);
-
-	if (ifindex <= 0)
-		return NULL;
-
-	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
-	return NMP_OBJECT_CAST_LINK (obj);
+	return NMP_OBJECT_CAST_LINK (nm_platform_link_get_obj (self, ifindex, TRUE));
 }
 
 /**
@@ -833,7 +887,7 @@ nm_platform_link_get_by_address (NMPlatform *self,
 	return NMP_OBJECT_CAST_LINK (obj);
 }
 
-static NMPlatformError
+static int
 _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, const NMPlatformLink **out_link)
 {
 	const NMPlatformLink *pllink;
@@ -843,20 +897,19 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
 		gboolean wrong_type;
 
 		wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type;
-		_LOGD ("link: skip adding link due to existing interface '%s' of type %s%s%s",
-		       name,
-		       nm_link_type_to_string (pllink->type),
-		       wrong_type ? ", expected " : "",
-		       wrong_type ? nm_link_type_to_string (type) : "");
+		_LOG2D ("link: skip adding link due to existing interface of type %s%s%s",
+		        nm_link_type_to_string (pllink->type),
+		        wrong_type ? ", expected " : "",
+		        wrong_type ? nm_link_type_to_string (type) : "");
 		if (out_link)
 			*out_link = pllink;
 		if (wrong_type)
-			return NM_PLATFORM_ERROR_WRONG_TYPE;
-		return NM_PLATFORM_ERROR_EXISTS;
+			return -NME_PL_WRONG_TYPE;
+		return -NME_PL_EXISTS;
 	}
 	if (out_link)
 		*out_link = NULL;
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
 /**
@@ -870,16 +923,16 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
  * @out_link: on success, the link object
  *
  * Add a software interface.  If the interface already exists and is of type
- * @type, return NM_PLATFORM_ERROR_EXISTS and returns the link
+ * @type, return -NME_PL_EXISTS and returns the link
  * in @out_link.  If the interface already exists and is not of type @type,
- * return NM_PLATFORM_ERROR_WRONG_TYPE.
+ * return -NME_PL_WRONG_TYPE.
  *
  * Any link-changed ADDED signal will be emitted directly, before this
  * function finishes.
  *
- * Returns: the error reason or NM_PLATFORM_ERROR_SUCCESS.
+ * Returns: the negative nm-error on failure.
  */
-static NMPlatformError
+static int
 nm_platform_link_add (NMPlatform *self,
                       const char *name,
                       NMLinkType type,
@@ -888,38 +941,35 @@ nm_platform_link_add (NMPlatform *self,
                       size_t address_len,
                       const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 	char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , -NME_BUG);
+	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, -NME_BUG);
+	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, type, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, type, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': %s (%d)"
-	       "%s%s" /* address */
-	       "%s%s" /* veth peer */
-	       "",
-	       name,
-	       nm_link_type_to_string (type),
-	       (int) type,
-	       address ? ", address: " : "",
-	       address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
-	       veth_peer ? ", veth-peer: " : "",
-	       veth_peer ?: "");
+	_LOG2D ("link: adding link: %s (%d)"
+	        "%s%s" /* address */
+	        "%s%s" /* veth peer */
+	        "",
+	        nm_link_type_to_string (type),
+	        (int) type,
+	        address ? ", address: " : "",
+	        address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
+	        veth_peer ? ", veth-peer: " : "",
+	        veth_peer ?: "");
 
-	if (!klass->link_add (self, name, type, veth_peer, address, address_len, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return klass->link_add (self, name, type, veth_peer, address, address_len, out_link);
 }
 
-NMPlatformError
+int
 nm_platform_link_veth_add (NMPlatform *self,
                             const char *name,
                             const char *peer,
@@ -936,7 +986,7 @@ nm_platform_link_veth_add (NMPlatform *self,
  *
  * Create a software ethernet-like interface
  */
-NMPlatformError
+int
 nm_platform_link_dummy_add (NMPlatform *self,
                             const char *name,
                             const NMPlatformLink **out_link)
@@ -952,15 +1002,11 @@ nm_platform_link_dummy_add (NMPlatform *self,
 gboolean
 nm_platform_link_delete (NMPlatform *self, int ifindex)
 {
-	const NMPlatformLink *pllink;
-
 	_CHECK_SELF (self, klass, FALSE);
 
-	pllink = nm_platform_link_get (self, ifindex);
-	if (!pllink)
-		return FALSE;
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: deleting '%s' (%d)", pllink->name, ifindex);
+	_LOG3D ("link: deleting");
 	return klass->link_delete (self, ifindex);
 }
 
@@ -975,18 +1021,12 @@ nm_platform_link_delete (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
 {
-	const NMPlatformLink *pllink;
-
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (netns_fd > 0, FALSE);
 
-	pllink = nm_platform_link_get (self, ifindex);
-	if (!pllink)
-		return FALSE;
-
-	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
+	_LOG3D ("link: move link to network namespace with fd %d", netns_fd);
 	return klass->link_set_netns (self, ifindex, netns_fd);
 }
 
@@ -996,7 +1036,7 @@ nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
  * @name: Interface name
  *
  * Returns: The interface index corresponding to the given interface name
- * or 0. Inteface name is owned by #NMPlatform, don't free it.
+ * or 0. Interface name is owned by #NMPlatform, don't free it.
  */
 int
 nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
@@ -1036,8 +1076,6 @@ nm_platform_link_get_name (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, NULL);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->name : NULL;
 }
@@ -1055,8 +1093,6 @@ nm_platform_link_get_type (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, NM_LINK_TYPE_NONE);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->type : NM_LINK_TYPE_NONE;
 }
@@ -1075,16 +1111,13 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 {
 	const NMPObject *obj;
 
-	_CHECK_SELF (self, klass, NULL);
-
 	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
-
 	if (!obj)
 		return NULL;
 
 	if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
 		/* We could detect the @link_type. In this case the function returns
-		 * our internel module names, which differs from rtnl_link_get_type():
+		 * our internal module names, which differs from rtnl_link_get_type():
 		 *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
 		 *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
 		 * Note that this functions is only used by NMDeviceGeneric to
@@ -1199,11 +1232,6 @@ nm_platform_link_get_ifi_flags (NMPlatform *self,
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, -EINVAL);
-
-	if (ifindex <= 0)
-		return -EINVAL;
-
 	/* include invisible links (only in netlink, not udev). */
 	pllink = NMP_OBJECT_CAST_LINK (nm_platform_link_get_obj (self, ifindex, FALSE));
 	if (!pllink)
@@ -1242,8 +1270,6 @@ nm_platform_link_is_connected (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, FALSE);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->connected : FALSE;
 }
@@ -1309,10 +1335,6 @@ nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
 {
 	const NMPObject *obj_cache;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex >= 0, NULL);
-
 	obj_cache = nm_platform_link_get_obj (self, ifindex, FALSE);
 	return obj_cache ? obj_cache->_link.udev.device : NULL;
 }
@@ -1333,10 +1355,6 @@ nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex >= 0, FALSE);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	if (pllink && pllink->inet6_addr_gen_mode_inv)
 		return _nm_platform_uint8_inv (pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE;
@@ -1352,14 +1370,14 @@ nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
  * platform or OS doesn't support changing the IPv6LL address mode, this call
  * will fail and return %FALSE.
  *
- * Returns: %NM_PLATFORM_ERROR_SUCCESS if the operation was successful or an error code otherwise.
+ * Returns: the negative nm-error on failure.
  */
-NMPlatformError
+int
 nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled)
 {
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (ifindex > 0, -NME_BUG);
 
 	return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
 }
@@ -1372,21 +1390,19 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
  *
  * Set interface MAC address.
  */
-NMPlatformError
+int
 nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
 {
 	gs_free char *mac = NULL;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (length > 0, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (ifindex > 0, -NME_BUG);
+	g_return_val_if_fail (address, -NME_BUG);
+	g_return_val_if_fail (length > 0, -NME_BUG);
 
-	_LOGD ("link: setting %s (%d) hardware address to %s",
-	       nm_strquote_a (20, nm_platform_link_get_name (self, ifindex)),
-	       ifindex,
-	       (mac = nm_utils_hwaddr_ntoa (address, length)));
+	_LOG3D ("link: setting hardware address to %s",
+	        (mac = nm_utils_hwaddr_ntoa (address, length)));
 	return klass->link_set_address (self, ifindex, address, length);
 }
 
@@ -1404,12 +1420,7 @@ nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
 	pllink = nm_platform_link_get (self, ifindex);
-
 	if (   !pllink
 	    || pllink->addr.len <= 0) {
 		NM_SET_OUT (length, 0);
@@ -1488,25 +1499,20 @@ nm_platform_link_supports_sriov (NMPlatform *self, int ifindex)
  * @self: platform instance
  * @ifindex: the index of the interface to change
  * @num_vfs: the number of VFs to create
- * @autoprobe: -1 to keep the current autoprobe-drivers value,
- *   or {0,1} to set a new value
+ * @autoprobe: the new autoprobe-drivers value (pass
+ *     %NM_TERNARY_DEFAULT to keep current value)
  */
 gboolean
 nm_platform_link_set_sriov_params (NMPlatform *self,
                                    int ifindex,
                                    guint num_vfs,
-                                   int autoprobe)
+                                   NMTernary autoprobe)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (NM_IN_SET (autoprobe, -1, 0, 1), FALSE);
 
-	_LOGD ("link: setting %u total VFs and autoprobe %d for %s (%d)",
-	       num_vfs,
-	       autoprobe,
-	       nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)),
-	       ifindex);
+	_LOG3D ("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe);
 	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
 }
 
@@ -1518,14 +1524,11 @@ nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformV
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting VFs for \"%s\" (%d):",
-	       nm_platform_link_get_name (self, ifindex),
-	       ifindex);
-
+	_LOG3D ("link: setting VFs");
 	for (i = 0; vfs[i]; i++) {
 		const NMPlatformVF *vf = vfs[i];
 
-		_LOGD ("link:   VF %s", nm_platform_vf_to_string (vf, NULL, 0));
+		_LOG3D ("link:   VF %s", nm_platform_vf_to_string (vf, NULL, 0));
 	}
 
 	return klass->link_set_sriov_vfs (self, ifindex, vfs);
@@ -1546,7 +1549,7 @@ nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmwar
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting up %s (%d)", nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)), ifindex);
+	_LOG3D ("link: setting up");
 	return klass->link_set_up (self, ifindex, out_no_firmware);
 }
 
@@ -1564,7 +1567,7 @@ nm_platform_link_set_down (NMPlatform *self, int ifindex)
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting down %s (%d)", nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)), ifindex);
+	_LOG3D ("link: setting down");
 	return klass->link_set_down (self, ifindex);
 }
 
@@ -1582,7 +1585,7 @@ nm_platform_link_set_arp (NMPlatform *self, int ifindex)
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 
-	_LOGD ("link: setting arp %s (%d)", nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)), ifindex);
+	_LOG3D ("link: setting arp");
 	return klass->link_set_arp (self, ifindex);
 }
 
@@ -1600,7 +1603,7 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 
-	_LOGD ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
+	_LOG3D ("link: setting noarp");
 	return klass->link_set_noarp (self, ifindex);
 }
 
@@ -1612,7 +1615,7 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
  *
  * Set interface MTU.
  */
-NMPlatformError
+int
 nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 {
 	_CHECK_SELF (self, klass, FALSE);
@@ -1620,7 +1623,7 @@ nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (mtu > 0, FALSE);
 
-	_LOGD ("link: setting '%s' (%d) mtu %"G_GUINT32_FORMAT, nm_platform_link_get_name (self, ifindex), ifindex, mtu);
+	_LOG3D ("link: setting mtu %"G_GUINT32_FORMAT, mtu);
 	return klass->link_set_mtu (self, ifindex, mtu);
 }
 
@@ -1636,8 +1639,6 @@ nm_platform_link_get_mtu (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, 0);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->mtu : 0;
 }
@@ -1658,7 +1659,7 @@ nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name)
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (name, FALSE);
 
-	_LOGD ("link: setting '%s' (%d) name %s", nm_platform_link_get_name (self, ifindex), ifindex, name);
+	_LOG3D ("link: setting name %s", name);
 
 	if (strlen (name) + 1 > IFNAMSIZ)
 		return FALSE;
@@ -1769,47 +1770,43 @@ nm_platform_link_get_driver_info (NMPlatform *self,
  * nm_platform_link_enslave:
  * @self: platform instance
  * @master: Interface index of the master
- * @slave: Interface index of the slave
+ * @ifindex: Interface index of the slave
  *
- * Enslave @slave to @master.
+ * Enslave @ifindex to @master.
  */
 gboolean
-nm_platform_link_enslave (NMPlatform *self, int master, int slave)
+nm_platform_link_enslave (NMPlatform *self, int master, int ifindex)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (master > 0, FALSE);
-	g_return_val_if_fail (slave> 0, FALSE);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: enslaving '%s' (%d) to master '%s' (%d)",
-	       nm_platform_link_get_name (self, slave), slave,
-	       nm_platform_link_get_name (self, master), master);
-	return klass->link_enslave (self, master, slave);
+	_LOG3D ("link: enslaving to master '%s'", nm_platform_link_get_name (self, master));
+	return klass->link_enslave (self, master, ifindex);
 }
 
 /**
  * nm_platform_link_release:
  * @self: platform instance
  * @master: Interface index of the master
- * @slave: Interface index of the slave
+ * @ifindex: Interface index of the slave
  *
  * Release @slave from @master.
  */
 gboolean
-nm_platform_link_release (NMPlatform *self, int master, int slave)
+nm_platform_link_release (NMPlatform *self, int master, int ifindex)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (master > 0, FALSE);
-	g_return_val_if_fail (slave > 0, FALSE);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (nm_platform_link_get_master (self, slave) != master)
+	if (nm_platform_link_get_master (self, ifindex) != master)
 		return FALSE;
 
-	_LOGD ("link: releasing '%s' (%d) from master '%s' (%d)",
-	       nm_platform_link_get_name (self, slave), slave,
-	       nm_platform_link_get_name (self, master), master);
-	return klass->link_release (self, master, slave);
+	_LOG3D ("link: releasing from master '%s'", nm_platform_link_get_name (self, master));
+	return klass->link_release (self, master, ifindex);
 }
 
 /**
@@ -1824,10 +1821,6 @@ nm_platform_link_get_master (NMPlatform *self, int slave)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, 0);
-
-	g_return_val_if_fail (slave >= 0, FALSE);
-
 	pllink = nm_platform_link_get (self, slave);
 	return pllink ? pllink->master : 0;
 }
@@ -1865,7 +1858,7 @@ nm_platform_link_can_assume (NMPlatform *self, int ifindex)
  * Returns: the internal link lnk object. The returned object
  * is owned by the platform cache and must not be modified. Note
  * however, that the object is guaranteed to be immutable, so
- * you can savely take a reference and keep it for yourself
+ * you can safely take a reference and keep it for yourself
  * (but don't modify it).
  */
 const NMPObject *
@@ -1873,15 +1866,11 @@ nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, c
 {
 	const NMPObject *obj;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	NM_SET_OUT (out_link, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
 	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
-	if (!obj)
+	if (!obj) {
+		NM_SET_OUT (out_link, NULL);
 		return NULL;
+	}
 
 	NM_SET_OUT (out_link, &obj->link);
 
@@ -1996,6 +1985,64 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
 
 /*****************************************************************************/
 
+int
+nm_platform_link_wireguard_add (NMPlatform *self,
+                                const char *name,
+                                const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_WIREGUARD, NULL, NULL, 0, out_link);
+}
+
+int
+nm_platform_link_wireguard_change (NMPlatform *self,
+                                   int ifindex,
+                                   const NMPlatformLnkWireGuard *lnk_wireguard,
+                                   const NMPWireGuardPeer *peers,
+                                   guint peers_len,
+                                   gboolean replace_peers)
+{
+	_CHECK_SELF (self, klass, -NME_BUG);
+
+	nm_assert (klass->link_wireguard_change);
+
+	if (_LOGD_ENABLED ()) {
+		char buf_lnk[256];
+		char buf_peers[512];
+
+		buf_peers[0] = '\0';
+		if (peers_len > 0) {
+			char *b = buf_peers;
+			gsize len = sizeof (buf_peers);
+			guint i;
+
+			nm_utils_strbuf_append_str (&b, &len, " { ");
+			for (i = 0; i < peers_len; i++) {
+				nm_utils_strbuf_append_str (&b, &len, " { ");
+				nm_platform_wireguard_peer_to_string (&peers[i], b, len);
+				nm_utils_strbuf_seek_end (&b, &len);
+				nm_utils_strbuf_append_str (&b, &len, " } ");
+			}
+			nm_utils_strbuf_append_str (&b, &len, "}");
+		}
+
+		_LOG3D ("link: change wireguard ifindex %d, %s, %u peers%s%s",
+		        ifindex,
+		        nm_platform_lnk_wireguard_to_string (lnk_wireguard, buf_lnk, sizeof (buf_lnk)),
+		        peers_len,
+		        buf_peers,
+		        replace_peers ? " (replace-peers)" : " (update-peers)");
+	}
+
+	return klass->link_wireguard_change (self,
+	                                     ifindex,
+	                                     lnk_wireguard,
+	                                     peers,
+	                                     peers_len,
+	                                     replace_peers);
+}
+
+/*****************************************************************************/
+
 /**
  * nm_platform_link_bridge_add:
  * @self: platform instance
@@ -2006,7 +2053,7 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
  *
  * Create a software bridge.
  */
-NMPlatformError
+int
 nm_platform_link_bridge_add (NMPlatform *self,
                              const char *name,
                              const void *address,
@@ -2024,7 +2071,7 @@ nm_platform_link_bridge_add (NMPlatform *self,
  *
  * Create a software bonding device.
  */
-NMPlatformError
+int
 nm_platform_link_bond_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
@@ -2040,7 +2087,7 @@ nm_platform_link_bond_add (NMPlatform *self,
  *
  * Create a software teaming device.
  */
-NMPlatformError
+int
 nm_platform_link_team_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
@@ -2058,7 +2105,7 @@ nm_platform_link_team_add (NMPlatform *self,
  *
  * Create a software VLAN device.
  */
-NMPlatformError
+int
 nm_platform_link_vlan_add (NMPlatform *self,
                            const char *name,
                            int parent,
@@ -2066,24 +2113,24 @@ nm_platform_link_vlan_add (NMPlatform *self,
                            guint32 vlanflags,
                            const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (vlanid >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (parent >= 0, -NME_BUG);
+	g_return_val_if_fail (vlanid >= 0, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': vlan parent %d vlanid %d vlanflags %x",
-	       name, parent, vlanid, vlanflags);
+	_LOG2D ("link: adding link vlan parent %d vlanid %d vlanflags %x",
+	        parent, vlanid, vlanflags);
 
 	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2095,28 +2142,27 @@ nm_platform_link_vlan_add (NMPlatform *self,
  *
  * Create a VXLAN device.
  */
-NMPlatformError
+int
 nm_platform_link_vxlan_add (NMPlatform *self,
                             const char *name,
                             const NMPlatformLnkVxlan *props,
                             const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': %s",
-	       name, nm_platform_lnk_vxlan_to_string (props, NULL, 0));
+	_LOG2D ("link: adding link %s", nm_platform_lnk_vxlan_to_string (props, NULL, 0));
 
 	if (!klass->link_vxlan_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2138,7 +2184,7 @@ nm_platform_link_vxlan_add (NMPlatform *self,
  *
  * Create a TUN or TAP interface.
  */
-NMPlatformError
+int
 nm_platform_link_tun_add (NMPlatform *self,
                           const char *name,
                           const NMPlatformLnkTun *props,
@@ -2146,30 +2192,29 @@ nm_platform_link_tun_add (NMPlatform *self,
                           int *out_fd)
 {
 	char b[255];
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (NM_IN_SET (props->type, IFF_TUN, IFF_TAP), NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (NM_IN_SET (props->type, IFF_TUN, IFF_TAP), -NME_BUG);
 
 	/* creating a non-persistant device requires that the caller handles
 	 * the file descriptor. */
-	g_return_val_if_fail (props->persist || out_fd, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props->persist || out_fd, -NME_BUG);
 
 	NM_SET_OUT (out_fd, -1);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_TUN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_TUN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': %s",
-	       name, nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
+	_LOG2D ("link: adding link %s", nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
 
 	if (!klass->link_tun_add (self, name, props, out_link, out_fd))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2181,39 +2226,38 @@ nm_platform_link_tun_add (NMPlatform *self,
  *
  * Create a 6LoWPAN interface.
  */
-NMPlatformError
+int
 nm_platform_link_6lowpan_add (NMPlatform *self,
                               const char *name,
                               int parent,
                               const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': 6lowpan parent %u", name, parent);
+	_LOG2D ("adding link 6lowpan parent %u", parent);
 
 	if (!klass->link_6lowpan_add (self, name, parent, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 gboolean
 nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent)
 {
 	const NMPlatformLink *plink;
-	_CHECK_SELF (self, klass, FALSE);
 
 	plink = nm_platform_link_get (self, ifindex);
-
 	if (!plink)
 		return FALSE;
+
 	if (plink->type != NM_LINK_TYPE_6LOWPAN)
 		return FALSE;
 
@@ -2252,9 +2296,10 @@ link_set_option (NMPlatform *self, int ifindex, const char *category, const char
 	if (dirfd < 0)
 		return FALSE;
 
-	path = nm_sprintf_bufa (strlen (category) + strlen (option) + 2,
-	                        "%s/%s",
-	                        category, option);
+	path = nm_sprintf_buf_unsafe_a (strlen (category) + strlen (option) + 2,
+	                                "%s/%s",
+	                                category,
+	                                option);
 	return nm_platform_sysctl_set (self, NMP_SYSCTL_PATHID_NETDIR_unsafe (dirfd, ifname_verified, path), value);
 }
 
@@ -2272,9 +2317,9 @@ link_get_option (NMPlatform *self, int ifindex, const char *category, const char
 	if (dirfd < 0)
 		return NULL;
 
-	path = nm_sprintf_bufa (strlen (category) + strlen (option) + 2,
-	                        "%s/%s",
-	                        category, option);
+	path = nm_sprintf_buf_unsafe_a (strlen (category) + strlen (option) + 2,
+	                                "%s/%s",
+	                                category, option);
 	return nm_platform_sysctl_get (self, NMP_SYSCTL_PATHID_NETDIR_unsafe (dirfd, ifname_verified, path));
 }
 
@@ -2407,7 +2452,7 @@ nm_platform_link_vlan_change (NMPlatform *self,
 				nm_utils_strbuf_append_str (&b, &len, " (reset-all)");
 		}
 
-		_LOGD ("link: change vlan %d:%s", ifindex, buf);
+		_LOG3D ("link: change vlan %s", buf);
 	}
 	return klass->link_vlan_change (self,
 	                                ifindex,
@@ -2452,81 +2497,78 @@ nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, i
  *
  * Create a software GRE device.
  */
-NMPlatformError
+int
 nm_platform_link_gre_add (NMPlatform *self,
                           const char *name,
                           const NMPlatformLnkGre *props,
                           const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_gre_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_gre_to_string (props, NULL, 0));
 
 	if (!klass->link_gre_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
-static NMPlatformError
+static int
 _infiniband_add_add_or_delete (NMPlatform *self,
-                               int parent,
+                               int ifindex,
                                int p_key,
                                gboolean add,
                                const NMPlatformLink **out_link)
 {
 	char name[IFNAMSIZ];
 	const NMPlatformLink *parent_link;
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (p_key >= 0 && p_key <= 0xffff, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (ifindex >= 0, -NME_BUG);
+	g_return_val_if_fail (p_key >= 0 && p_key <= 0xffff, -NME_BUG);
 
 	/* the special keys 0x0000 and 0x8000 are not allowed. */
 	if (NM_IN_SET (p_key, 0, 0x8000))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
+		return -NME_UNSPEC;
 
-	parent_link = nm_platform_link_get (self, parent);
+	parent_link = nm_platform_link_get (self, ifindex);
 	if (!parent_link)
-		return NM_PLATFORM_ERROR_NOT_FOUND;
+		return -NME_PL_NOT_FOUND;
 
 	if (parent_link->type != NM_LINK_TYPE_INFINIBAND)
-		return NM_PLATFORM_ERROR_WRONG_TYPE;
+		return -NME_PL_WRONG_TYPE;
 
 	nm_utils_new_infiniband_name (name, parent_link->name, p_key);
 
 	if (add) {
-		plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_INFINIBAND, out_link);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-			return plerr;
-
-		_LOGD ("link: adding infiniband partition %s for parent '%s' (%d), key %d",
-		       name, parent_link->name, parent, p_key);
-		if (!klass->infiniband_partition_add (self, parent, p_key, out_link))
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+		r = _link_add_check_existing (self, name, NM_LINK_TYPE_INFINIBAND, out_link);
+		if (r < 0)
+			return r;
+
+		_LOG3D ("link: adding infiniband partition %s, key %d", name, p_key);
+		if (!klass->infiniband_partition_add (self, ifindex, p_key, out_link))
+			return -NME_UNSPEC;
 	} else {
-		_LOGD ("link: deleting infiniband partition %s for parent '%s' (%d), key %d",
-		       name, parent_link->name, parent, p_key);
+		_LOG3D ("link: deleting infiniband partition %s, key %d", name, p_key);
 
-		if (!klass->infiniband_partition_delete (self, parent, p_key))
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+		if (!klass->infiniband_partition_delete (self, ifindex, p_key))
+			return -NME_UNSPEC;
 	}
 
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
-NMPlatformError
+int
 nm_platform_link_infiniband_add (NMPlatform *self,
                                  int parent,
                                  int p_key,
@@ -2535,7 +2577,7 @@ nm_platform_link_infiniband_add (NMPlatform *self,
 	return _infiniband_add_add_or_delete (self, parent, p_key, TRUE, out_link);
 }
 
-NMPlatformError
+int
 nm_platform_link_infiniband_delete (NMPlatform *self,
                                    int parent,
                                    int p_key)
@@ -2612,30 +2654,29 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
  *
  * Create an IPv6 tunnel.
  */
-NMPlatformError
+int
 nm_platform_link_ip6tnl_add (NMPlatform *self,
                              const char *name,
                              const NMPlatformLnkIp6Tnl *props,
                              const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (!props->is_gre, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail (!props->is_gre, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
 
 	if (!klass->link_ip6tnl_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2647,35 +2688,34 @@ nm_platform_link_ip6tnl_add (NMPlatform *self,
  *
  * Create an IPv6 GRE/GRETAP tunnel.
  */
-NMPlatformError
+int
 nm_platform_link_ip6gre_add (NMPlatform *self,
                              const char *name,
                              const NMPlatformLnkIp6Tnl *props,
                              const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (props->is_gre, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail (props->is_gre, -NME_BUG);
 
-	plerr = _link_add_check_existing (self,
-	                                  name,
-	                                  props->is_tap
-	                                      ? NM_LINK_TYPE_IP6GRETAP
-	                                      : NM_LINK_TYPE_IP6GRE,
-	                                  out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self,
+	                              name,
+	                              props->is_tap
+	                                  ? NM_LINK_TYPE_IP6GRETAP
+	                                  : NM_LINK_TYPE_IP6GRE,
+	                              out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
 
 	if (!klass->link_ip6gre_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2687,29 +2727,28 @@ nm_platform_link_ip6gre_add (NMPlatform *self,
  *
  * Create an IPIP tunnel.
  */
-NMPlatformError
+int
 nm_platform_link_ipip_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLnkIpIp *props,
                            const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_ipip_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_ipip_to_string (props, NULL, 0));
 
 	if (!klass->link_ipip_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2722,30 +2761,29 @@ nm_platform_link_ipip_add (NMPlatform *self,
  *
  * Create a MACsec interface.
  */
-NMPlatformError
+int
 nm_platform_link_macsec_add (NMPlatform *self,
                              const char *name,
                              int parent,
                              const NMPlatformLnkMacsec *props,
                              const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_macsec_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_macsec_to_string (props, NULL, 0));
 
 	if (!klass->link_macsec_add (self, name, parent, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2757,33 +2795,32 @@ nm_platform_link_macsec_add (NMPlatform *self,
  *
  * Create a MACVLAN or MACVTAP device.
  */
-NMPlatformError
+int
 nm_platform_link_macvlan_add (NMPlatform *self,
                               const char *name,
                               int parent,
                               const NMPlatformLnkMacvlan *props,
                               const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 	NMLinkType type;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
 	type = props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
 
-	plerr = _link_add_check_existing (self, name, type, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, type, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_macvlan_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_macvlan_to_string (props, NULL, 0));
 
 	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2795,29 +2832,28 @@ nm_platform_link_macvlan_add (NMPlatform *self,
  *
  * Create a software SIT device.
  */
-NMPlatformError
+int
 nm_platform_link_sit_add (NMPlatform *self,
                           const char *name,
                           const NMPlatformLnkSit *props,
                           const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_sit_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_sit_to_string (props, NULL, 0));
 
 	if (!klass->link_sit_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 gboolean
@@ -2825,12 +2861,11 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 {
 	const NMPlatformLink *plink;
 	int peer_ifindex;
-	_CHECK_SELF (self, klass, FALSE);
 
 	plink = nm_platform_link_get (self, ifindex);
-
 	if (!plink)
 		return FALSE;
+
 	if (plink->type != NM_LINK_TYPE_VETH)
 		return FALSE;
 
@@ -2888,14 +2923,11 @@ nm_platform_link_tun_get_properties (NMPlatform *self,
 	gint64 group;
 	gint64 flags;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex > 0, FALSE);
-
 	/* we consider also invisible links (those that are not yet in udev). */
 	plobj = nm_platform_link_get_obj (self, ifindex, FALSE);
 	if (!plobj)
 		return FALSE;
+
 	if (NMP_OBJECT_CAST_LINK (plobj)->type != NM_LINK_TYPE_TUN)
 		return FALSE;
 
@@ -3148,6 +3180,16 @@ nm_platform_wpan_set_short_addr (NMPlatform *self, int ifindex, guint16 short_ad
 	return klass->wpan_set_short_addr (self, ifindex, short_addr);
 }
 
+gboolean
+nm_platform_wpan_set_channel (NMPlatform *self, int ifindex, guint8 page, guint8 channel)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_set_channel (self, ifindex, page, channel);
+}
+
 #define TO_STRING_DEV_BUF_SIZE (5+15+1)
 static const char *
 _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
@@ -3387,7 +3429,7 @@ nm_platform_ip4_address_add (NMPlatform *self,
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
-		_LOGD ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
+		_LOG3D ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
 	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags, label);
 }
@@ -3421,7 +3463,7 @@ nm_platform_ip6_address_add (NMPlatform *self,
 		addr.preferred = preferred;
 		addr.n_ifa_flags = flags;
 
-		_LOGD ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr, NULL, 0));
+		_LOG3D ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr, NULL, 0));
 	}
 	return klass->ip6_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
 }
@@ -3430,20 +3472,24 @@ gboolean
 nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
-	char str_peer2[NM_UTILS_INET_ADDRSTRLEN];
-	char str_peer[100];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
+	char str_peer[INET_ADDRSTRLEN + 50];
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen <= 32, FALSE);
 
-	_LOGD ("address: deleting IPv4 address %s/%d, %sifindex %d%s",
-	       nm_utils_inet4_ntop (address, NULL), plen,
-	       peer_address != address
-	           ? nm_sprintf_buf (str_peer, "peer %s, ", nm_utils_inet4_ntop (peer_address, str_peer2)) : "",
-	       ifindex,
-	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	_LOG3D ("address: deleting IPv4 address %s/%d, %s%s",
+	        nm_utils_inet4_ntop (address, b1),
+	        plen,
+	        peer_address != address
+	            ? nm_sprintf_buf (str_peer,
+	                              "peer %s, ",
+	                              nm_utils_inet4_ntop (peer_address, b2))
+	            : "",
+	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
 }
 
@@ -3451,15 +3497,16 @@ gboolean
 nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen <= 128, FALSE);
 
-	_LOGD ("address: deleting IPv6 address %s/%d, ifindex %d%s",
-	       nm_utils_inet6_ntop (&address, NULL), plen, ifindex,
-	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	_LOG3D ("address: deleting IPv6 address %s/%d, %s",
+	        nm_utils_inet6_ntop (&address, sbuf), plen,
+	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip6_address_delete (self, ifindex, address, plen);
 }
 
@@ -3711,7 +3758,7 @@ ip4_addr_subnets_is_secondary (const NMPObject *address,
  *   That means, expired addresses and addresses that could not be added
  *   will be dropped.
  *   Hence, the input argument @known_addresses is also an output argument
- *   telling which addresses were succesfully added.
+ *   telling which addresses were successfully added.
  *   Addresses are removed by unrefing the instance via nmp_object_unref()
  *   and leaving a NULL tombstone.
  *
@@ -3885,7 +3932,7 @@ ip6_address_scope_cmp (gconstpointer a, gconstpointer b)
  *   That means, expired addresses and addresses that could not be added
  *   will be dropped.
  *   Hence, the input argument @known_addresses is also an output argument
- *   telling which addresses were succesfully added.
+ *   telling which addresses were successfully added.
  *   Addresses are removed by unrefing the instance via nmp_object_unref()
  *   and leaving a NULL tombstone.
  * @full_sync: Also remove link-local and temporary addresses.
@@ -4193,7 +4240,6 @@ nm_platform_ip_route_sync (NMPlatform *self,
 	gboolean success = TRUE;
 	char sbuf1[sizeof (_nm_utils_to_string_buffer)];
 	char sbuf2[sizeof (_nm_utils_to_string_buffer)];
-	char sbuf_err[60];
 
 	nm_assert (NM_IS_PLATFORM (self));
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
@@ -4205,7 +4251,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 
 	for (i_type = 0; routes && i_type < 2; i_type++) {
 		for (i = 0; i < routes->len; i++) {
-			NMPlatformError plerr, plerr2;
+			int r, r2;
 			gboolean gateway_route_added = FALSE;
 
 			conf_o = routes->pdata[i];
@@ -4227,8 +4273,8 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				                               (GEqualFunc) nmp_object_id_equal);
 			}
 			if (!g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
-				_LOGD ("route-sync: skip adding duplicate route %s",
-				       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
+				_LOG3D ("route-sync: skip adding duplicate route %s",
+				        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
 				continue;
 			}
 
@@ -4245,7 +4291,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) == 0)
 					continue;
 
-				/* we need to replace the existing route with a (slightly) differnt
+				/* we need to replace the existing route with a (slightly) different
 				 * one. Delete it first. */
 				if (!nm_platform_object_delete (self, plat_o)) {
 					/* ignore error. */
@@ -4253,12 +4299,12 @@ nm_platform_ip_route_sync (NMPlatform *self,
 			}
 
 sync_route_add:
-			plerr = nm_platform_ip_route_add (self,
-			                                    NMP_NLM_FLAG_APPEND
-			                                  | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-			                                  conf_o);
-			if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
-				if (-((int) plerr) == EEXIST) {
+			r = nm_platform_ip_route_add (self,
+			                                NMP_NLM_FLAG_APPEND
+			                              | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
+			                              conf_o);
+			if (r < 0) {
+				if (r == -EEXIST) {
 					/* Don't fail for EEXIST. It's not clear that the existing route
 					 * is identical to the one that we were about to add. However,
 					 * above we should have deleted conflicting (non-identical) routes. */
@@ -4267,92 +4313,92 @@ sync_route_add:
 						                                       NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 						                                       conf_o);
 						if (!plat_entry) {
-							_LOGD ("route-sync: adding route %s failed with EEXIST, however we cannot find such a route",
-							       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
+							_LOG3D ("route-sync: adding route %s failed with EEXIST, however we cannot find such a route",
+							        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
 						} else if (vt->route_cmp (NMP_OBJECT_CAST_IPX_ROUTE (conf_o),
 						                          NMP_OBJECT_CAST_IPX_ROUTE (plat_entry->obj),
 						                          NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) != 0) {
-							_LOGD ("route-sync: adding route %s failed due to existing (different!) route %s",
-							       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-							       nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
+							_LOG3D ("route-sync: adding route %s failed due to existing (different!) route %s",
+							        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+							        nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
 						}
 					}
 				} else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
-					_LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s",
+					_LOG3D ("route-sync: ignore failure to add IPv%c route: %s: %s",
 					       vt->is_ip4 ? '4' : '6',
 					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
-				} else if (   -((int) plerr) == EINVAL
+					       nm_strerror (r));
+				} else if (   r == -EINVAL
 				           && out_temporary_not_available
 				           && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) {
-					_LOGD ("route-sync: ignore failure to add IPv6 route with tentative IPv6 pref-src: %s: %s",
-					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					_LOG3D ("route-sync: ignore failure to add IPv6 route with tentative IPv6 pref-src: %s: %s",
+					        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					        nm_strerror (r));
 					if (!*out_temporary_not_available)
 						*out_temporary_not_available = g_ptr_array_new_full (0, (GDestroyNotify) nmp_object_unref);
 					g_ptr_array_add (*out_temporary_not_available, (gpointer) nmp_object_ref (conf_o));
 				} else if (   !gateway_route_added
-				           && (   (   -((int) plerr) == ENETUNREACH
+				           && (   (   r == -ENETUNREACH
 				                   && vt->is_ip4
 				                   && !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway)
-				               || (   -((int) plerr) == EHOSTUNREACH
+				               || (   r == -EHOSTUNREACH
 				                   && !vt->is_ip4
 				                   && !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))) {
 					NMPObject oo;
 
 					if (vt->is_ip4) {
-						const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (conf_o);
+						const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE (conf_o);
 
 						nmp_object_stackinit (&oo,
 						                      NMP_OBJECT_TYPE_IP4_ROUTE,
 						                      &((NMPlatformIP4Route) {
-						                          .ifindex = r->ifindex,
-						                          .network = r->gateway,
+						                          .ifindex = rt->ifindex,
+						                          .network = rt->gateway,
 						                          .plen = 32,
-						                          .metric = r->metric,
-						                          .rt_source = r->rt_source,
-						                          .table_coerced = r->table_coerced,
+						                          .metric = rt->metric,
+						                          .rt_source = rt->rt_source,
+						                          .table_coerced = rt->table_coerced,
 						                      }));
 					} else {
-						const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (conf_o);
+						const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE (conf_o);
 
 						nmp_object_stackinit (&oo,
 						                      NMP_OBJECT_TYPE_IP6_ROUTE,
 						                      &((NMPlatformIP6Route) {
-						                          .ifindex = r->ifindex,
-						                          .network = r->gateway,
+						                          .ifindex = rt->ifindex,
+						                          .network = rt->gateway,
 						                          .plen = 128,
-						                          .metric = r->metric,
-						                          .rt_source = r->rt_source,
-						                          .table_coerced = r->table_coerced,
+						                          .metric = rt->metric,
+						                          .rt_source = rt->rt_source,
+						                          .table_coerced = rt->table_coerced,
 						                      }));
 					}
 
-					_LOGD ("route-sync: failure to add IPv%c route: %s: %s; try adding direct route to gateway %s",
-					       vt->is_ip4 ? '4' : '6',
-					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)),
-					       nmp_object_to_string (&oo, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
-
-					plerr2 = nm_platform_ip_route_add (self,
-					                                     NMP_NLM_FLAG_APPEND
-					                                   | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-					                                   &oo);
-
-					if (plerr2 != NM_PLATFORM_ERROR_SUCCESS) {
-						_LOGD ("route-sync: failure to add gateway IPv%c route: %s: %s",
-						       vt->is_ip4 ? '4' : '6',
-						       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-						       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					_LOG3D ("route-sync: failure to add IPv%c route: %s: %s; try adding direct route to gateway %s",
+					        vt->is_ip4 ? '4' : '6',
+					        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					        nm_strerror (r),
+					        nmp_object_to_string (&oo, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
+
+					r2 = nm_platform_ip_route_add (self,
+					                                 NMP_NLM_FLAG_APPEND
+					                               | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
+					                               &oo);
+
+					if (r2 < 0) {
+						_LOG3D ("route-sync: failure to add gateway IPv%c route: %s: %s",
+						        vt->is_ip4 ? '4' : '6',
+						        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+						        nm_strerror (r2));
 					}
 
 					gateway_route_added = TRUE;
 					goto sync_route_add;
 				} else {
-					_LOGW ("route-sync: failure to add IPv%c route: %s: %s",
+					_LOG3W ("route-sync: failure to add IPv%c route: %s: %s",
 					       vt->is_ip4 ? '4' : '6',
 					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					       nm_strerror (r));
 					success = FALSE;
 				}
 			}
@@ -4494,30 +4540,32 @@ nm_platform_ip_route_normalize (int addr_family,
 	}
 }
 
-static NMPlatformError
+static int
 _ip_route_add (NMPlatform *self,
                NMPNlmFlags flags,
                int addr_family,
                gconstpointer route)
 {
 	char sbuf[sizeof (_nm_utils_to_string_buffer)];
+	int ifindex;
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	nm_assert (route);
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	_LOGD ("route: %-10s IPv%c route: %s",
-	       _nmp_nlm_flag_to_string (flags & NMP_NLM_FLAG_FMASK),
-	       nm_utils_addr_family_to_char (addr_family),
-	       addr_family == AF_INET
-	         ? nm_platform_ip4_route_to_string (route, sbuf, sizeof (sbuf))
-	         : nm_platform_ip6_route_to_string (route, sbuf, sizeof (sbuf)));
+	ifindex = ((NMPlatformObject *)route)->ifindex;
+	_LOG3D ("route: %-10s IPv%c route: %s",
+	        _nmp_nlm_flag_to_string (flags & NMP_NLM_FLAG_FMASK),
+	        nm_utils_addr_family_to_char (addr_family),
+	        addr_family == AF_INET
+	          ? nm_platform_ip4_route_to_string (route, sbuf, sizeof (sbuf))
+	          : nm_platform_ip6_route_to_string (route, sbuf, sizeof (sbuf)));
 
 	return klass->ip_route_add (self, flags, addr_family, route);
 }
 
-NMPlatformError
+int
 nm_platform_ip_route_add (NMPlatform *self,
                           NMPNlmFlags flags,
                           const NMPObject *route)
@@ -4538,7 +4586,7 @@ nm_platform_ip_route_add (NMPlatform *self,
 	return _ip_route_add (self, flags, addr_family, NMP_OBJECT_CAST_IP_ROUTE (route));
 }
 
-NMPlatformError
+int
 nm_platform_ip4_route_add (NMPlatform *self,
                            NMPNlmFlags flags,
                            const NMPlatformIP4Route *route)
@@ -4546,7 +4594,7 @@ nm_platform_ip4_route_add (NMPlatform *self,
 	return _ip_route_add (self, flags, AF_INET, route);
 }
 
-NMPlatformError
+int
 nm_platform_ip6_route_add (NMPlatform *self,
                            NMPNlmFlags flags,
                            const NMPlatformIP6Route *route)
@@ -4558,6 +4606,7 @@ gboolean
 nm_platform_object_delete (NMPlatform *self,
                            const NMPObject *obj)
 {
+	int ifindex = obj->object.ifindex;
 	_CHECK_SELF (self, klass, FALSE);
 
 	if (!NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
@@ -4566,16 +4615,16 @@ nm_platform_object_delete (NMPlatform *self,
 	                                           NMP_OBJECT_TYPE_TFILTER))
 		g_return_val_if_reached (FALSE);
 
-	_LOGD ("%s: delete %s",
-	       NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
-	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	_LOG3D ("%s: delete %s",
+	        NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
+	        nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
 	return klass->object_delete (self, obj);
 }
 
 /*****************************************************************************/
 
-NMPlatformError
+int
 nm_platform_ip_route_get (NMPlatform *self,
                           int addr_family,
                           gconstpointer address /* in_addr_t or struct in6_addr */,
@@ -4583,16 +4632,15 @@ nm_platform_ip_route_get (NMPlatform *self,
                           NMPObject **out_route)
 {
 	nm_auto_nmpobj NMPObject *route = NULL;
-	NMPlatformError result;
+	int result;
 	char buf[NM_UTILS_INET_ADDRSTRLEN];
-	char buf_err[200];
 	char buf_oif[64];
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (address, -NME_BUG);
 	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET,
-	                                              AF_INET6), NM_PLATFORM_ERROR_BUG);
+	                                              AF_INET6), -NME_BUG);
 
 	_LOGT ("route: get IPv%c route for: %s%s",
 	       nm_utils_addr_family_to_char (addr_family),
@@ -4600,7 +4648,7 @@ nm_platform_ip_route_get (NMPlatform *self,
 	       oif_ifindex > 0 ? nm_sprintf_buf (buf_oif, " oif %d", oif_ifindex) : "");
 
 	if (!klass->ip_route_get)
-		result = NM_PLATFORM_ERROR_OPNOTSUPP;
+		result = -NME_PL_OPNOTSUPP;
 	else {
 		result = klass->ip_route_get (self,
 		                              addr_family,
@@ -4609,12 +4657,12 @@ nm_platform_ip_route_get (NMPlatform *self,
 		                              &route);
 	}
 
-	if (result != NM_PLATFORM_ERROR_SUCCESS) {
+	if (result < 0) {
 		nm_assert (!route);
 		_LOGW ("route: get IPv%c route for: %s failed with %s",
 		       nm_utils_addr_family_to_char (addr_family),
 		       inet_ntop (addr_family, address, buf, sizeof (buf)),
-		       nm_platform_error_to_string (result, buf_err, sizeof (buf_err)));
+		       nm_strerror (result));
 	} else {
 		nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (route), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 		nm_assert (!NMP_OBJECT_IS_STACKINIT (route));
@@ -4807,7 +4855,7 @@ _ip4_dev_route_blacklist_schedule (NMPlatform *self)
  * route, however it has a wrong metric of zero. We add our own device route (with
  * proper metric), but need to delete the route that kernel adds.
  *
- * The problem is, that kernel does not immidiately add the route, when adding
+ * The problem is, that kernel does not immediately add the route, when adding
  * the address. It only shows up some time later. So, we register here a list
  * of blacklisted routes, and when they show up within a time out, we assume it's
  * the kernel generated one, and we delete it.
@@ -4898,14 +4946,15 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 
 /*****************************************************************************/
 
-NMPlatformError
+int
 nm_platform_qdisc_add (NMPlatform *self,
                        NMPNlmFlags flags,
                        const NMPlatformQdisc *qdisc)
 {
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	int ifindex = qdisc->ifindex;
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	_LOGD ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
+	_LOG3D ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
 	return klass->qdisc_add (self, flags, qdisc);
 }
 
@@ -4954,7 +5003,7 @@ nm_platform_qdisc_sync (NMPlatform *self,
 			const NMPObject *q = g_ptr_array_index (known_qdiscs, i);
 
 			success &= (nm_platform_qdisc_add (self, NMP_NLM_FLAG_ADD,
-			                                   NMP_OBJECT_CAST_QDISC (q)) == NM_PLATFORM_ERROR_SUCCESS);
+			                                   NMP_OBJECT_CAST_QDISC (q)) >= 0);
 		}
 	}
 
@@ -4963,14 +5012,15 @@ nm_platform_qdisc_sync (NMPlatform *self,
 
 /*****************************************************************************/
 
-NMPlatformError
+int
 nm_platform_tfilter_add (NMPlatform *self,
                          NMPNlmFlags flags,
                          const NMPlatformTfilter *tfilter)
 {
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	int ifindex = tfilter->ifindex;
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	_LOGD ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
+	_LOG3D ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
 	return klass->tfilter_add (self, flags, tfilter);
 }
 
@@ -5019,7 +5069,7 @@ nm_platform_tfilter_sync (NMPlatform *self,
 			const NMPObject *q = g_ptr_array_index (known_tfilters, i);
 
 			success &= (nm_platform_tfilter_add (self, NMP_NLM_FLAG_ADD,
-			                                     NMP_OBJECT_CAST_TFILTER (q)) == NM_PLATFORM_ERROR_SUCCESS);
+			                                     NMP_OBJECT_CAST_TFILTER (q)) >= 0);
 		}
 	}
 
@@ -5477,6 +5527,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	char str_dst_port[25];
 	char str_tos[25];
 	char str_ttl[25];
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
 		return buf;
@@ -5487,7 +5538,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_group, sizeof (str_group),
 		            " %s %s",
 		            IN_MULTICAST (ntohl (lnk->group)) ? "group" : "remote",
-		            nm_utils_inet4_ntop (lnk->group, NULL));
+		            nm_utils_inet4_ntop (lnk->group, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->group6))
 		str_group6[0] = '\0';
@@ -5496,7 +5547,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		            " %s%s %s",
 		            IN6_IS_ADDR_MULTICAST (&lnk->group6) ? "group" : "remote",
 		            str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->group6, NULL));
+		            nm_utils_inet6_ntop (&lnk->group6, sbuf));
 	}
 
 	if (lnk->local == 0)
@@ -5504,7 +5555,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	else {
 		g_snprintf (str_local, sizeof (str_local),
 		            " local %s",
-		            nm_utils_inet4_ntop (lnk->local, NULL));
+		            nm_utils_inet4_ntop (lnk->local, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->local6))
 		str_local6[0] = '\0';
@@ -5512,7 +5563,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_local6, sizeof (str_local6),
 		            " local%s %s",
 		            str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->local6, NULL));
+		            nm_utils_inet6_ntop (&lnk->local6, sbuf));
 	}
 
 	g_snprintf (buf, len,
@@ -5554,28 +5605,26 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 const char *
 nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, gsize len)
 {
+	char *buf0 = buf;
 	gs_free char *public_key_b64 = NULL;
-	char s_endpoint[NM_UTILS_INET_ADDRSTRLEN + 100];
+	char s_sockaddr[NM_UTILS_INET_ADDRSTRLEN + 100];
+	char s_endpoint[20 + sizeof (s_sockaddr)];
 	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
 	guint i;
 
 	nm_utils_to_string_buffer_init (&buf, &len);
 
-	if (peer->endpoint_family == AF_INET) {
-		nm_sprintf_buf (s_endpoint,
-		                " endpoint %s:%u",
-		                nm_utils_inet4_ntop (peer->endpoint_addr.addr4, s_addr),
-		                (guint) peer->endpoint_port);
-	} else if (peer->endpoint_family == AF_INET6) {
+	public_key_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
+
+	if (peer->endpoint.sa.sa_family != AF_UNSPEC) {
 		nm_sprintf_buf (s_endpoint,
-		                " endpoint [%s]:%u",
-		                nm_utils_inet6_ntop (&peer->endpoint_addr.addr6, s_addr),
-		                (guint) peer->endpoint_port);
+		                " endpoint %s",
+		                nm_sock_addr_union_to_string (&peer->endpoint,
+		                                              s_sockaddr,
+		                                              sizeof (s_sockaddr)));
 	} else
 		s_endpoint[0] = '\0';
 
-	public_key_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
-
 	nm_utils_strbuf_append (&buf, &len,
 	                        "public-key %s"
 	                        "%s" /* preshared-key */
@@ -5584,7 +5633,7 @@ nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, g
 	                        " tx %"G_GUINT64_FORMAT
 	                        "%s", /* allowed-ips */
 	                        public_key_b64,
-	                        nm_utils_mem_all_zero (peer->preshared_key, sizeof (peer->preshared_key))
+	                        nm_utils_memeqzero (peer->preshared_key, sizeof (peer->preshared_key))
 	                          ? ""
 	                          : " preshared-key (hidden)",
 	                        s_endpoint,
@@ -5603,7 +5652,7 @@ nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, g
 		                        allowed_ip->mask);
 	}
 
-	return buf;
+	return buf0;
 }
 
 const char *
@@ -5614,7 +5663,7 @@ nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *bu
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
 		return buf;
 
-	if (!nm_utils_mem_all_zero (lnk->public_key, sizeof (lnk->public_key)))
+	if (!nm_utils_memeqzero (lnk->public_key, sizeof (lnk->public_key)))
 		public_b64 = g_base64_encode (lnk->public_key, sizeof (lnk->public_key));
 
 	g_snprintf (buf, len,
@@ -5627,7 +5676,7 @@ nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *bu
 	              ? " public-key "
 	              : "",
 	            public_b64 ?: "",
-	            nm_utils_mem_all_zero (lnk->private_key, sizeof (lnk->private_key))
+	            nm_utils_memeqzero (lnk->private_key, sizeof (lnk->private_key))
 	              ? ""
 	              : " private-key (hidden)",
 	            lnk->listen_port,
@@ -5926,13 +5975,21 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 const char *
 nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len)
 {
-	char s_network[INET6_ADDRSTRLEN], s_gateway[INET6_ADDRSTRLEN], s_pref_src[INET6_ADDRSTRLEN];
-	char s_src_all[INET6_ADDRSTRLEN + 40], s_src[INET6_ADDRSTRLEN];
+	char s_network[INET6_ADDRSTRLEN];
+	char s_gateway[INET6_ADDRSTRLEN];
+	char s_pref_src[INET6_ADDRSTRLEN];
+	char s_src_all[INET6_ADDRSTRLEN + 40];
+	char s_src[INET6_ADDRSTRLEN];
 	char str_table[30];
 	char str_pref[40];
 	char str_pref2[30];
-	char str_dev[TO_STRING_DEV_BUF_SIZE], s_source[50];
-	char str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char s_source[50];
+	char str_window[32];
+	char str_cwnd[32];
+	char str_initcwnd[32];
+	char str_initrwnd[32];
+	char str_mtu[32];
 	char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
@@ -6982,7 +7039,7 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 		/* if the lifetime is equal, compare the preferred time. */
 		ta = tb = 0;
 
-		if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0 /* liftime==0 means permanent! */)
+		if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0 /* lifetime==0 means permanent! */)
 			ta = G_MAXINT64;
 		else if (a->timestamp)
 			ta = ((gint64) a->timestamp) + a->preferred;
@@ -7017,44 +7074,43 @@ nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type
 static void
 log_link (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformLink *device, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-
-	_LOGD ("signal: link %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_link_to_string (device, NULL, 0));
+	_LOG3D ("signal: link %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_link_to_string (device, NULL, 0));
 }
 
 static void
 log_ip4_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: address 4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_address_to_string (address, NULL, 0));
+	_LOG3D ("signal: address 4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_address_to_string (address, NULL, 0));
 }
 
 static void
 log_ip6_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: address 6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_address_to_string (address, NULL, 0));
+	_LOG3D ("signal: address 6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_address_to_string (address, NULL, 0));
 }
 
 static void
 log_ip4_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: route   4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_route_to_string (route, NULL, 0));
+	_LOG3D ("signal: route   4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_route_to_string (route, NULL, 0));
 }
 
 static void
 log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
+	_LOG3D ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
 }
 
 static void
 log_qdisc (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformQdisc *qdisc, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: qdisc %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_qdisc_to_string (qdisc, NULL, 0));
+	_LOG3D ("signal: qdisc %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_qdisc_to_string (qdisc, NULL, 0));
 }
 
 static void
 log_tfilter (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformTfilter *tfilter, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: tfilter %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_tfilter_to_string (tfilter, NULL, 0));
+	_LOG3D ("signal: tfilter %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_tfilter_to_string (tfilter, NULL, 0));
 }
 
 /*****************************************************************************/
@@ -7069,6 +7125,7 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	gboolean visible_old;
 	const NMPObject *o;
 	const NMPClass *klass;
+	int ifindex;
 
 	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, NM_PLATFORM_SIGNAL_NONE,
 	                                                             NM_PLATFORM_SIGNAL_ADDED,
@@ -7110,6 +7167,7 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 		return;
 	}
 
+	ifindex = o->object.ifindex;
 	klass = NMP_OBJECT_GET_CLASS (o);
 
 	if (   klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
@@ -7117,10 +7175,10 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	    && NM_IN_SET (cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED))
 		_ip4_dev_route_blacklist_notify_route (self, o);
 
-	_LOGt ("emit signal %s %s: %s",
-	       klass->signal_type,
-	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
-	       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	_LOG3t ("emit signal %s %s: %s",
+	        klass->signal_type,
+	        nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
+	        nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
 	nmp_object_ref (o);
 	g_signal_emit (self,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 2bf5966c..412ac597 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -21,12 +21,6 @@
 #ifndef __NETWORKMANAGER_PLATFORM_H__
 #define __NETWORKMANAGER_PLATFORM_H__
 
-#include <netinet/in.h>
-#include <linux/if.h>
-#include <linux/if_addr.h>
-#include <linux/if_link.h>
-#include <linux/ip6_tunnel.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-core-types-internal.h"
 
@@ -35,6 +29,7 @@
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
 #include "nm-setting-ip-tunnel.h"
+#include "nm-utils/nm-errno.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -53,6 +48,14 @@
 
 /*****************************************************************************/
 
+/* IFNAMSIZ is both defined in <linux/if.h> and <net/if.h>. In the past, these
+ * headers conflicted, so we cannot simply include either of them in a header-file.*/
+#define NMP_IFNAMSIZ 16
+
+/*****************************************************************************/
+
+struct _NMPWireGuardPeer;
+
 struct udev_device;
 
 typedef gboolean (*NMPObjectPredicateFunc) (const NMPObject *obj,
@@ -149,29 +152,6 @@ typedef enum {
 
 } NMPlatformIPRouteCmpType;
 
-typedef enum { /*< skip >*/
-
-	/* dummy value, to enforce that the enum type is signed and has a size
-	 * to hold an integer. We want to encode errno from <errno.h> as negative
-	 * values. */
-	_NM_PLATFORM_ERROR_MININT = G_MININT,
-
-	NM_PLATFORM_ERROR_SUCCESS = 0,
-
-	NM_PLATFORM_ERROR_BUG,
-
-	NM_PLATFORM_ERROR_UNSPECIFIED,
-
-	NM_PLATFORM_ERROR_NOT_FOUND,
-	NM_PLATFORM_ERROR_EXISTS,
-	NM_PLATFORM_ERROR_WRONG_TYPE,
-	NM_PLATFORM_ERROR_NOT_SLAVE,
-	NM_PLATFORM_ERROR_NO_FIRMWARE,
-	NM_PLATFORM_ERROR_OPNOTSUPP,
-	NM_PLATFORM_ERROR_NETLINK,
-	NM_PLATFORM_ERROR_CANT_SET_MTU,
-} NMPlatformError;
-
 typedef enum {
 
 	/* match-flags are strictly inclusive. That means,
@@ -208,7 +188,7 @@ typedef enum {
 
 struct _NMPlatformLink {
 	__NMPlatformObject_COMMON;
-	char name[IFNAMSIZ];
+	char name[NMP_IFNAMSIZ];
 	NMLinkType type;
 
 	/* rtnl_link_get_type(), IFLA_INFO_KIND. */
@@ -299,10 +279,10 @@ struct _NMPlatformObject {
 	 *   are permanent. This rule is so that unset addresses (calloc) are permanent by default.
 	 * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly
 	 *   set to 0). Such addresses are permanent.
-	 * 3 Non permanent addreses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
+	 * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
 	 *   and never returned by nm_utils_get_monotonic_timestamp_s(). In this case @valid/@preferred
 	 *   is anchored at @timestamp.
-	 * 4 Non permanent addresses with @timestamp == 0 are implicitely anchored at *now*, thus the time
+	 * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time
 	 *   moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add().
 	 *
 	 * Non permanent addresses from DHCP/RA might have the @timestamp set to the moment of when the
@@ -355,7 +335,7 @@ struct _NMPlatformIP4Address {
 	 * */
 	in_addr_t peer_address;  /* PTP peer address */
 
-	char label[IFNAMSIZ];
+	char label[NMP_IFNAMSIZ];
 };
 
 /**
@@ -417,7 +397,7 @@ typedef union {
 	 * On the other hand, for IPv6 you cannot add two IPv6 routes that only differ
 	 * by an RTA_METRICS property.
 	 *
-	 * When deleting a route, kernel seems to ignore the RTA_METRICS propeties.
+	 * When deleting a route, kernel seems to ignore the RTA_METRICS properties.
 	 * That is a problem/bug for IPv4 because you cannot explicitly select which
 	 * route to delete. Kernel just picks the first. See rh#1475642. */ \
 	\
@@ -792,13 +772,14 @@ typedef struct {
 
 	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
 
-	gboolean (*link_add) (NMPlatform *,
-	                      const char *name,
-	                      NMLinkType type,
-	                      const char *veth_peer,
-	                      const void *address,
-	                      size_t address_len,
-	                      const NMPlatformLink **out_link);
+	int (*link_add) (NMPlatform *,
+	                 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);
@@ -815,15 +796,15 @@ typedef struct {
 	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
 	struct udev_device *(*link_get_udev_device) (NMPlatform *self, int ifindex);
 
-	NMPlatformError (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
+	int (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
 	gboolean (*link_set_token) (NMPlatform *, int ifindex, NMUtilsIPv6IfaceId iid);
 
 	gboolean (*link_get_permanent_address) (NMPlatform *,
 	                                        int ifindex,
 	                                        guint8 *buf,
 	                                        size_t *length);
-	NMPlatformError (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
-	NMPlatformError (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
+	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);
 	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
@@ -846,6 +827,13 @@ typedef struct {
 
 	gboolean (*link_can_assume) (NMPlatform *, int ifindex);
 
+	int (*link_wireguard_change) (NMPlatform *self,
+	                              int ifindex,
+	                              const NMPlatformLnkWireGuard *lnk_wireguard,
+	                              const struct _NMPWireGuardPeer *peers,
+	                              guint peers_len,
+	                              gboolean replace_peers);
+
 	gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
 	                              int ifindex,
@@ -927,6 +915,7 @@ typedef struct {
 	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);
 
@@ -950,23 +939,23 @@ typedef struct {
 	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);
 
-	NMPlatformError (*ip_route_add) (NMPlatform *,
-	                                 NMPNlmFlags flags,
-	                                 int addr_family,
-	                                 const NMPlatformIPRoute *route);
-	NMPlatformError (*ip_route_get) (NMPlatform *self,
-	                                 int addr_family,
-	                                 gconstpointer address,
-	                                 int oif_ifindex,
-	                                 NMPObject **out_route);
+	int (*ip_route_add) (NMPlatform *,
+	                     NMPNlmFlags flags,
+	                     int addr_family,
+	                     const NMPlatformIPRoute *route);
+	int (*ip_route_get) (NMPlatform *self,
+	                     int addr_family,
+	                     gconstpointer address,
+	                     int oif_ifindex,
+	                     NMPObject **out_route);
 
-	NMPlatformError (*qdisc_add)   (NMPlatform *self,
-	                                NMPNlmFlags flags,
-	                                const NMPlatformQdisc *qdisc);
+	int (*qdisc_add)   (NMPlatform *self,
+	                    NMPNlmFlags flags,
+	                    const NMPlatformQdisc *qdisc);
 
-	NMPlatformError (*tfilter_add)   (NMPlatform *self,
-	                                  NMPNlmFlags flags,
-	                                  const NMPlatformTfilter *tfilter);
+	int (*tfilter_add)   (NMPlatform *self,
+	                      NMPNlmFlags flags,
+	                      const NMPlatformTfilter *tfilter);
 
 	NMPlatformKernelSupportFlags (*check_kernel_support) (NMPlatform * self,
 	                                                      NMPlatformKernelSupportFlags request_flags);
@@ -1095,22 +1084,23 @@ gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
 
 const char *nm_link_type_to_string (NMLinkType link_type);
 
-const char *nm_platform_error_to_string (NMPlatformError error,
-                                         char *buf,
-                                         gsize buf_len);
-#define nm_platform_error_to_string_a(error) \
-	(nm_platform_error_to_string ((error), g_alloca (30), 30))
-
 #define NMP_SYSCTL_PATHID_ABSOLUTE(path) \
 	((const char *) NULL), -1, (path)
 
 #define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path) \
-	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//\0") + IFNAMSIZ + strlen (path), \
-	                 "net:/sys/class/net/%s/%s", (ifname), (path)), \
+	nm_sprintf_buf_unsafe_a (  NM_STRLEN ("net:/sys/class/net//\0") \
+	                         + NMP_IFNAMSIZ \
+	                         + ({ \
+	                             const gsize _l = strlen (path); \
+	                             \
+	                             nm_assert (_l < 200); \
+	                             _l; \
+	                            }), \
+	                         "net:/sys/class/net/%s/%s", (ifname), (path)), \
 	(dirfd), (path)
 
 #define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path) \
-	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//"path"/\0") + IFNAMSIZ, \
+	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//"path"/\0") + NMP_IFNAMSIZ, \
 	                 "net:/sys/class/net/%s/%s", (ifname), path), \
 	(dirfd), (""path"")
 
@@ -1120,7 +1110,35 @@ char *nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, c
 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);
 
-gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value);
+char *nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+                                      int addr_family,
+                                      const char *ifname,
+                                      const char *property);
+
+gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+                                                   int addr_family,
+                                                   const char *ifname,
+                                                   const char *property,
+                                                   guint base,
+                                                   gint64 min,
+                                                   gint64 max,
+                                                   gint64 fallback);
+
+gboolean nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+                                         int addr_family,
+                                         const char *ifname,
+                                         const char *property,
+                                         const char *value);
+
+gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+                                               int addr_family,
+                                               const char *ifname,
+                                               const char *property,
+                                               gint64 value);
+
+gboolean nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
+                                                             const char *iface,
+                                                             int value);
 
 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);
@@ -1135,11 +1153,11 @@ const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const ch
 const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, NMLinkType link_type, gconstpointer address, size_t length);
 
 GPtrArray *nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name);
-NMPlatformError nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
+int nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+int nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
+int nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+int nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+int nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
 
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
@@ -1160,7 +1178,7 @@ GPtrArray *nm_platform_lookup_clone (NMPlatform *platform,
                                      NMPObjectPredicateFunc predicate,
                                      gpointer user_data);
 
-/* convienience methods to lookup the link and access fields of NMPlatformLink. */
+/* convenience methods to lookup the link and access fields of NMPlatformLink. */
 int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
 const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
 NMLinkType nm_platform_link_get_type (NMPlatform *self, int ifindex);
@@ -1210,12 +1228,12 @@ const char *nm_platform_link_get_udi (NMPlatform *self, int ifindex);
 
 struct udev_device *nm_platform_link_get_udev_device (NMPlatform *self, int ifindex);
 
-NMPlatformError nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
+int nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
 gboolean nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
 
 gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length);
-NMPlatformError nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
-NMPlatformError nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
+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);
 gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
@@ -1259,12 +1277,12 @@ const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int if
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkWireGuard *nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
-NMPlatformError nm_platform_link_vlan_add (NMPlatform *self,
-                                           const char *name,
-                                           int parent,
-                                           int vlanid,
-                                           guint32 vlanflags,
-                                           const NMPlatformLink **out_link);
+int nm_platform_link_vlan_add (NMPlatform *self,
+                               const char *name,
+                               int parent,
+                               int vlanid,
+                               guint32 vlanflags,
+                               const NMPlatformLink **out_link);
 gboolean nm_platform_link_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_change (NMPlatform *self,
@@ -1278,18 +1296,18 @@ gboolean nm_platform_link_vlan_change (NMPlatform *self,
                                        const NMVlanQosMapping *egress_map,
                                        gsize n_egress_map);
 
-NMPlatformError nm_platform_link_vxlan_add (NMPlatform *self,
-                                            const char *name,
-                                            const NMPlatformLnkVxlan *props,
-                                            const NMPlatformLink **out_link);
-
-NMPlatformError nm_platform_link_infiniband_add (NMPlatform *self,
-                                                 int parent,
-                                                 int p_key,
-                                                 const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_infiniband_delete (NMPlatform *self,
-                                                    int parent,
-                                                    int p_key);
+int nm_platform_link_vxlan_add (NMPlatform *self,
+                                const char *name,
+                                const NMPlatformLnkVxlan *props,
+                                const NMPlatformLink **out_link);
+
+int nm_platform_link_infiniband_add (NMPlatform *self,
+                                     int parent,
+                                     int p_key,
+                                     const NMPlatformLink **out_link);
+int nm_platform_link_infiniband_delete (NMPlatform *self,
+                                        int parent,
+                                        int p_key);
 gboolean nm_platform_link_infiniband_get_properties (NMPlatform *self, int ifindex, int *parent, int *p_key, const char **mode);
 
 gboolean nm_platform_link_veth_get_properties   (NMPlatform *self, int ifindex, int *out_peer_ifindex);
@@ -1318,55 +1336,67 @@ 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);
 
 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);
 
 const NMPlatformIP4Address *nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 
-NMPlatformError nm_platform_link_gre_add (NMPlatform *self,
-                                          const char *name,
-                                          const NMPlatformLnkGre *props,
-                                          const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_ip6tnl_add (NMPlatform *self,
-                                             const char *name,
-                                             const NMPlatformLnkIp6Tnl *props,
-                                             const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_ip6gre_add (NMPlatform *self,
-                                             const char *name,
-                                             const NMPlatformLnkIp6Tnl *props,
-                                             const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_ipip_add (NMPlatform *self,
-                                           const char *name,
-                                           const NMPlatformLnkIpIp *props,
-                                           const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_macsec_add (NMPlatform *self,
-                                             const char *name,
-                                             int parent,
-                                             const NMPlatformLnkMacsec *props,
-                                             const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_macvlan_add (NMPlatform *self,
-                                              const char *name,
-                                              int parent,
-                                              const NMPlatformLnkMacvlan *props,
-                                              const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_sit_add (NMPlatform *self,
-                                          const char *name,
-                                          const NMPlatformLnkSit *props,
-                                          const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
-                                          const char *name,
-                                          const NMPlatformLnkTun *props,
-                                          const NMPlatformLink **out_link,
-                                          int *out_fd);
-NMPlatformError nm_platform_link_6lowpan_add (NMPlatform *self,
-                                              const char *name,
-                                              int parent,
-                                              const NMPlatformLink **out_link);
+int nm_platform_link_gre_add (NMPlatform *self,
+                              const char *name,
+                              const NMPlatformLnkGre *props,
+                              const NMPlatformLink **out_link);
+int nm_platform_link_ip6tnl_add (NMPlatform *self,
+                                 const char *name,
+                                 const NMPlatformLnkIp6Tnl *props,
+                                 const NMPlatformLink **out_link);
+int nm_platform_link_ip6gre_add (NMPlatform *self,
+                                 const char *name,
+                                 const NMPlatformLnkIp6Tnl *props,
+                                 const NMPlatformLink **out_link);
+int nm_platform_link_ipip_add (NMPlatform *self,
+                               const char *name,
+                               const NMPlatformLnkIpIp *props,
+                               const NMPlatformLink **out_link);
+int nm_platform_link_macsec_add (NMPlatform *self,
+                                 const char *name,
+                                 int parent,
+                                 const NMPlatformLnkMacsec *props,
+                                 const NMPlatformLink **out_link);
+int nm_platform_link_macvlan_add (NMPlatform *self,
+                                  const char *name,
+                                  int parent,
+                                  const NMPlatformLnkMacvlan *props,
+                                  const NMPlatformLink **out_link);
+int nm_platform_link_sit_add (NMPlatform *self,
+                              const char *name,
+                              const NMPlatformLnkSit *props,
+                              const NMPlatformLink **out_link);
+int nm_platform_link_tun_add (NMPlatform *self,
+                              const char *name,
+                              const NMPlatformLnkTun *props,
+                              const NMPlatformLink **out_link,
+                              int *out_fd);
+int nm_platform_link_6lowpan_add (NMPlatform *self,
+                                  const char *name,
+                                  int parent,
+                                  const NMPlatformLink **out_link);
 gboolean nm_platform_link_6lowpan_get_properties (NMPlatform *self,
                                                   int ifindex,
                                                   int *out_parent);
 
+int nm_platform_link_wireguard_add (NMPlatform *self,
+                                    const char *name,
+                                    const NMPlatformLink **out_link);
+
+int nm_platform_link_wireguard_change (NMPlatform *self,
+                                       int ifindex,
+                                       const NMPlatformLnkWireGuard *lnk_wireguard,
+                                       const struct _NMPWireGuardPeer *peers,
+                                       guint peers_len,
+                                       gboolean replace_peers);
+
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
 gboolean nm_platform_object_delete (NMPlatform *self, const NMPObject *route);
@@ -1399,11 +1429,11 @@ gboolean nm_platform_ip_address_flush (NMPlatform *self,
 void nm_platform_ip_route_normalize (int addr_family,
                                      NMPlatformIPRoute *route);
 
-NMPlatformError nm_platform_ip_route_add (NMPlatform *self,
-                                          NMPNlmFlags flags,
-                                          const NMPObject *route);
-NMPlatformError nm_platform_ip4_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
-NMPlatformError nm_platform_ip6_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
+int nm_platform_ip_route_add (NMPlatform *self,
+                              NMPNlmFlags flags,
+                              const NMPObject *route);
+int nm_platform_ip4_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
+int nm_platform_ip6_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
 
 GPtrArray *nm_platform_ip_route_get_prune_list (NMPlatform *self,
                                                 int addr_family,
@@ -1421,22 +1451,22 @@ gboolean nm_platform_ip_route_flush (NMPlatform *self,
                                      int addr_family,
                                      int ifindex);
 
-NMPlatformError nm_platform_ip_route_get (NMPlatform *self,
-                                          int addr_family,
-                                          gconstpointer address,
-                                          int oif_ifindex,
-                                          NMPObject **out_route);
+int nm_platform_ip_route_get (NMPlatform *self,
+                              int addr_family,
+                              gconstpointer address,
+                              int oif_ifindex,
+                              NMPObject **out_route);
 
-NMPlatformError nm_platform_qdisc_add   (NMPlatform *self,
-                                         NMPNlmFlags flags,
-                                         const NMPlatformQdisc *qdisc);
+int nm_platform_qdisc_add   (NMPlatform *self,
+                             NMPNlmFlags flags,
+                             const NMPlatformQdisc *qdisc);
 gboolean nm_platform_qdisc_sync         (NMPlatform *self,
                                          int ifindex,
                                          GPtrArray *known_qdiscs);
 
-NMPlatformError nm_platform_tfilter_add   (NMPlatform *self,
-                                           NMPNlmFlags flags,
-                                           const NMPlatformTfilter *tfilter);
+int nm_platform_tfilter_add   (NMPlatform *self,
+                               NMPNlmFlags flags,
+                               const NMPlatformTfilter *tfilter);
 gboolean nm_platform_tfilter_sync         (NMPlatform *self,
                                            int ifindex,
                                            GPtrArray *known_tfilters);
@@ -1467,7 +1497,6 @@ const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     char *buf,
                                                     gsize len);
 
-struct _NMPWireGuardPeer;
 const char *nm_platform_wireguard_peer_to_string (const struct _NMPWireGuardPeer *peer,
                                                   char *buf,
                                                   gsize len);
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index fdc27440..80e65b49 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -24,6 +24,7 @@
 
 #include <unistd.h>
 #include <linux/rtnetlink.h>
+#include <linux/if.h>
 #include <libudev.h>
 
 #include "nm-utils.h"
@@ -88,6 +89,192 @@ struct _NMPCache {
 
 /*****************************************************************************/
 
+int
+nm_sock_addr_union_cmp (const NMSockAddrUnion *a, const NMSockAddrUnion *b)
+{
+	nm_assert (!a || NM_IN_SET (a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
+	nm_assert (!b || NM_IN_SET (b->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	NM_CMP_SELF (a, b);
+
+	NM_CMP_FIELD (a, b, sa.sa_family);
+	switch (a->sa.sa_family) {
+	case AF_INET:
+		NM_CMP_DIRECT (ntohl (a->in.sin_addr.s_addr), ntohl (b->in.sin_addr.s_addr));
+		NM_CMP_DIRECT (htons (a->in.sin_port), htons (b->in.sin_port));
+		break;
+	case AF_INET6:
+		NM_CMP_DIRECT_IN6ADDR (&a->in6.sin6_addr, &b->in6.sin6_addr);
+		NM_CMP_DIRECT (htons (a->in6.sin6_port), htons (b->in6.sin6_port));
+		NM_CMP_FIELD (a, b, in6.sin6_scope_id);
+		NM_CMP_FIELD (a, b, in6.sin6_flowinfo);
+		break;
+	}
+	return 0;
+}
+
+void
+nm_sock_addr_union_hash_update (const NMSockAddrUnion *a, NMHashState *h)
+{
+	if (!a) {
+		nm_hash_update_val (h, 1241364739u);
+		return;
+	}
+
+	nm_assert (NM_IN_SET (a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	switch (a->sa.sa_family) {
+	case AF_INET:
+		nm_hash_update_vals (h,
+		                     a->in.sin_family,
+		                     a->in.sin_addr.s_addr,
+		                     a->in.sin_port);
+		return;
+	case AF_INET6:
+		nm_hash_update_vals (h,
+		                     a->in6.sin6_family,
+		                     a->in6.sin6_addr,
+		                     a->in6.sin6_port,
+		                     a->in6.sin6_scope_id,
+		                     a->in6.sin6_flowinfo);
+		return;
+	default:
+		nm_hash_update_val (h, a->sa.sa_family);
+		return;
+	}
+}
+
+/**
+ * nm_sock_addr_union_cpy:
+ * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
+ *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
+ *   error).
+ * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in
+ *   memory. If not %NULL, the buffer must be at least large enough to contain
+ *   sa.sa_family, and then, depending on sa.sa_family, it must be large enough
+ *   to hold struct sockaddr_in or struct sockaddr_in6.
+ *
+ * @dst will always be fully initialized (including setting all un-used bytes to zero).
+ */
+void
+nm_sock_addr_union_cpy (NMSockAddrUnion *dst,
+                        gconstpointer src /* unaligned (const NMSockAddrUnion *) */)
+{
+	struct sockaddr sa;
+	gsize src_len;
+
+	nm_assert (dst);
+
+	*dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (!src)
+		return;
+
+	memcpy (&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof (sa.sa_family));
+
+	if (sa.sa_family == AF_INET)
+		src_len = sizeof (struct sockaddr_in);
+	else if (sa.sa_family == AF_INET6)
+		src_len = sizeof (struct sockaddr_in6);
+	else
+		return;
+
+	memcpy (dst, src, src_len);
+	nm_assert (dst->sa.sa_family == sa.sa_family);
+}
+
+/**
+ * nm_sock_addr_union_cpy_untrusted:
+ * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
+ *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
+ *   error).
+ * @src: the source buffer with an sockaddr to copy. It may be unaligned in
+ *   memory.
+ * @src_len: the length of @src in bytes.
+ *
+ * The function requires @src_len to be either sizeof(struct sockaddr_in) or sizeof (struct sockaddr_in6).
+ * If that's the case, then @src will be interpreted as such structure (unaligned), and
+ * accessed. It will check sa.sa_family to match the expected sizes, and if it does, the
+ * struct will be copied.
+ *
+ * On any failure, @dst will be set to sa.sa_family AF_UNSPEC.
+ * @dst will always be fully initialized (including setting all un-used bytes to zero).
+ */
+void
+nm_sock_addr_union_cpy_untrusted (NMSockAddrUnion *dst,
+                                  gconstpointer src /* unaligned (const NMSockAddrUnion *) */,
+                                  gsize src_len)
+{
+	int f_expected;
+	struct sockaddr sa;
+
+	nm_assert (dst);
+
+	*dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (src_len == sizeof (struct sockaddr_in))
+		f_expected = AF_INET;
+	else if (src_len == sizeof (struct sockaddr_in6))
+		f_expected = AF_INET6;
+	else
+		return;
+
+	memcpy (&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof (sa.sa_family));
+
+	if (sa.sa_family != f_expected)
+		return;
+
+	memcpy (dst, src, src_len);
+	nm_assert (dst->sa.sa_family == sa.sa_family);
+}
+
+const char *
+nm_sock_addr_union_to_string (const NMSockAddrUnion *sa,
+                              char *buf,
+                              gsize len)
+{
+	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	char s_scope_id[40];
+
+	if (!nm_utils_to_string_buffer_init_null (sa, &buf, &len))
+		return buf;
+
+	/* maybe we should use getnameinfo(), but here implement it ourself.
+	 *
+	 * We want to see the actual bytes for debugging (as we understand them),
+	 * and now what getnameinfo() makes of it. Also, it's simpler this way. */
+
+	switch (sa->sa.sa_family) {
+	case AF_INET:
+		g_snprintf (buf, len,
+		            "%s:%u",
+		            nm_utils_inet4_ntop (sa->in.sin_addr.s_addr, s_addr),
+		            (guint) htons (sa->in.sin_port));
+		break;
+	case AF_INET6:
+		g_snprintf (buf, len,
+		            "[%s%s]:%u",
+		            nm_utils_inet6_ntop (&sa->in6.sin6_addr, s_addr),
+		            (  sa->in6.sin6_scope_id != 0
+		             ? nm_sprintf_buf (s_scope_id, "%u", sa->in6.sin6_scope_id)
+		             : ""),
+		            (guint) htons (sa->in6.sin6_port));
+		break;
+	case AF_UNSPEC:
+		g_snprintf (buf, len, "unspec");
+		break;
+	default:
+		g_snprintf (buf, len,
+		            "{addr-family:%u}",
+		            (unsigned) sa->sa.sa_family);
+		break;
+	}
+
+	return buf;
+}
+
+/*****************************************************************************/
+
 static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class;
 
 static void
@@ -391,14 +578,9 @@ _wireguard_peer_hash_update (const NMPWireGuardPeer *peer,
 	                     peer->rx_bytes,
 	                     peer->tx_bytes,
 	                     peer->last_handshake_time.tv_sec,
-	                     peer->last_handshake_time.tv_nsec,
-	                     peer->endpoint_port,
-	                     peer->endpoint_family);
+	                     peer->last_handshake_time.tv_nsec);
 
-	if (peer->endpoint_family == AF_INET)
-		nm_hash_update_val (h, peer->endpoint_addr.addr4);
-	else if (peer->endpoint_family == AF_INET6)
-		nm_hash_update_val (h, peer->endpoint_addr.addr6);
+	nm_sock_addr_union_hash_update (&peer->endpoint, h);
 
 	for (i = 0; i < peer->allowed_ips_len; i++)
 		_wireguard_allowed_ip_hash_update (&peer->allowed_ips[i], h);
@@ -418,15 +600,11 @@ _wireguard_peer_cmp (const NMPWireGuardPeer *a,
 	NM_CMP_FIELD (a, b, tx_bytes);
 	NM_CMP_FIELD (a, b, allowed_ips_len);
 	NM_CMP_FIELD (a, b, persistent_keepalive_interval);
-	NM_CMP_FIELD (a, b, endpoint_port);
-	NM_CMP_FIELD (a, b, endpoint_family);
+	NM_CMP_FIELD (a, b, endpoint.sa.sa_family);
 	NM_CMP_FIELD_MEMCMP (a, b, public_key);
 	NM_CMP_FIELD_MEMCMP (a, b, preshared_key);
 
-	if (a->endpoint_family == AF_INET)
-		NM_CMP_FIELD (a, b, endpoint_addr.addr4);
-	else if (a->endpoint_family == AF_INET6)
-		NM_CMP_FIELD_IN6ADDR (a, b, endpoint_addr.addr6);
+	NM_CMP_RETURN (nm_sock_addr_union_cmp (&a->endpoint, &b->endpoint));
 
 	for (i = 0; i < a->allowed_ips_len; i++) {
 		NM_CMP_RETURN (_wireguard_allowed_ip_cmp (&a->allowed_ips[i],
@@ -629,9 +807,12 @@ _nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass)
 	nm_assert (obj);
 	nm_assert (klass);
 
-	memset (obj, 0, sizeof (NMPObject));
-	obj->_class = klass;
-	obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
+	*obj = (NMPObject) {
+		.parent = {
+			.klass      = (const NMDedupMultiObjClass *) klass,
+			._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
+		},
+	};
 }
 
 static NMPObject *
@@ -643,9 +824,12 @@ _nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type)
 	klass = nmp_class_from_type (obj_type);
 	nm_assert (klass);
 
-	memset (obj, 0, sizeof (NMPObject));
-	obj->_class = klass;
-	obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
+	*obj = (NMPObject) {
+		.parent = {
+			.klass      = (const NMDedupMultiObjClass *) klass,
+			._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
+		},
+	};
 	return obj;
 }
 
@@ -896,11 +1080,9 @@ static const char * \
 _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
 { \
 	plat_type *const obj = (plat_type *) _obj; \
-	char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
-	char buf2[NM_UTILS_INET_ADDRSTRLEN]; \
+	_nm_unused char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
+	_nm_unused char buf2[NM_UTILS_INET_ADDRSTRLEN]; \
 	\
-	(void) buf1; \
-	(void) buf2; \
 	g_snprintf (buf, buf_len, \
 	            __VA_ARGS__); \
 	return buf; \
@@ -1636,7 +1818,7 @@ nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *m
  * The flag obj->link.connected depends on the state of other links in the
  * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex
  * of a master, check if the cache contains such a master link that needs
- * toogling of the connected flag.
+ * toggling of the connected flag.
  *
  * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled.
  *   Otherwise, return the link object from inside the cache with the given ifindex.
@@ -1975,6 +2157,8 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	} else if (!ifname && !match_fn)
 		return NULL;
 	else {
+		const NMPObject *obj_best = NULL;
+
 		if (ifname) {
 			if (strlen (ifname) >= IFNAMSIZ)
 				return NULL;
@@ -1986,16 +2170,21 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 		nmp_cache_iter_for_each_link (&iter, head_entry, &link) {
 			obj = NMP_OBJECT_UP_CAST (link);
 
-			if (visible_only && !nmp_object_is_visible (obj))
-				continue;
 			if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
 				continue;
+			if (visible_only && !nmp_object_is_visible (obj))
+				continue;
 			if (match_fn && !match_fn (obj, user_data))
 				continue;
 
-			return obj;
+			/* if there are multiple candidates, prefer the visible ones. */
+			if (   visible_only
+			    || nmp_object_is_visible (obj))
+				return obj;
+			if (!obj_best)
+				obj_best = obj;
 		}
-		return NULL;
+		return obj_best;
 	}
 }
 
@@ -2338,7 +2527,7 @@ nmp_cache_remove_netlink (NMPCache *cache,
  *
  * Returns: how the cache changed.
  *
- * Even if there was no change in the cace (NMP_CACHE_OPS_UNCHANGED), @out_obj_old
+ * Even if there was no change in the cache (NMP_CACHE_OPS_UNCHANGED), @out_obj_old
  * and @out_obj_new will be set accordingly.
  **/
 NMPCacheOpsType
@@ -2609,7 +2798,7 @@ update_done:
 		nm_dedup_multi_entry_reorder (entry_cur, NULL, FALSE);
 		break;
 	default:
-		/* this is an unexecpted case, probably a bug that we need to handle better. */
+		/* this is an unexpected case, probably a bug that we need to handle better. */
 		resync_required = TRUE;
 		break;
 	}
@@ -3096,7 +3285,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_wireguard,
 		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_wireguard,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_wireguard_to_string,
-		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vlan_hash_update,
-		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_wireguard_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_wireguard_cmp,
 	},
 };
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 97be8321..4649441b 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -21,6 +21,8 @@
 #ifndef __NMP_OBJECT_H__
 #define __NMP_OBJECT_H__
 
+#include <netinet/in.h>
+
 #include "nm-utils/nm-obj.h"
 #include "nm-utils/nm-dedup-multi.h"
 #include "nm-platform.h"
@@ -29,6 +31,38 @@ struct udev_device;
 
 /*****************************************************************************/
 
+typedef union {
+	struct sockaddr     sa;
+	struct sockaddr_in  in;
+	struct sockaddr_in6 in6;
+} NMSockAddrUnion;
+
+#define NM_SOCK_ADDR_UNION_INIT_UNSPEC \
+	{ \
+		.sa = { \
+			.sa_family = AF_UNSPEC, \
+		}, \
+	}
+
+int nm_sock_addr_union_cmp (const NMSockAddrUnion *a,
+                            const NMSockAddrUnion *b);
+
+void nm_sock_addr_union_hash_update (const NMSockAddrUnion *a,
+                                     NMHashState *h);
+
+void nm_sock_addr_union_cpy (NMSockAddrUnion *dst,
+                             gconstpointer src /* unaligned (const NMSockAddrUnion *) */);
+
+void nm_sock_addr_union_cpy_untrusted (NMSockAddrUnion *dst,
+                                       gconstpointer src /* unaligned (const NMSockAddrUnion *) */,
+                                       gsize src_len);
+
+const char *nm_sock_addr_union_to_string (const NMSockAddrUnion *sa,
+                                          char *buf,
+                                          gsize len);
+
+/*****************************************************************************/
+
 typedef struct {
 	NMIPAddr addr;
 	guint8 family;
@@ -36,10 +70,12 @@ typedef struct {
 } NMPWireGuardAllowedIP;
 
 typedef struct _NMPWireGuardPeer {
-	NMIPAddr endpoint_addr;
+	NMSockAddrUnion endpoint;
+
 	struct timespec last_handshake_time;
 	guint64 rx_bytes;
 	guint64 tx_bytes;
+
 	union {
 		const NMPWireGuardAllowedIP *allowed_ips;
 		guint _construct_idx_start;
@@ -48,11 +84,11 @@ typedef struct _NMPWireGuardPeer {
 		guint allowed_ips_len;
 		guint _construct_idx_end;
 	};
+
 	guint16 persistent_keepalive_interval;
-	guint16 endpoint_port;
+
 	guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
 	guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN];
-	guint8 endpoint_family;
 } NMPWireGuardPeer;
 
 /*****************************************************************************/
@@ -100,14 +136,14 @@ typedef enum { /*< skip >*/
 	 *
 	 * Also, note that links may be considered invisible. This index type
 	 * expose all links, even invisible ones. For addresses/routes, this
-	 * distiction doesn't exist, as all addresses/routes that are alive
+	 * distinction doesn't exist, as all addresses/routes that are alive
 	 * are visible as well. */
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 
 	/* index for the link objects by ifname. */
 	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
 
-	/* indeces for the visible default-routes, ignoring ifindex.
+	/* indices for the visible default-routes, ignoring ifindex.
 	 * This index only contains two partitions: all visible default-routes,
 	 * separate for IPv4 and IPv6. */
 	NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
@@ -504,7 +540,7 @@ nmp_object_ref (const NMPObject *obj)
 	}
 
 	/* ref and unref accept const pointers. NMPObject is supposed to be shared
-	 * and kept immutable. Disallowing to take/retrun a reference to a const
+	 * and kept immutable. Disallowing to take/return a reference to a const
 	 * NMPObject is cumbersome, because callers are precisely expected to
 	 * keep a ref on the otherwise immutable object. */
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index bedc6916..3704e2ac 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -22,7 +22,7 @@ foreach test_unit: test_units
     'platform/' + test_unit[0],
     test_script,
     timeout: test_unit[3],
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
   )
 endforeach
 
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index ddef8853..d84a806d 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -189,7 +189,7 @@ test_ip4_address_general_2 (void)
 	inet_pton (AF_INET, IP4_ADDRESS, &addr);
 	g_assert (ifindex > 0);
 
-	/* Looks like addresses are not announced by kerenl when the interface
+	/* Looks like addresses are not announced by kernel when the interface
 	 * is down. Link-local IPv6 address is automatically added.
 	 */
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, DEVICE_IFINDEX, NULL));
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 12d91812..6c73a63e 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -52,7 +52,7 @@ test_cleanup_internal (void)
 	inet_pton (AF_INET6, "2001:db8:e:f:1:2:3:4", &gateway6);
 
 	/* Create and set up device */
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL)));
 	accept_signal (link_added);
 	free_signal (link_added);
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
@@ -128,8 +128,7 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_setup_tests (void)
 {
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
 
 	g_test_add_func ("/internal", test_cleanup_internal);
 	/* FIXME: add external cleanup check */
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 1cb2f516..8e29b5ce 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -366,9 +366,12 @@ _nmtstp_assert_ip4_route_exists (const char *file,
 	                    &c);
 
 	if (c != c_exists && c_exists != -1) {
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet4_ntop (network, NULL), plen,
+		         nm_utils_inet4_ntop (network, sbuf),
+		         plen,
 		         metric,
 		         tos,
 		         c_exists,
@@ -821,7 +824,8 @@ _ip_address_add (NMPlatform *platform,
 		gs_free char *s_valid = NULL;
 		gs_free char *s_preferred = NULL;
 		gs_free char *s_label = NULL;
-		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+		char b1[NM_UTILS_INET_ADDRSTRLEN];
+		char b2[NM_UTILS_INET_ADDRSTRLEN];
 
 		ifname = nm_platform_link_get_name (platform, ifindex);
 		g_assert (ifname);
@@ -834,14 +838,14 @@ _ip_address_add (NMPlatform *platform,
 			s_label = g_strdup_printf ("%s:%s", ifname, label);
 
 		if (is_v4) {
-			char s_peer[100];
+			char s_peer[NM_UTILS_INET_ADDRSTRLEN + 50];
 
 			g_assert (flags == 0);
 
 			if (   peer_address->addr4 != address->addr4
 			    || nmtst_get_rand_int () % 2) {
 				/* If the peer is the same as the local address, we can omit it. The result should be identical */
-				g_snprintf (s_peer, sizeof (s_peer), " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
+				nm_sprintf_buf (s_peer, " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
 			} else
 				s_peer[0] = '\0';
 
@@ -1006,7 +1010,7 @@ void nmtstp_ip4_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert_cmpint (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_REPLACE, &route)));
 }
 
 void nmtstp_ip6_route_add (NMPlatform *platform,
@@ -1030,7 +1034,7 @@ void nmtstp_ip6_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert_cmpint (nm_platform_ip6_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip6_route_add (platform, NMP_NLM_FLAG_REPLACE, &route)));
 }
 
 /*****************************************************************************/
@@ -1052,7 +1056,8 @@ _ip_address_del (NMPlatform *platform,
 
 	if (external_command) {
 		const char *ifname;
-		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+		char b1[NM_UTILS_INET_ADDRSTRLEN];
+		char b2[NM_UTILS_INET_ADDRSTRLEN];
 		int success;
 		gboolean had_address;
 
@@ -1198,7 +1203,7 @@ nmtstp_link_veth_add (NMPlatform *platform,
 			nmtstp_assert_wait_for_link (platform, peer, NM_LINK_TYPE_VETH, 10);
 		}
 	} else
-		success = nm_platform_link_veth_add (platform, name, peer, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_veth_add (platform, name, peer, &pllink));
 
 	g_assert (success);
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_VETH);
@@ -1225,7 +1230,7 @@ nmtstp_link_dummy_add (NMPlatform *platform,
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_DUMMY, 100);
 	} else
-		success = nm_platform_link_dummy_add (platform, name, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (platform, name, &pllink));
 
 	g_assert (success);
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_DUMMY);
@@ -1240,7 +1245,8 @@ nmtstp_link_gre_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET_ADDRSTRLEN];
+	char b1[INET_ADDRSTRLEN];
+	char b2[INET_ADDRSTRLEN];
 	NMLinkType link_type;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
@@ -1265,15 +1271,15 @@ nmtstp_link_gre_add (NMPlatform *platform,
 		                                name,
 		                                type,
 		                                dev ?: "",
-		                                nm_utils_inet4_ntop (lnk->local, NULL),
-		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                nm_utils_inet4_ntop (lnk->local, b1),
+		                                nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
-		success = nm_platform_link_gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_gre_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, link_type);
 
@@ -1288,7 +1294,8 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET6_ADDRSTRLEN];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
 	char encap[20];
 	char tclass[20];
 	gboolean encap_ignore;
@@ -1326,8 +1333,8 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 		                                name,
 		                                mode,
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, NULL),
-		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                nm_utils_inet6_ntop (&lnk->local, b1),
+		                                nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit),
@@ -1335,7 +1342,7 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IP6TNL, 100);
 	} else
-		success = nm_platform_link_ip6tnl_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_ip6tnl_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_IP6TNL);
 
@@ -1350,7 +1357,8 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET6_ADDRSTRLEN];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
 	char tclass[20];
 	gboolean tclass_inherit;
 
@@ -1373,8 +1381,8 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 		                                name,
 		                                lnk->is_tap ? "ip6gretap" : "ip6gre",
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, NULL),
-		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                nm_utils_inet6_ntop (&lnk->local, b1),
+		                                nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                lnk->flow_label);
@@ -1385,7 +1393,7 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 			                                      100);
 		}
 	} else
-		success = nm_platform_link_ip6gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_ip6gre_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE);
 
@@ -1400,7 +1408,8 @@ nmtstp_link_ipip_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET_ADDRSTRLEN];
+	char b1[INET_ADDRSTRLEN];
+	char b2[INET_ADDRSTRLEN];
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1417,15 +1426,15 @@ nmtstp_link_ipip_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, NULL),
-		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                nm_utils_inet4_ntop (lnk->local, b1),
+		                                nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IPIP, 100);
 	} else
-		success = nm_platform_link_ipip_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_ipip_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_IPIP);
 
@@ -1473,7 +1482,7 @@ nmtstp_link_macvlan_add (NMPlatform *platform,
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
-		success = nm_platform_link_macvlan_add (platform, name, parent, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_macvlan_add (platform, name, parent, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, link_type);
 
@@ -1488,7 +1497,8 @@ nmtstp_link_sit_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET_ADDRSTRLEN];
+	char b1[INET_ADDRSTRLEN];
+	char b2[INET_ADDRSTRLEN];
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1510,15 +1520,15 @@ nmtstp_link_sit_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, NULL),
-		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                nm_utils_inet4_ntop (lnk->local, b1),
+		                                nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_SIT, 100);
 	} else
-		success = nm_platform_link_sit_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_sit_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_SIT);
 
@@ -1533,8 +1543,8 @@ nmtstp_link_tun_add (NMPlatform *platform,
                      int *out_fd)
 {
 	const NMPlatformLink *pllink = NULL;
-	NMPlatformError plerr;
 	int err;
+	int r;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 	g_assert (lnk);
@@ -1579,8 +1589,8 @@ nmtstp_link_tun_add (NMPlatform *platform,
 			g_error ("failure to add tun/tap device via ip-route");
 	} else {
 		g_assert (lnk->persist || out_fd);
-		plerr = nm_platform_link_tun_add (platform, name, lnk, &pllink, out_fd);
-		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
+		r = nm_platform_link_tun_add (platform, name, lnk, &pllink, out_fd);
+		g_assert_cmpint (r, ==, 0);
 	}
 
 	g_assert (pllink);
@@ -1596,8 +1606,8 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
                        const NMPlatformLnkVxlan *lnk)
 {
 	const NMPlatformLink *pllink = NULL;
-	NMPlatformError plerr;
 	int err;
+	int r;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1607,27 +1617,32 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
 
 	if (external_command) {
 		gs_free char *dev = NULL;
-		gs_free char *local = NULL, *remote = NULL;
+		char local[NM_UTILS_INET_ADDRSTRLEN];
+		char group[NM_UTILS_INET_ADDRSTRLEN];
 
 		if (lnk->parent_ifindex)
 			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
 
 		if (lnk->local)
-			local = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->local, NULL));
+			nm_utils_inet4_ntop (lnk->local, local);
 		else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any)))
-			local = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->local6, NULL));
+			nm_utils_inet6_ntop (&lnk->local6, local);
+		else
+			local[0] = '\0';
 
 		if (lnk->group)
-			remote = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->group, NULL));
+			nm_utils_inet4_ntop (lnk->group, group);
 		else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any)))
-			remote = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->group6, NULL));
+			nm_utils_inet6_ntop (&lnk->group6, group);
+		else
+			group[0] = '\0';
 
 		err = nmtstp_run_command ("ip link add %s type vxlan id %u %s local %s group %s ttl %u tos %02x dstport %u srcport %u %u ageing %u",
 		                          name,
 		                          lnk->id,
 		                          dev ?: "",
 		                          local,
-		                          remote,
+		                          group,
 		                          lnk->ttl,
 		                          lnk->tos,
 		                          lnk->dst_port,
@@ -1641,8 +1656,8 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
 			_LOGI ("Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task.");
 	}
 	if (!pllink) {
-		plerr = nm_platform_link_vxlan_add (platform, name, lnk, &pllink);
-		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
+		r = nm_platform_link_vxlan_add (platform, name, lnk, &pllink);
+		g_assert (NMTST_NM_ERR_SUCCESS (r));
 		g_assert (pllink);
 	}
 
@@ -1702,10 +1717,11 @@ nmtstp_link_get (NMPlatform *platform,
 /*****************************************************************************/
 
 void
-nmtstp_link_del (NMPlatform *platform,
-                 gboolean external_command,
-                 int ifindex,
-                 const char *name)
+nmtstp_link_delete (NMPlatform *platform,
+                    gboolean external_command,
+                    int ifindex,
+                    const char *name,
+                    gboolean require_exist)
 {
 	gint64 end_time;
 	const NMPlatformLink *pllink;
@@ -1718,7 +1734,10 @@ nmtstp_link_del (NMPlatform *platform,
 
 	pllink = nmtstp_link_get (platform, ifindex, name);
 
-	g_assert (pllink);
+	if (!pllink) {
+		g_assert (!require_exist);
+		return;
+	}
 
 	name = name_copy = g_strdup (pllink->name);
 	ifindex = pllink->ifindex;
@@ -2072,7 +2091,7 @@ main (int argc, char **argv)
 
 	result = g_test_run ();
 
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
 
 	g_object_unref (NM_PLATFORM_GET);
 	return result;
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 1baadfa1..048fd9dd 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -21,6 +21,9 @@
 #include <syslog.h>
 #include <string.h>
 #include <arpa/inet.h>
+#include <linux/if.h>
+#include <linux/if_link.h>
+#include <linux/ip6_tunnel.h>
 
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
@@ -324,10 +327,11 @@ const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform,
                                              const char *name,
                                              const NMPlatformLnkVxlan *lnk);
 
-void nmtstp_link_del (NMPlatform *platform,
-                      gboolean external_command,
-                      int ifindex,
-                      const char *name);
+void nmtstp_link_delete (NMPlatform *platform,
+                         gboolean external_command,
+                         int ifindex,
+                         const char *name,
+                         gboolean require_exist);
 
 /*****************************************************************************/
 
@@ -346,9 +350,9 @@ _nmtstp_env1_wrapper_setup (const NmtstTestData *test_data)
 
 	_LOGT ("TEST[%s]: setup", test_data->testpath);
 
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
+
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL)));
 
 	*p_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	g_assert_cmpint (*p_ifindex, >, 0);
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 057490c4..bfd33058 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -47,7 +47,7 @@
 #define MTU 1357
 
 #define _ADD_DUMMY(platform, name) \
-	g_assert_cmpint (nm_platform_link_dummy_add ((platform), (name), NULL), ==, NM_PLATFORM_ERROR_SUCCESS)
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add ((platform), (name), NULL)))
 
 static void
 test_bogus(void)
@@ -77,7 +77,7 @@ test_bogus(void)
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU) != NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (!NMTST_NM_ERR_SUCCESS (nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU)));
 
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
@@ -107,30 +107,30 @@ software_add (NMLinkType link_type, const char *name)
 {
 	switch (link_type) {
 	case NM_LINK_TYPE_DUMMY:
-		return nm_platform_link_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, name, NULL));
 	case NM_LINK_TYPE_BRIDGE:
-		return nm_platform_link_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return NMTST_NM_ERR_SUCCESS (nm_platform_link_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL));
 	case NM_LINK_TYPE_BOND:
 		{
 			gboolean bond0_exists = !!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0");
-			NMPlatformError plerr;
+			int r;
 
-			plerr = nm_platform_link_bond_add (NM_PLATFORM_GET, name, NULL);
+			r = nm_platform_link_bond_add (NM_PLATFORM_GET, name, NULL);
 
 			/* Check that bond0 is *not* automatically created. */
 			if (!bond0_exists)
 				g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0"));
-			return plerr == NM_PLATFORM_ERROR_SUCCESS;
+			return r >= 0;
 		}
 	case NM_LINK_TYPE_TEAM:
-		return nm_platform_link_team_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return NMTST_NM_ERR_SUCCESS (nm_platform_link_team_add (NM_PLATFORM_GET, name, NULL));
 	case NM_LINK_TYPE_VLAN: {
 		SignalData *parent_added;
 		SignalData *parent_changed;
 
 		/* Don't call link_callback for the bridge interface */
 		parent_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, PARENT_NAME);
-		if (nm_platform_link_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
+		if (NMTST_NM_ERR_SUCCESS (nm_platform_link_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL)))
 			accept_signal (parent_added);
 		free_signal (parent_added);
 
@@ -147,7 +147,7 @@ software_add (NMLinkType link_type, const char *name)
 				accept_signals (parent_changed, 1, 2);
 			free_signal (parent_changed);
 
-			return nm_platform_link_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+			return NMTST_NM_ERR_SUCCESS (nm_platform_link_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL));
 		}
 	}
 	default:
@@ -343,7 +343,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	ensure_no_signal (link_added);
 	ensure_no_signal (link_changed);
 	ensure_no_signal (link_removed);
-	nmtstp_link_del (NULL, -1, ifindex, NULL);
+	nmtstp_link_delete (NULL, -1, ifindex, NULL, TRUE);
 	accept_signals (master_changed, 0, 1);
 	accept_signals (link_changed, 0, 1);
 	accept_signal (link_removed);
@@ -439,18 +439,18 @@ test_software (NMLinkType link_type, const char *link_typename)
 	free_signal (link_changed);
 
 	/* Delete */
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
 	accept_signal (link_removed);
 
 	/* Delete again */
-	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME)));
+	g_assert (nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME) <= 0);
 	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
 	/* VLAN: Delete parent */
 	if (link_type == NM_LINK_TYPE_VLAN) {
 		SignalData *link_removed_parent = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, link_callback, vlan_parent);
 
-		nmtstp_link_del (NULL, -1, vlan_parent, NULL);
+		nmtstp_link_delete (NULL, -1, vlan_parent, NULL, TRUE);
 		accept_signal (link_removed_parent);
 		free_signal (link_removed_parent);
 	}
@@ -466,12 +466,20 @@ test_bridge (void)
 	test_software (NM_LINK_TYPE_BRIDGE, "bridge");
 }
 
+static int
+_system (const char *cmd)
+{
+	/* some gcc version really want to warn on -Werror=unused-result. Add a bogus wrapper
+	 * function. */
+	return system (cmd);
+}
+
 static void
 test_bond (void)
 {
 	if (nmtstp_is_root_test () &&
 	    !g_file_test ("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) &&
-	    system("modprobe --show bonding") != 0) {
+	    _system("modprobe --show bonding") != 0) {
 		g_test_skip ("Skipping test for bonding: bonding module not available");
 		return;
 	}
@@ -502,7 +510,7 @@ test_bridge_addr (void)
 
 	nm_utils_hwaddr_aton ("de:ad:be:ef:00:11", addr, sizeof (addr));
 
-	g_assert_cmpint (nm_platform_link_bridge_add (NM_PLATFORM_GET, DEVICE_NAME, addr, sizeof (addr), &plink), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_bridge_add (NM_PLATFORM_GET, DEVICE_NAME, addr, sizeof (addr), &plink)));
 	g_assert (plink);
 	link = *plink;
 	g_assert_cmpstr (link.name, ==, DEVICE_NAME);
@@ -518,13 +526,13 @@ test_bridge_addr (void)
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
-		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE)));
 		g_assert (nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 		g_assert (plink);
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_NONE);
 
-		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE)));
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 		g_assert (plink);
@@ -534,7 +542,7 @@ test_bridge_addr (void)
 	g_assert_cmpint (plink->addr.len, ==, sizeof (addr));
 	g_assert (!memcmp (plink->addr.data, addr, sizeof (addr)));
 
-	nmtstp_link_del (NULL, -1, link.ifindex, link.name);
+	nmtstp_link_delete (NULL, -1, link.ifindex, link.name, TRUE);
 }
 
 /*****************************************************************************/
@@ -554,11 +562,11 @@ test_internal (void)
 	g_assert (!nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
 	/* Add device */
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL)));
 	accept_signal (link_added);
 
 	/* Try to add again */
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_EXISTS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == -NME_PL_EXISTS);
 
 	/* Check device index, name and type */
 	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
@@ -595,7 +603,7 @@ test_internal (void)
 	g_assert (nm_platform_link_supports_vlans (NM_PLATFORM_GET, ifindex));
 
 	/* Set MAC address */
-	g_assert (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac)) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac))));
 	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &addrlen);
 	g_assert (addrlen == sizeof(mac));
 	g_assert (!memcmp (address, mac, addrlen));
@@ -604,12 +612,12 @@ test_internal (void)
 	accept_signal (link_changed);
 
 	/* Set MTU */
-	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU)));
 	g_assert_cmpint (nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex), ==, MTU);
 	accept_signal (link_changed);
 
 	/* Delete device */
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
 	accept_signal (link_removed);
 
 	/* Try to delete again */
@@ -684,6 +692,233 @@ test_external (void)
 
 /*****************************************************************************/
 
+static guint8 *
+_copy_base64 (guint8 *dst, gsize dst_len, const char *base64_src)
+{
+	g_assert (dst);
+	g_assert (dst_len > 0);
+
+	if (!base64_src)
+		memset (dst, 0, dst_len);
+	else {
+		gs_free guint8 *b = NULL;
+		gsize l;
+
+		b = g_base64_decode (base64_src, &l);
+		g_assert (b);
+		g_assert (l == dst_len);
+
+		memcpy (dst, b, dst_len);
+	}
+	return dst;
+}
+
+typedef struct {
+	const char *pri;
+	const char *pub;
+	const char *pre;
+} KeyPair;
+
+static void
+_test_wireguard_change (NMPlatform *platform,
+                        int ifindex,
+                        int test_mode)
+{
+	const KeyPair self_key =
+		{ "yOWEsaXFxX9/DOkQPzqB9RufZOpfSP4LZZCErP0N0Xo=", "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk=" };
+	const KeyPair keys[100] = {
+		{ "+BDHMh11bkheGfvlQpqt8P/H7N1sPXtVi05XraZS0E8=", "QItu7PJadBVXFXGv55CMtVnbRHdrI6E2CGlu2N5oGx4=", "2IvZnKTzbF1UlChznWSsEYtGbPjhYSTT41GXO6zLxvk=" },
+		{ "qGZyV2BO1nyY/FGYd6elBPirwJC9QyZwqbm2OJAgLkY=", "v8L1FEitO0xo+wW/CVVUnALlw0zGveApSFdlITi/5lI=", "/R2c0JmBNGJzT594NQ0mBJ2XJjxt2QUSo+ZiqeY0EQA=" },
+		{ "YDgsIb0oe+9NcxIx2r0HEEPQpRMxmRN0ALoLm9Sh40Q=", "nFPs1HaU7uFBvE9xZCMF8oOAjzLpZ49AzDHOluY1O2E=", "zsYED2Ef7zIHJoRBPcen+w4ktrRsLPEfYwZhWIuXfds=" },
+		{ "kHkosM503LWu43tdYXbNwVOpRrtPgd9XFqcN7k4t6G4=", "b8e092WT+eNmnxCr5WE2QC/MXAjDagfG1g03cs2mBC0=", "VXz0ShGWT7H0CBCg2awfatmOJF15ZtaSMPhMsp+hc3A=" },
+		{ "4C2w5CEnxH59Y2aa6CJXgLdDtWoNMS2UJounRCM1Jkk=", "gC/R9umlnEQL+Qsz/Y64AlsdMge4ECe5/u/JHZCMWSs=", "2bmL5ISr+V5a7l7xFJ695BLIJyBgx8xnxzybkxiRHOg=" },
+		{ "KHJSzFGkXcZf/wbH2rr99SYtGKWbL680wA2AcDE94lo=", "BsN23h4aOi458Q3EgMQsodsWQxd9h/RxqskAUpsXfgg=", "nK4Zv34YKEhjuexhq1SgK4oTd4MZJT5gcpYvEuPjc7Q=" },
+		{ "QGMulXJ9e3AVxtpi8+UUVqPOr/YBWvCNFVsWS9IUnUA=", "kjVclP5Ifi6og2BBCEHKS/aa/WktArB4+ig06lYaVlg=", "0+mmceDPcSRK3vFnYqHd9iAfY+Nyjzf/1KgDeYGlRkQ=" },
+		{ "AOJiDD4y6GA7P7gugjjQG9Cctvc5Y27fajHz6aU3gU4=", "gEnHn6euHtcMEwZBlX6HANPeN9Of+voBDtltS38xDUw=", "wIH1OxgX6GLxx/bnR+t3fbmjGZDTU3WMxp7t1XGezqM=" },
+		{ "COsls2BlCltaIrrq1+FU51cWddlmoPPppSeIDunOxGA=", "+n6WuV8Tb1/iZArTrHsyNqkRHABbavBQt9Me72K2KEc=", "t4baiprSO9ZbKD2/RutOY9cr+yCajQWZGCTnQdrFQj0=" },
+		{ "uHawQq2BRyJlsTPoCa+MfBVnv4MwtRoS+S9FEpFOEVg=", "8lcmr27afeb6iI3BQCaDtvalF2Cl7gxRZgs+nyJ/fEg=", "Eh9o/6W60iujBLIHuRfNrPhOWAn7PambT2JRln9iwA0=" },
+		{ "yL7hmoE/JfRGAotRzx9xOpjfrDA3BFlPEemFiQw40Wk=", "BHK0PHi5kp7rOfQ46oc9xlVpB+pZeZYXTtH7EXr5TwU=", "BS2h2ZZyW0BlYMmLR29xyHEcZ4jtO7rgj1jkz/EEaxU=" },
+		{ "ON8YrTHQgoC5e0mAR9FakZ8hZ/9I7ysuE21sG546J1Y=", "Bm3l5I6iH1tDrv6EgdZU9PzHqp0H26Z6ggmmIypwxy8=", "qKVfbCnK1EI3eC28SsL+jyBnEnIF/nRJQJzDevBFdNQ=" },
+		{ "KGLO0RI0VlQFCG+ETUqk25HdaKUnUPXKOKkTh/w8BXU=", "sBDBwQFC7k8UMdMzuavKBkBjYYKigzvYmGF5rm6muQc=", "BNfZF9d7540pUGGOQCh3iDxAZcPBqX4qqM00GSLPBek=" },
+		{ "KGdWyEodIn7KKI2e4EFK9tT6Bt3bNMFBRFVTKjjJm2E=", "lrbjU/Xn9wDCZQiU8E5fY6igTSozghvo47QeVIIWaUk=", "LczqW48MW8qDQIZYRELsz/VCzVDc5niROvk7OqrTxR0=" },
+		{ "wO3xinUGABgEmX+RJZbBbOtAmBuPPFG6oVdimvoo90w=", "dCIvTzR6EerOOsRKnWly1a9WGzbJ4qc+6t3SSzLgWFk=", "wFj0zpr5PadBoBy0couLuZ1qudZbXLbV/j3UT+AyKeo=" },
+		{ "+JNOBlO4tp9vvQk6UO4r3sILyEgjl+BBoWketZufyn0=", "Q6LSv9y7YQkJEzQ/1mpjJEgOrO8GYPUTgcizjh7Cm34=", "kg7AG9MuN04xPJ5Z0IcNZ4a8d1b/n4GsGIeyA4FaaSE=" },
+		{ "+EJcThLRwjZ+h1CNNFu15HWzznf4u/lPVw8hifTm2Ec=", "Kkn2jFqwBzyIFQfD0OpmePFSmBYmhKagv4pGgkqsWgE=", "jYpxojj8WKYe/XIXMP+uv1Fv0+TKrs83tqfzP0AGdcI=" },
+		{ "+DKFqSMNFmxriEFj3qatuzYeTJ9+xWYspZ4ydL3eC0Q=", "3o37bsg6HhRg/M9+fTlLcFYc2w/Bz9rLQySvvYCKbRE=", "Jb9qoDIBat1EexlgfpRbXa7OflptME8/zt93bldkiVE=" },
+		{ "EH3MjFOMqRoDFQz+hSlJpntWBeH3lTk6WPjTIQjr42o=", "PbPewED/nxSBLdM7AXMj7uS3bCgAAg8M6F4iPLd0b1U=", "pj4+UgOGkpJwlRvX5BRXZzmzAUnDtUtJsS7LzbcJWzw=" },
+		{ "kL6M2KvO+vPBLEc/a0DpEHTibQ1bwaMRT9b9SkzeP0Y=", "pS3G2bHHlkOE6UHP0qVitDuxXgjEaZTviTjNc55RbVs=", "ZVZpWOtYqhX3CpF1kATg/38J6pvUJo8AS1sVYjs3rUE=" },
+		{ "sFlcRLDn36fnew2Ld92IHJwnKifdS3aF1MWRPs6K3Wg=", "OpjUOTiWExaDYULTINB4yQFqc3mnU3RjQzGRV+KtdFY=", "of0V/uoFRNljv/XTt/tXgoquLRH93Ty0KNiaPUpEi2w=" },
+		{ "UJ8hjDsg3jfsnnfPH8Gw9FnCb6taTuviurAfZu+kEFg=", "3byjHksUOv8CNjGGKvHvvrJDURBhCIL5UtfZbgyVWCE=", "9f7dbWif51gGrE7R9LeuewQSrvGFGTOB3ceJC67jSkI=" },
+		{ "iFFPKGIfqeUKY/w72KAZSjd/PGTqCakHYBV10xMDfnI=", "ehneHATNSXtsJTOiPjVSc0QARkihgcfcvoXKFWKfQnI=", "yKdqDBcRwA7RCg4GiY/b5IsWcExPleOBde/hjxc36a4=" },
+		{ "GDGocdPJTPUAllxQo7SpXZKqMPn7lpxQELQUX9ETHmE=", "n0ScNEou4ekfrXRRXvcADLu2Afj8g5D3TuDP/I4KrnY=", "8QhswqAhi/ehhcmCwQF5aSh80TvIGC/gRL5jBn5wOH8=" },
+		{ "UCcrlN8fX2ZdNdhaEBNwktwL+H0ZO7fhaj7rgdUutmw=", "LF8J728ilXs4TphnrgR6r0p7W3912DYnsXkGMEPQnRE=", "cYfMjxl2REYir7frGeB0u+NHAaFYF02ysgpBhOL5ygc=" },
+		{ "SH0657XuIiHidVmViXZF30RUWtkuWXcWWHmKZHTiOG8=", "7k6j49W1u5qgLE5MQUc1osPVW1oPPhzjrGvJ7o9YamY=", "dV2+7rNk/3LR2IcwYg/c+Wvzep1yjY7/u1I+nnlTQ00=" },
+		{ "qFQWs6jzrscV42pGISQyA7JDvFFAvEQCWJi584VHD2g=", "AT63nHKLC17yUvkR4lOVPxCr4DD3QhXmcmecOTn+Amc=", "2Qe2fwJbFcu1CmKpktElOFkSQMqlyvlV3ZUIAd/Dcts=" },
+		{ "6J+yLxgPwWtUbk9I3zbeD8RuK6XkQjJ0wTJ1zSVhflQ=", "NJzMBYyPZjk3eLmgdKaOeWyNER5YZF1mR8Umeiu9f28=", "pp+4+XHw5ZmHGJ7WbZ1xLYRsnTI17QIbb0bzHzYZrBs=" },
+		{ "KJGoWYNVDUWcEMg+E4tljv1LiWAbdRw2QVapYqdFa1Y=", "M2SGk9WVnzYNGnT777G/JE8uUsY2f7mszTwlue73UDE=", "Jg8N7GbhbYB400foFP+OH0v+hCaL1jW61bajSA6EZqc=" },
+		{ "uAgrgppPyIvk8S1CHUmaCaORsgFFfBreB8pxXmbSdXw=", "dJ22bER4fD3qF2/yIGWQ7SgmZ990sy/SbANjmUMkzws=", "mKkd0OoAR9sClmD+k3fL9weBsoCy5GQGz9BP0kAQIjc=" },
+		{ "EI3Q8gePNPrtFyoMcv7hOihQgroF/dfjzPn8yvpGPWQ=", "Y9QhJFeiyuuIZNPU56B6U//ZK+XTTe4EP7h/p3Q+dE8=", "qBVUYw9rTWaxn55nUd55NpCWtxOUSWLt4WJGusiDS8Q=" },
+		{ "oEQvdRm/yHkx+JvlhHGT5RUFrFEUleKb9DCT55EqZ30=", "8hsh/UHwWADTHntOJq4dy0o7ahcNAAlo2rDpjzzrVXk=", "/GF2inW2mPtA26IgFdgOEBbBEerT740wWuP/8NyANdc=" },
+		{ "6OWrGKZKNsgfRezSUw29EnHymcgKyEKvX5/pZQlLmWs=", "oaJeO6YSS2dodNEf97DvgWrYnFFelG5daEdN84jVVGw=", "T5wvTdyVxK0LY96kouLjs06oUfhGChfty8OUL1Mddro=" },
+		{ "mPmzQbh2R+r1DC5hSquzKM1SDrxUdfnBPRPJrpqrgkE=", "BtUHAnYWjDjBI42qBf9dJqezTUikYsF96o6PKEWPrVo=", "MxU8EMmq+vVHpuK/AkFZrZDF2b+VbSqukZLPbNsCcgo=" },
+		{ "8GVxuoo1Veyr+nqxr5Q4vmsMf5qfiXwSlQ4q3+BU60g=", "uSOLe/E9/OjIgUOk0NMBHB45E0+q4Rd+IUO2UOxmKlM=", "+30F+56OE0Sr3wY2clKw4kgE+2XiucMg7xjK6EemXuk=" },
+		{ "qORPKb+qFuU/9TpFbRUupHsqm9iyk9pa6cpik+EVDkg=", "bMZxxd+Z9I0XA1h9U8JEY+/mRxWGnvbXDZ5Dxz7YzS0=", "SmkkqOz4OhHuSL6cxuRm9+Mlt50Sfd0sMDFTC78gqOE=" },
+		{ "2Ko3IYhXKcdOMIJGNpASk9saNSZsI64lyJPOoxpQ2kI=", "xVOc9PxY1VFaZfemmKi+Ei2liHhmeTu+JMa+rS00gnA=", "+398DlW8kWeI2aRaC4QfcrEjwqPKCohyDeWdaI1wvv0=" },
+		{ "CPioGCVpxnym62nH/QoCt1RiiaaxUcuFjvh5kRhjqHA=", "W0XxlBLrZgFKhggMvvv6oFf/RJbfs92qv8JK9e+5i2o=", "bsK2U6CRAUv1uVgYQ7NpqjWWswFIDiDPDEtU1XQygSA=" },
+		{ "AF17siKaeiO85hikYN0IWCMGWqPm1UOoCkXMltJGUVk=", "B+PFos9aN2S5bLxzGZHljRZj41j3rIx8RWu0vDUzq1w=", "Qb8d6iDYv3m1h7PE8j0Exl2cSwpHkim/fJ1S4P7MYvY=" },
+		{ "wMFDBTJzx5tDCBhMkrptYJ8w9EeURjc4xeDQpevxAWo=", "4ec439EXE5WQzvtV9reSX+aMmdq5k7o9Ayt8oQp+RhQ=", "jwQlvdNH5WtSSU10H+fh/JisOlaBaohDPEp/BYnTt1Q=" },
+		{ "4GaJpIFigNDwd31O84pLIMM/o2qhp0ydlI/ydD/2a2Q=", "r/LdkCoK5/BPGdq2+XJO8sCRhI+8ULFmg0887V43PAI=", "Da+3ZZvEJdx4TYFMIDUlbkmytILnSTNxTKX+sQdjMd4=" },
+		{ "gMnojGqCLmMfGp2m31xlKZ/rIV2b8ockw9DPahRyu0c=", "H6tKCTosnM4BXKqflXrkTdJyNlCIZhQ3ZRxfrvSdrDk=", "4Z6K3LKIMV89plcjMb9CzSzJl03SWRe/++geBMZcOtY=" },
+		{ "wKCg22aNNoHnDJ0oAKE46FcSSsREW4AaGn5WxCSeXUs=", "9NDTFC0iPt4HbbWepLHhN5poNTN2fdxJKNadsNT7qzY=", "GSVTOCnfLpJ1VCOLHaKSjCMv7/OlcnQiP5+5woqkud8=" },
+		{ "oCoykq7pcJcg2X2V5TBRzGwn8hzzHC05WUreuotdznY=", "DxfwnbMqr5Wn5SAyFolfEmNQT6l84Oq69ngpg6H6Iio=", "p1RHBuqhuDa1MAQ2lbqmUQFu0CTwYlf73fWZSj9tQhA=" },
+		{ "UO7YVyRUVkcKr7c63VWWV2zj36XD3HyDfLZCqvrZmFc=", "360lzYtIyHq5lv/QXSCe4bL4G2J1jBXFJ8yS+Ycr7Bc=", "RRPQ1XWF1HN8Us4dtfn2eemdjgtWm7U8r7mM3y00NOk=" },
+		{ "eCGFYV/NuGP4H552E8Of1xU+3IvZxGyX+p5UFGW8iHI=", "LqhZ4AS9dQ/MhsQnE5Oy7Q8INXY+P+mrfGY5dtg9SlE=", "SICfqs8T5wP6IzATDCT4ovamBKPdkZ7JP4Cfsb3izec=" },
+		{ "oI3HBZknoIMMZw1BuYMkTBylt25reX7AbCqtWQv8cno=", "B7dUgLgvQhi0RGmvaMrmf26WdjEVrhaiBclVkCKd4AA=", "5O9K9pLXwxFAt5lfMWh4qGbwX1BM7sz0QGYxAnR77dk=" },
+		{ "sBfYn14EFVIS2M/E1aahP7mOmRNbNtyDChDMS1s6aGs=", "FLYv0ZvzxMkc9A7OzhC4P1ZRu1aKIQd7u6gqfdekC3c=", "kaYLcNCXnCLgiB8fleMQuboUJsj5u3YAmXL9x3ywV0M=" },
+		{ "qFwZESU/XYZXUtxwrGsFU9qPAFTzjm7EhTS1Q6ajGlM=", "hraQQaqJCkS2yQXv+ccMOVh9V9a/qgSZJgdMAhrt8ms=", "72oDfnWOn39gdk/ncw8Lv267I0I+m73SwxrpYojpWYk=" },
+		{ "YHhp6Zf/miuc2QXeI2lTezy0lL0pTv2b+nWNkmjYQWI=", "UhNO5arLzF0WlZSgNOx7+IjWN+GSxDdQxZRp8uIwsyI=", "1Q39Nzv2NGI9zWKWMpYLURAMZUg+FP+OboHHzFU8Anc=" },
+		{ "mLBBXKaCJ+7qeBZpS3wxGi/SQ4kLzun+K+QwwdwfJVQ=", "gIq/nh7NwCJ36MvRnyrWHaRWu8lTmwfN2NvsjVl6SXQ=", "AahcNR91GDyJBIP+vC8ZuIV8ukqjSGtd8s+cmjVC2Ao=" },
+		{ "OJG4LZlNNngFtAEQdbVVWVm6QAjOOauGcMZGbQrb40M=", "pFHAC6HaWAOtvTRRVfSHvzG05mp4SJZXKsN/tkSF0kM=", "IoXT3wIqWNxQhYuHWl12ODq/P7RM9LwaqglhmjKg+0g=" },
+		{ "OOwBFOQNhepiqDf04DehQLh1gpBNOluDF1ia752Yfng=", "u715uJ/XhdjXjThCTJ9w6zzXnIhp3VCxhtso1wk+oBQ=", "5x1Ip3Ym0KzDjGhiYjmpeWWr+dgrZlYwfr02GngPOTM=" },
+		{ "eNPFnwkQy1qw+IjFAlrDA6+sIsxbWDlzbNSsBW8R1UA=", "OaOXaAfPb1MRpWadawFje8YZ0oxJgdCIDIP8c5X+r1U=", "NtfaRRD0GqujnaQQoNoBbtovgO4dfVwEmEQx/YgnDpw=" },
+		{ "OLdaZItbtxH3mGqItkibIJp7KV27FrQavjhd5zq6s3Q=", "SLSmAYxkMCGj0DO35cMLkC3NVAqK2VmVFndbOZEdA24=", "SnBO68XQTDjxYbmYaAeEHgLwD2u4D+BPT86raRuUQZM=" },
+		{ "UBQOEz08izwr4eEK/SnQUpkt+TxCjo6Sya/XOGMLOE0=", "wQwrwezI9LzKevGsJJCBHDG8noR0yIEtOK5Rig97SSo=", "DpyS+0d7lrlFWkztsniG2v/j44vcuvWz3sPeghRyb5A=" },
+		{ "mN98iuqUKh67ggUdq9ZIQNZCZM90fgycTVqYKEo+DkY=", "GYdXVW1jpS0dN1q9zMehubP7LfYqs34kszN0bXQqxxA=", "AJPIHffB4uvvJJki4xCG0VORVBbF6bc2mZQqUx+idPc=" },
+		{ "OEd/1it8C3o+NOWxDI3DfLMXVBHJQg15N3E8F8d99l8=", "QL1NcuUkoXxDy7M9VjGslCejcUlnUDHRghFVnr+8fmA=", "nven9Dicl8U6QXuDO8rRNtjd4NYaa90SU+Gmv435XKY=" },
+		{ "AFMCGDu2oAP68miucsi4fmYX2KeRZnsEGv8tQm8JEng=", "1sNxvk8uZhFsBUgxOXmuCMjDAgBbjVeWe9oaFk5Osy0=", "t5iI5XXd56S5q0Y9HC91gzgF9uGjL9FIy6NUaKqkydo=" },
+		{ "CIAwfJghQHHr4YlztN9at6/iWkrEVCGFAxNVuQCuT3I=", "zpUOF1h17g7RpBzrVlN7oTRz6e+dxcDL8OsAtHwgLC8=", "kOSwC1p6Uoti9E9Eg7ViPZwCytuvp5Fr5Buw677aogU=" },
+		{ "sC8vrAVBU0zvhWDRfzfySjvopXm2/cTMkTLmioyO3Es=", "p6H7GWm8NfgyO5OCX/COjvVT4MAnTs9ZUj4uZMK8XHA=", "9Tzqo57V/h7+6nSNAHSBKdmU7ultlvZbAnNKSRlrLi4=" },
+		{ "eI63gjxCZGnzqZxPEi/ifYphXhxIRI2ZxK8jzqo3mmU=", "XyNzEuU9x37fxFCnrZH89Krs5/UqGVx5wNkGfQCAYz0=", "vZ2fTlRPnJQ+q33YdS5p1aweqPGj/kTMc4Uq80FtFjI=" },
+		{ "aNJlGtm79/RS4SQ/PC4YM6LFo9zAqDr2/RjLqk/z/1A=", "lgZ9akPrABmfHQMlfNFnnpAJzGtcsaU9mUjEYKfzZHc=", "d0Xt1Bcgphd1HMI0RneA4VdBbMZL1qNGJAvFhb080eA=" },
+		{ "oONSnHirNh3cuH93Ty0C9AXKebGY+cdF3R0DtPzIQlo=", "TuREKfA8EVQiYWsPx8veUzjN2cz/b72limSLWlrCWxw=", "vEqqKbpZf0EM6EApMUaUH65r3Zr81Y/DSODhE4H7U3Y=" },
+		{ "+D6RyLEaHJ9YF9WDyOlwh87KaNJcc6lqX8Arp6yqHF0=", "EpecjfIo1/EEbmsgUtzEDqLu2ut+SMmzqaBL9Z/MlCA=", "oYfO6/7XQgEYT9zmr4sqFrk0muK/fEv3FfD8MzZzjkE=" },
+		{ "OCmW5KQql2PRMJnsMYQjXlr6TSYUbxJBknqZtXJPSHM=", "ZZR2ghHlCwAJu/XlsEZuNS6XiGPwuXzyMPPywYFapVM=", "fqSCXq+pKJJ6yNvlOi+tyQ9E4Y6kc4kblGrVqN2WuXA=" },
+		{ "APWXDAe8d2ia1CUbf/IzSPXOUjR8TVuJgmISiWw0/EY=", "jrT5P5YCkG+U7cfNTvCKy0GSEgsjwmJtHg+8HBP6ZCA=", "t75aUjZXMPir8Ao0yhVClh9/BdWxSL+11CjK3iELNWk=" },
+		{ "8Nw4sRis7M/6Om+3w5YHXthyMzLGuP48teqdzbHNPlA=", "lj3q3ZYij3ZJ/QunK8n9I00cv/Z+O1TU1kFFl8x3DTE=", "adqB79P7gbXEYYnSd1/UPCwFffTAPXa9kHWynRBYcGo=" },
+		{ "yOupps5XbjV0fIZKnGhrpcxB7yDQzbBILC0UMJyVS1c=", "+MDV/t9UCIdgm3IkH3BZlxaRPJ3lejRmrm4UPApq4mk=", "AOJPmQxsU6hjOd+9mHnF0VL7Afih3P1Fr625xFT4FtY=" },
+		{ "cD2DEl4MBwONuTV0db5XreoVjQAUZFNXqIeFEU3KFkE=", "2CeHrjN7tBX48k4Sgv5fIHG06e57q/ucCL+8DIRmfXw=", "3EUo6MRzs6rSoY/7AFs8wiBiTXPcHzerLh6Xp3aMGKo=" },
+		{ "EA+S3a9ZeOLiRbhTxaT2wkpyDheAmai+UJa6SFGzSm8=", "GGByUKZx/FPa2OkJoqVTHXx+6jrIpIw5rf0rp43MHko=", "BXoDA3yn0JcMV7hHVzEqhlwAORvhToFO1qG00nas92A=" },
+		{ "eBeJi/imBiV52WEqrwAprUQggqdQmvTTmWtLq8pDDkM=", "zCX26ZTOZHLpq5x5aIUL1XhIVoXJLp/zcXwnmFA3jBo=", "Dm/DCxXWYXEsmQgxAD3KREK2PF0bUSnV5WRAaya8s1I=" },
+		{ "8C7p+EQO+CnWUSjHVu3PpeWpUIbLy48zpftZu021plM=", "DxpnF/IbKAh6kmWC5Jpj8iw387EDkrvjsjOb9fbTSng=", "bGrk0OshJB+0oQOK0QGKU8+lotnIDz3oeUnMZGienyM=" },
+		{ "COIez7YcBJiOJCLxxWV5UGLW5/o009YI0aszlD/PiUc=", "eD9USWV37LFIOxlDSHyOmfFqNJFpORRlzEI+HoF/czI=", "n+/ra86gUSF2pNZS51nt2JgrzXnQJl+dWswOq/Ahs94=" },
+		{ "iBBSTG9VLC+T9+ahNaQ4umZoig8o7w1DaeOw+cD2BGc=", "XnAxqDlvGnQ6aakv8ABGHVj07qVQfk4NChZbstTMBxg=", "7KKSwu/4yWr1UzFmNMGtiaSwdYMhP/HKbrQLlABL4UE=" },
+		{ "eNmwattflehr9+KsVqTuwt1YaAc5ONkaIaTQt9Gkhn8=", "1NNVvm++YTTGMKyAXfGOCZ4aDDdFFH5Um3vAg4XimDo=", "bXNrnDTP0pBay9ytZe7xpiKoSi12F7WUXqoIeI++Xvo=" },
+		{ "QJotpmZINx9eptKpkh9j3JlEDcHdWnjEbicdBS7gPXM=", "3gLYKeoruVZ/AYjym0gciDvRHj45UIWyHhNjWj2Wj28=", "NwuUkkE5yOWT7wed7bltgAk71miz3cSooiIDAdv6kKw=" },
+		{ "OAYGuxH70OPQvhVIX4BhSCWUyzAI5H2IkYxKgC/AO0M=", "Rj9iNF/FagkXfdLPqc9LHfaoGR8GlvY3gun2FilE508=", "hkNXLVMlBRsMEaQKkSzevcEK2sMu0AShGKQJMNqdzWM=" },
+		{ "uBrgZ7wLHrOV/0dNiEqo7FjY9VnJqL5eUDHJWAc9QXg=", "3Hnln8ZHfSaK4OzESJe5U6NcLaW56wzfZICzvzefnSo=", "DhXsehe5FAmbUidXT5ZpZIAuu1eF9rkU6cF3FBoKwOE=" },
+		{ "sJknn3CHvx/812EWU3ddLdLLZFBKsc+wx35GXyiRsHY=", "qqa2dNSt0jWozGyqpokP392H5/DOAUUZmUpyZDaUEEE=", "1Dyz8CvmF17oKT/wG2fu3vRzPzgQv8/OY9GJYew4FG8=" },
+		{ "yOumS9HN68ZwIm+5hZol3jFQ0DB4SKuW/ld3y8wioGk=", "6PowsbKj/fKSzXZMAfaSkP3fE+4AThL9xm6ysQzMDxg=", "vF8cKu9X9FxgCjyVZ5RG7nuue8RelF5Qsb8Efme4M4A=" },
+		{ "mISm5vQfPdK72SsaHh6O1/ARvaWCtm+KZNcpTsyt500=", "YCORDQDpb1U8vADdstBgXkg0N7QaAc5VoXJ4QFuA/UY=", "JlrBmfaCgbfEVD9YQq3c03WwwsHWc1nBwp1JkFORC3A=" },
+		{ "cPyu3Qry6qbsiOJKFGRziZ9LJWJ47k3ZSXiGkQXuQm0=", "/cmBZTbqEp8sababPAxGb3OvDAEE7MlwPOwEFHE+7yM=", "lp0Hhc/rVtpT5FtLLccChqDl3El48XtP6Wm6JwjI7jo=" },
+		{ "YKsMYU0SINbPwWw4RDCJV6GnzDlSp1ZNwUw2euGWi0A=", "/KmBReATQbFnLg8YKV0jwhqKeishRoWvlVtMX3550Vk=", "7fXpPSMo1Fw2sOXtjTtvFU+DbZvS/FWB9wAsywWx6R4=" },
+		{ "WD0YI3y71eIp/GXw9i+7scEiQKSBkGihZWE+s6fGmWo=", "RdthAL/qPnAZFb3xBgRMiAtGHNjgokzoKX9iO2K5qhc=", "4dk4HGkT9dBmomGNorDE/hLr/HEFhljtl4zz3M4sG58=" },
+		{ "oD8KWhJYZVhutJRb0kZlZnB22QUXzi2FfPRD0ll65UM=", "MYTBGHh4Ukj97pKj6qcfWmxGNQzmU3/aBOX2f1tfhG0=", "VT1gC+a9nRJzYMi/TPvRVnn3IQlaop/jKmmxZePEME0=" },
+		{ "0Ns/1SOiqR2CpHRG03QNzJJd5gxTm1XJmSkFlugjQ3k=", "KvQAI+ekNOa2xfEvfyc9JGcS+CTUrnnhsKrlyJGJixg=", "J8LmSX6zElX3S9q4PNvh2NKUtAiQ3oHiYjSJ7yErPlY=" },
+		{ "mD6TeF4ezSPXN/csN1OhoAREFSXllI+zl4DUOInVq2Q=", "WmLJ9ep2EqFcSftnYFJsmWyUxqL0zzuSzVEv94PcISM=", "U2+ILy2NDmmfgSW78C8dl8GyHESUc1lXPHPpg5F+gr8=" },
+		{ "SMJoXOYgHz8HSzY+ByeWLcSP5qFwv7YjRe0bcKesRnM=", "DEsNSOY3TEs9J2YgqroQ4xKq8T8xNJQjvvE4UrTItQw=", "Bws1Hk2+lO+JQ7ME16EbwAdsBkWsGvti0Gb6LY2Lrms=" },
+		{ "iGhXF0Hg0tqZmpwAMiolxvbvTPClQ7LlBAspSSyFEHE=", "7Xxzpwl7yRWehHNWTYVtFkdChJdXhtY4Mtw1fA9QcCg=", "Swjfv0PjuaE8Oq3a17BVno5I+q49dZlPwKK1bPUoKNI=" },
+		{ "MIazjx3qTi6Qz3WzhtCPw3i4Q2uZBHcuMoh++ZGFYUk=", "oni8pbFqk9Ya+Fx+911Nl1SN0FD/hR1jwb2RH3t/pRk=", "ZYAFcj67LkbNURYbSnCCWGxAG8QLDGWwbl968mA6ZA4=" },
+		{ "MAadYdiFM2cPuJF19q20Yoo5KJabuR9TUQ9jG5nvA1w=", "5OcE8XV+UPoBVbgqBQdVF62GZCW9DOQEdxrQsktPPBA=", "FCZsEFXouy+xtxv8X7VroXtvPG1Z1HFHL724tz1jcUI=" },
+		{ "+GwxMmD2dee5+QmvXNI0NdP+rNWoSXTN42otbp0aZ24=", "Y0N44baz9ihclCUnv6rRbDqCYu4BxQlBfNnTz3NNe2A=", "/LqSgkVQNkQ/oBiZSgpM9Rw7BJv0RvRpEQpvlizvHy0=" },
+		{ "8FREpCtncOcT7+W2nW4aYSjmSbADtVSH9rIliQZZUH4=", "fTNSd0JeREhXmPfjrmrAu6Lu/yHkB9GyxR3SyO4kZ28=", "262KN/iG/iJEaZeerFm1yVtvhFVGgQFwSvtxTcjZzeg=" },
+		{ "EDhaRQGtscjoSE9wJOnSXoQVtVruIqyzknty+x/vDWo=", "eMmMgws6ZxDIxZ6QSwGjZO1Mx/r5T+fJjSTKGMBk/BU=", "0CyaJV6AG9bZ0C4yeZ/RDsOs9BdNqZpUxAsD30WmJO4=" },
+		{ "CJV0UB2YdvVDG1cs4oiJgHAS+f1FocGr/vGCfiovsWQ=", "9/O9GWZEOXVm7On8lftL27PffRORju8OKl6gZd/74CA=", "A+kXRVNOwIrA5DUa+3v7dpRC+Gbxm23LTiYmOUAXUyY=" },
+		{ "gCjDsJUwZGA7BjYVoCQsvdIgN9Q4lBHlSyKwUrl751A=", "HRwS8T9y2qPYk7JVU/8Y+6cS+Bk8XCLCXxwN/ttbQiI=", "iFotjA6rhUfkDv4S/wspJgEWunEmrlGSGsXcJ0+8laQ=" },
+		{ "6N5pL4gsuK+shHpDxirTnAGdyKXIlYHyfIhtB0njJGA=", "CVZvW7NaN2XMEEKHodghBA9hLCwee/jrmttiWh/CmEg=", "OpPEd3Sp8r6KdjNDTN4bVHETlGJ92BCK74FCdEaDe9g=" },
+		{ "UIPPTUdvhlg8qEDv6JRxM4/8F5ORjJz4ud82QZrgeEY=", "7Nd13z5EpB3ChytvQC1CxvDY7n0H8r2Y7lzLEY8hdEk=", "b22PvgU0M2QfNC7ZGN+RXNe5fjOzMsY32IcHTwLNIqw=" },
+		{ "oBn53Q5fmxKX02PgI6F47Rb+XoLeFQO07ok2tYhk0lE=", "e0gtPDKXCZSoNW1uHqBPQXLfiYgyeqPMU2zZJgPXACI=", "wmjW2wDT2EzFkyaGui7YWNLTRu8Q4eD/GVKM2utZkEs=" },
+	};
+	gs_unref_ptrarray GPtrArray *allowed_ips_keep_alive = NULL;
+	gs_unref_array GArray *peers = NULL;
+	NMPlatformLnkWireGuard lnk_wireguard;
+	int r;
+	guint i;
+
+	allowed_ips_keep_alive = g_ptr_array_new_with_free_func (g_free);
+
+	peers = g_array_new (FALSE, TRUE, sizeof (NMPWireGuardPeer));
+
+	lnk_wireguard = (NMPlatformLnkWireGuard) {
+		.listen_port = 50754,
+		.fwmark = 0x1102,
+	};
+	_copy_base64 (lnk_wireguard.private_key, sizeof (lnk_wireguard.private_key), self_key.pri);
+	_copy_base64 (lnk_wireguard.public_key,  sizeof (lnk_wireguard.public_key),  self_key.pub);
+
+	if (test_mode == 0) {
+		/* no peers. */
+	} else if (NM_IN_SET (test_mode, 1, 2)) {
+		guint num_peers = (test_mode == 1) ? 1 : G_N_ELEMENTS (keys);
+
+		for (i = 0; i < num_peers; i++) {
+			NMPWireGuardPeer peer;
+			char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+			NMSockAddrUnion endpoint;
+			guint i_allowed_ips, n_allowed_ips;
+			NMPWireGuardAllowedIP *allowed_ips;
+
+			if ((i % 2) == 1) {
+				endpoint = (NMSockAddrUnion) {
+					.in = {
+						.sin_family = AF_INET,
+						.sin_addr   = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "192.168.7.%d", i)),
+						.sin_port   = htons (14000 + i),
+					},
+				};
+			} else {
+				endpoint = (NMSockAddrUnion) {
+					.in6 = {
+						.sin6_family = AF_INET6,
+						.sin6_addr   = *nmtst_inet6_from_string (nm_sprintf_buf (s_addr, "a:b:c:e::1:%d", i)),
+						.sin6_port   = htons (16000 + i),
+					},
+				};
+			}
+
+			if (test_mode == 1)
+				n_allowed_ips = 1;
+			else
+				n_allowed_ips = i % 10;
+			allowed_ips = g_new0 (NMPWireGuardAllowedIP, n_allowed_ips);
+			g_ptr_array_add (allowed_ips_keep_alive, allowed_ips);
+			for (i_allowed_ips = 0; i_allowed_ips < n_allowed_ips; i_allowed_ips++) {
+				NMPWireGuardAllowedIP *aip = &allowed_ips[i_allowed_ips];
+
+				aip->family = (i_allowed_ips % 2) ? AF_INET : AF_INET6;
+				if (aip->family == AF_INET) {
+					aip->addr.addr4 = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "10.%u.%u.0", i, i_allowed_ips));
+					aip->mask = 32 - (i_allowed_ips % 8);
+				} else {
+					aip->addr.addr6 = *nmtst_inet6_from_string (nm_sprintf_buf (s_addr, "a:d:f:%02x:%02x::", i, i_allowed_ips));
+					aip->mask = 128 - (i_allowed_ips % 10);
+				}
+			}
+
+			peer = (NMPWireGuardPeer) {
+				.persistent_keepalive_interval = 60+i,
+				.endpoint                      = endpoint,
+				.allowed_ips                   = n_allowed_ips > 0 ? allowed_ips : NULL,
+				.allowed_ips_len               = n_allowed_ips,
+			};
+			_copy_base64 (peer.public_key, sizeof (peer.public_key), keys[i].pub);
+			_copy_base64 (peer.preshared_key, sizeof (peer.preshared_key), (i % 3) ? NULL : keys[i].pre);
+
+			g_array_append_val (peers, peer);
+		}
+	} else
+		g_assert_not_reached ();
+
+	r = nm_platform_link_wireguard_change (platform,
+	                                       ifindex,
+	                                       &lnk_wireguard,
+	                                       (const NMPWireGuardPeer *) peers->data,
+	                                       peers->len,
+	                                       TRUE);
+	g_assert (NMTST_NM_ERR_SUCCESS (r));
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	NMLinkType link_type;
 	int test_mode;
@@ -697,6 +932,7 @@ test_software_detect (gconstpointer user_data)
 	int ifindex, ifindex_parent;
 	const NMPlatformLink *plink;
 	const NMPObject *lnk;
+	int r;
 	guint i_step;
 	const gboolean ext = test_data->external_command;
 	NMPlatformLnkTun lnk_tun;
@@ -897,7 +1133,7 @@ test_software_detect (gconstpointer user_data)
 
 			dummy = nmtstp_link_dummy_add (NM_PLATFORM_GET, FALSE, "dummy-tmp");
 			g_assert_cmpint (dummy->ifindex, ==, i);
-			nmtstp_link_del (NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL);
+			nmtstp_link_delete (NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL, TRUE);
 		}
 
 		if (!nmtstp_link_macvlan_add (NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan))
@@ -1005,6 +1241,19 @@ test_software_detect (gconstpointer user_data)
 		                                 : NULL));
 		break;
 	}
+	case NM_LINK_TYPE_WIREGUARD: {
+		const NMPlatformLink *link;
+
+		r = nm_platform_link_wireguard_add (NM_PLATFORM_GET, DEVICE_NAME, &link);
+		if (r == -EOPNOTSUPP) {
+			g_test_skip ("wireguard not supported (modprobe wireguard?)");
+			goto out_delete_parent;
+		}
+
+		g_assert (NMTST_NM_ERR_SUCCESS (r));
+		g_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (link)) == NMP_OBJECT_TYPE_LINK);
+		break;
+	}
 	default:
 		g_assert_not_reached ();
 	}
@@ -1217,7 +1466,7 @@ test_software_detect (gconstpointer user_data)
 				g_assert_cmpint (plnk->dst_port, ==, 4789);
 				if (   plnk->src_port_min != 0
 				    || plnk->src_port_max != 0) {
-					/* on some kernels, omiting the port range results in setting
+					/* on some kernels, omitting the port range results in setting
 					 * following default port range. */
 					g_assert_cmpint (plnk->src_port_min, ==, 32768);
 					g_assert_cmpint (plnk->src_port_max, ==, 61000);
@@ -1238,14 +1487,26 @@ test_software_detect (gconstpointer user_data)
 			}
 			break;
 		}
+		case NM_LINK_TYPE_WIREGUARD: {
+			const NMPlatformLnkWireGuard *plnk = &lnk->lnk_wireguard;
+
+			g_assert (plnk == nm_platform_link_get_lnk_wireguard (NM_PLATFORM_GET, ifindex, NULL));
+
+			if (plink->n_ifi_flags & IFF_UP) {
+				_test_wireguard_change (NM_PLATFORM_GET, plink->ifindex, test_data->test_mode);
+				if (_LOGD_ENABLED ())
+					_system ("WG_HIDE_KEYS=never wg show all");
+			}
+			break;
+		}
 		default:
 			g_assert_not_reached ();
 		}
 	}
 
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
 out_delete_parent:
-	nmtstp_link_del (NULL, -1, ifindex_parent, PARENT_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex_parent, PARENT_NAME, TRUE);
 }
 
 static void
@@ -1822,8 +2083,8 @@ test_vlan_set_xgress (void)
 		_assert_vlan_flags (ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP);
 	}
 
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
-	nmtstp_link_del (NULL, -1, ifindex_parent, PARENT_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
+	nmtstp_link_delete (NULL, -1, ifindex_parent, PARENT_NAME, TRUE);
 }
 
 /*****************************************************************************/
@@ -1879,7 +2140,7 @@ test_create_many_links_do (guint n_devices)
 		if (EX == 2)
 			nmtstp_run_command_check ("ip link delete %s", name);
 		else
-			nmtstp_link_del (NULL, EX, g_array_index (ifindexes, int, i), name);
+			nmtstp_link_delete (NULL, EX, g_array_index (ifindexes, int, i), name, TRUE);
 	}
 
 	_LOGI (">>> process events after deleting devices...");
@@ -1965,7 +2226,7 @@ test_nl_bugs_veth (void)
 	});
 
 out:
-	nmtstp_link_del (NULL, -1, ifindex_veth0, IFACE_VETH0);
+	nmtstp_link_delete (NULL, -1, ifindex_veth0, IFACE_VETH0, TRUE);
 	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_veth0, IFACE_VETH0));
 	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_veth1, IFACE_VETH1));
 	nmtstp_namespace_handle_release (ns_handle);
@@ -2018,7 +2279,7 @@ again:
 	}
 
 	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_bond0, IFACE_BOND0));
-	nmtstp_link_del (NULL, -1, ifindex_dummy0, IFACE_DUMMY0);
+	nmtstp_link_delete (NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE);
 }
 
 /*****************************************************************************/
@@ -2072,8 +2333,8 @@ again:
 		goto again;
 	}
 
-	nmtstp_link_del (NULL, -1, ifindex_bridge0, IFACE_BRIDGE0);
-	nmtstp_link_del (NULL, -1, ifindex_dummy0, IFACE_DUMMY0);
+	nmtstp_link_delete (NULL, -1, ifindex_bridge0, IFACE_BRIDGE0, TRUE);
+	nmtstp_link_delete (NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE);
 }
 
 /*****************************************************************************/
@@ -2637,8 +2898,8 @@ test_sysctl_rename (void)
 	}
 
 	nm_platform_process_events (PL);
-	nmtstp_link_del (PL, -1, ifindex[0], NULL);
-	nmtstp_link_del (PL, -1, ifindex[1], NULL);
+	nmtstp_link_delete (PL, -1, ifindex[0], NULL, TRUE);
+	nmtstp_link_delete (PL, -1, ifindex[1], NULL, TRUE);
 }
 
 /*****************************************************************************/
@@ -2717,7 +2978,7 @@ test_sysctl_netns_switch (void)
 	else
 		g_assert_cmpint (ifindex_tmp, ==, -1);
 
-	nmtstp_link_del (PL, FALSE, ifindex, NULL);
+	nmtstp_link_delete (PL, FALSE, ifindex, NULL, TRUE);
 }
 
 /*****************************************************************************/
@@ -2800,12 +3061,8 @@ test_ethtool_features_get (void)
 
 		ethtool_features_dump (features);
 
-		if (_LOGT_ENABLED ()) {
-			int ignore;
-
-			ignore = system ("ethtool -k lo");
-			(void) ignore;
-		}
+		if (_LOGT_ENABLED ())
+			_system ("ethtool -k lo");
 
 		if (!do_set) {
 			requested = gfree_keeper->pdata[i_run * 2 - 2];
@@ -2829,9 +3086,9 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_setup_tests (void)
 {
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, SLAVE_NAME));
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME));
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, SLAVE_NAME, FALSE);
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, PARENT_NAME, FALSE);
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, SLAVE_NAME));
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, PARENT_NAME));
@@ -2862,6 +3119,9 @@ _nmtstp_setup_tests (void)
 		test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1);
+		test_software_detect_add ("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0);
+		test_software_detect_add ("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1);
+		test_software_detect_add ("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2);
 
 		g_test_add_func ("/link/software/vlan/set-xgress", test_vlan_set_xgress);
 
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 85b14b57..4c0f686f 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -427,7 +427,7 @@ test_ip4_route_get (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	in_addr_t a;
-	NMPlatformError result;
+	int result;
 	nm_auto_nmpobj NMPObject *route = NULL;
 	const NMPlatformIP4Route *r;
 
@@ -446,7 +446,7 @@ test_ip4_route_get (void)
 	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
 	                                   &route);
 
-	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (result));
 	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP4_ROUTE);
 	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
 	g_assert (route->parent._ref_count == 1);
@@ -565,7 +565,7 @@ test_ip4_route_options (gconstpointer test_data)
 	}
 
 	for (i = 0; i < rts_n; i++)
-		g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i])));
 
 	for (i = 0; i < rts_n; i++) {
 		rts_cmp[i] = rts_add[i];
@@ -589,7 +589,7 @@ test_ip6_route_get (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	const struct in6_addr *a;
-	NMPlatformError result;
+	int result;
 	nm_auto_nmpobj NMPObject *route = NULL;
 	const NMPlatformIP6Route *r;
 
@@ -608,7 +608,7 @@ test_ip6_route_get (void)
 	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
 	                                   &route);
 
-	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (result));
 	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP6_ROUTE);
 	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
 	g_assert (route->parent._ref_count == 1);
@@ -724,7 +724,7 @@ test_ip6_route_options (gconstpointer test_data)
 	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6);
 
 	for (i = 0; i < rts_n; i++)
-		g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i])));
 
 	for (i = 0; i < rts_n; i++) {
 		rts_cmp[i] = rts_add[i];
@@ -823,7 +823,7 @@ again_find_idx:
 			order_idx[order_len++] = idx;
 
 			r->ifindex = iface_data[idx].ifindex;
-			g_assert (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r) == NM_PLATFORM_ERROR_SUCCESS);
+			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;
 			idx = order_idx[i];
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index b3bb2bb6..0df3d2d8 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -30,7 +30,9 @@
 #include <net/ethernet.h>
 #include <unistd.h>
 #include <linux/nl80211.h>
+#include <linux/if.h>
 
+#include "nm-utils/nm-errno.h"
 #include "platform/nm-netlink.h"
 #include "nm-wifi-utils-private.h"
 #include "platform/nm-platform.h"
@@ -38,11 +40,16 @@
 #include "nm-utils.h"
 
 #define _NMLOG_PREFIX_NAME      "wifi-nl80211"
-#define _NMLOG(level, domain, ...) \
+#define _NMLOG_DOMAIN           LOGD_PLATFORM | LOGD_WIFI
+#define _NMLOG(level, ...) \
 	G_STMT_START { \
-		nm_log ((level), (domain), NULL, NULL, \
-		        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-		        _NMLOG_PREFIX_NAME \
+		char _ifname_buf[IFNAMSIZ]; \
+		const char *_ifname = self ? nmp_utils_if_indextoname (self->parent.ifindex, _ifname_buf) : NULL; \
+		\
+		nm_log ((level), _NMLOG_DOMAIN, _ifname ?: NULL, NULL, \
+		        "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        _NMLOG_PREFIX_NAME, \
+		        NM_PRINT_FMT_QUOTED (_ifname, " (", _ifname, ")", "") \
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
@@ -103,16 +110,16 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-nl80211_alloc_msg (NMWifiUtilsNl80211 *nl80211, guint32 cmd, guint32 flags)
+nl80211_alloc_msg (NMWifiUtilsNl80211 *self, guint32 cmd, guint32 flags)
 {
-	return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags);
+	return _nl80211_alloc_msg (self->id, self->parent.ifindex, self->phy, cmd, flags);
 }
 
 static int
-_nl80211_send_and_recv (struct nl_sock *nl_sock,
-                        struct nl_msg *msg,
-                        int (*valid_handler) (struct nl_msg *, void *),
-                        void *valid_data)
+nl80211_send_and_recv (NMWifiUtilsNl80211 *self,
+                       struct nl_msg *msg,
+                       int (*valid_handler) (struct nl_msg *, void *),
+                       void *valid_data)
 {
 	int err;
 	int done = 0;
@@ -129,7 +136,7 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 
 	g_return_val_if_fail (msg != NULL, -ENOMEM);
 
-	err = nl_send_auto (nl_sock, msg);
+	err = nl_send_auto (self->nl_sock, msg);
 	if (err < 0)
 		return err;
 
@@ -137,19 +144,18 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 	 * done will be 1, on error it will be < 0.
 	 */
 	while (!done) {
-		err = nl_recvmsgs (nl_sock, &cb);
+		err = nl_recvmsgs (self->nl_sock, &cb);
 		if (err < 0 && err != -EAGAIN) {
 			/* Kernel scan list can change while we are dumping it, as new scan
 			 * results from H/W can arrive. BSS info is assured to be consistent
 			 * and we don't need consistent view of whole scan list. Hence do
 			 * not warn on DUMP_INTR error for get scan command.
 			 */
-			if (err == -NLE_DUMP_INTR &&
+			if (err == -NME_NL_DUMP_INTR &&
 			    genlmsg_hdr (nlmsg_hdr (msg))->cmd == NL80211_CMD_GET_SCAN)
 				break;
 
-			_LOGW (LOGD_WIFI, "nl_recvmsgs() error: (%d) %s",
-			       err, nl_geterror (err));
+			_LOGW ("nl_recvmsgs() error: (%d) %s", err, nm_strerror (err));
 			break;
 		}
 	}
@@ -159,22 +165,12 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 	return err;
 }
 
-static int
-nl80211_send_and_recv (NMWifiUtilsNl80211 *nl80211,
-                       struct nl_msg *msg,
-                       int (*valid_handler) (struct nl_msg *, void *),
-                       void *valid_data)
-{
-	return _nl80211_send_and_recv (nl80211->nl_sock, msg,
-	                               valid_handler, valid_data);
-}
-
 static void
 dispose (GObject *object)
 {
-	NMWifiUtilsNl80211 *nl80211 = NM_WIFI_UTILS_NL80211 (object);
+	NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211 (object);
 
-	g_clear_pointer (&nl80211->freqs, g_free);
+	g_clear_pointer (&self->freqs, g_free);
 }
 
 struct nl80211_iface_info {
@@ -213,15 +209,15 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 static NM80211Mode
 wifi_nl80211_get_mode (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_iface_info iface_info = {
 		.mode = NM_802_11_MODE_UNKNOWN,
 	};
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_INTERFACE, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_INTERFACE, 0);
 
-	if (nl80211_send_and_recv (nl80211, msg, nl80211_iface_info_handler,
+	if (nl80211_send_and_recv (self, msg, nl80211_iface_info_handler,
 	                           &iface_info) < 0)
 		return NM_802_11_MODE_UNKNOWN;
 
@@ -231,11 +227,11 @@ wifi_nl80211_get_mode (NMWifiUtils *data)
 static gboolean
 wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_INTERFACE, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_SET_INTERFACE, 0);
 
 	switch (mode) {
 	case NM_802_11_MODE_INFRA:
@@ -251,7 +247,7 @@ wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 		g_assert_not_reached ();
 	}
 
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 	return err >= 0;
 
 nla_put_failure:
@@ -261,14 +257,14 @@ nla_put_failure:
 static gboolean
 wifi_nl80211_set_powersave (NMWifiUtils *data, guint32 powersave)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_POWER_SAVE, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_SET_POWER_SAVE, 0);
 	NLA_PUT_U32 (msg, NL80211_ATTR_PS_STATE,
 	             powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED);
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 	return err >= 0;
 
 nla_put_failure:
@@ -318,13 +314,13 @@ nl80211_get_wake_on_wlan_handler (struct nl_msg *msg, void *arg)
 static NMSettingWirelessWakeOnWLan
 wifi_nl80211_get_wake_on_wlan (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	NMSettingWirelessWakeOnWLan wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_WOWLAN, 0);
 
-	nl80211_send_and_recv (nl80211, msg, nl80211_get_wake_on_wlan_handler, &wowl);
+	nl80211_send_and_recv (self, msg, nl80211_get_wake_on_wlan_handler, &wowl);
 
 	return wowl;
 }
@@ -332,7 +328,7 @@ wifi_nl80211_get_wake_on_wlan (NMWifiUtils *data)
 static gboolean
 wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nlattr *triggers;
 	int err;
@@ -340,7 +336,7 @@ wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wo
 	if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
 		return TRUE;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_WOWLAN, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_SET_WOWLAN, 0);
 	if (!msg)
 		return FALSE;
 
@@ -363,7 +359,7 @@ wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wo
 
 	nla_nest_end(msg, triggers);
 
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 
 	return err >= 0;
 
@@ -491,25 +487,25 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_bss_info (NMWifiUtilsNl80211 *nl80211,
+nl80211_get_bss_info (NMWifiUtilsNl80211 *self,
                       struct nl80211_bss_info *bss_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
 	memset (bss_info, 0, sizeof (*bss_info));
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
 
-	nl80211_send_and_recv (nl80211, msg, nl80211_bss_dump_handler, bss_info);
+	nl80211_send_and_recv (self, msg, nl80211_bss_dump_handler, bss_info);
 }
 
 static guint32
 wifi_nl80211_get_freq (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
-	nl80211_get_bss_info (nl80211, &bss_info);
+	nl80211_get_bss_info (self, &bss_info);
 
 	return bss_info.freq;
 }
@@ -517,12 +513,12 @@ wifi_nl80211_get_freq (NMWifiUtils *data)
 static guint32
 wifi_nl80211_find_freq (NMWifiUtils *data, const guint32 *freqs)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	int i;
 
-	for (i = 0; i < nl80211->num_freqs; i++) {
+	for (i = 0; i < self->num_freqs; i++) {
 		while (*freqs) {
-			if (nl80211->freqs[i] == *freqs)
+			if (self->freqs[i] == *freqs)
 				return *freqs;
 			freqs++;
 		}
@@ -533,10 +529,10 @@ wifi_nl80211_find_freq (NMWifiUtils *data, const guint32 *freqs)
 static gboolean
 wifi_nl80211_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
-	nl80211_get_bss_info (nl80211, &bss_info);
+	nl80211_get_bss_info (self, &bss_info);
 
 	if (bss_info.valid)
 		memcpy (out_bssid, bss_info.bssid, ETH_ALEN);
@@ -615,7 +611,7 @@ nl80211_station_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_ap_info (NMWifiUtilsNl80211 *nl80211,
+nl80211_get_ap_info (NMWifiUtilsNl80211 *self,
                      struct nl80211_station_info *sta_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -623,15 +619,15 @@ nl80211_get_ap_info (NMWifiUtilsNl80211 *nl80211,
 
 	memset (sta_info, 0, sizeof (*sta_info));
 
-	nl80211_get_bss_info (nl80211, &bss_info);
+	nl80211_get_bss_info (self, &bss_info);
 	if (!bss_info.valid)
 		return;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_STATION, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_STATION, 0);
 	if (msg) {
 		NLA_PUT (msg, NL80211_ATTR_MAC, ETH_ALEN, bss_info.bssid);
 
-		nl80211_send_and_recv (nl80211, msg, nl80211_station_handler, sta_info);
+		nl80211_send_and_recv (self, msg, nl80211_station_handler, sta_info);
 		if (!sta_info->signal_valid) {
 			/* Fall back to bss_info signal quality (both are in percent) */
 			sta_info->signal = bss_info.beacon_signal;
@@ -647,10 +643,10 @@ nla_put_failure:
 static guint32
 wifi_nl80211_get_rate (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
-	nl80211_get_ap_info (nl80211, &sta_info);
+	nl80211_get_ap_info (self, &sta_info);
 
 	return sta_info.txrate;
 }
@@ -658,21 +654,21 @@ wifi_nl80211_get_rate (NMWifiUtils *data)
 static int
 wifi_nl80211_get_qual (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
-	nl80211_get_ap_info (nl80211, &sta_info);
+	nl80211_get_ap_info (self, &sta_info);
 	return sta_info.signal;
 }
 
 static gboolean
 wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
-	msg = nl80211_alloc_msg (nl80211,
+	msg = nl80211_alloc_msg (self,
 	                         running
 	                           ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */
 	                           : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */,
@@ -690,7 +686,7 @@ wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 		             5000);
 	}
 
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 	return err >= 0;
 
 nla_put_failure:
@@ -698,6 +694,7 @@ nla_put_failure:
 }
 
 struct nl80211_device_info {
+	NMWifiUtilsNl80211 *self;
 	int phy;
 	guint32 *freqs;
 	int num_freqs;
@@ -723,6 +720,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nl80211_device_info *info = arg;
+	NMWifiUtilsNl80211 *self = info->self;
 	struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1];
 	struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
 	struct nlattr *nl_band;
@@ -872,8 +870,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 			case WLAN_CIPHER_SUITE_SMS4:
 				break;
 			default:
-				_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-				       "don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.",
+				_LOGD ("don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.",
 				       ciphers[i]);
 				break;
 			}
@@ -929,86 +926,66 @@ nm_wifi_utils_nl80211_class_init (NMWifiUtilsNl80211Class *klass)
 NMWifiUtils *
 nm_wifi_utils_nl80211_new (int ifindex, struct nl_sock *genl)
 {
-	gs_unref_object NMWifiUtilsNl80211 *nl80211 = NULL;
+	gs_unref_object NMWifiUtilsNl80211 *self = NULL;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	struct nl80211_device_info device_info = {};
-	char ifname[IFNAMSIZ];
+	struct nl80211_device_info device_info = { };
 
 	if (!genl)
 		return NULL;
 
-	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
-		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
-		       "can't determine interface name for ifindex %d", ifindex);
-		nm_sprintf_buf (ifname, "if %d", ifindex);
-	}
-
-	nl80211 = g_object_new (NM_TYPE_WIFI_UTILS_NL80211, NULL);
+	self = g_object_new (NM_TYPE_WIFI_UTILS_NL80211, NULL);
 
-	nl80211->parent.ifindex = ifindex;
-	nl80211->nl_sock = genl;
+	self->parent.ifindex = ifindex;
+	self->nl_sock = genl;
 
-	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
-	if (nl80211->id < 0) {
-		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\"");
+	self->id = genl_ctrl_resolve (self->nl_sock, "nl80211");
+	if (self->id < 0) {
+		_LOGD ("genl_ctrl_resolve: failed to resolve \"nl80211\"");
 		return NULL;
 	}
 
-	nl80211->phy = -1;
+	self->phy = -1;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WIPHY, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_WIPHY, 0);
 
-	if (nl80211_send_and_recv (nl80211, msg, nl80211_wiphy_info_handler,
+	device_info.self = self;
+	if (nl80211_send_and_recv (self, msg, nl80211_wiphy_info_handler,
 	                           &device_info) < 0) {
-		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): NL80211_CMD_GET_WIPHY request failed",
-		       ifname);
+		_LOGD ("NL80211_CMD_GET_WIPHY request failed");
 		return NULL;
 	}
 
 	if (!device_info.success) {
-		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): NL80211_CMD_GET_WIPHY request indicated failure",
-		       ifname);
+		_LOGD ("NL80211_CMD_GET_WIPHY request indicated failure");
 		return NULL;
 	}
 
 	if (!device_info.supported) {
-		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): driver does not fully support nl80211, falling back to WEXT",
-		       ifname);
+		_LOGD ("driver does not fully support nl80211, falling back to WEXT");
 		return NULL;
 	}
 
 	if (!device_info.can_scan_ssid) {
-		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): driver does not support SSID scans",
-		       ifname);
+		_LOGE ("driver does not support SSID scans");
 		return NULL;
 	}
 
 	if (device_info.num_freqs == 0 || device_info.freqs == NULL) {
-		nm_log_err (LOGD_PLATFORM | LOGD_WIFI,
-		            "(%s): driver reports no supported frequencies",
-		            ifname);
+		_LOGE ("driver reports no supported frequencies");
 		return NULL;
 	}
 
 	if (device_info.caps == 0) {
-		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): driver doesn't report support of any encryption",
-		       ifname);
+		_LOGE ("driver doesn't report support of any encryption");
 		return NULL;
 	}
 
-	nl80211->phy = device_info.phy;
-	nl80211->freqs = device_info.freqs;
-	nl80211->num_freqs = device_info.num_freqs;
-	nl80211->parent.caps = device_info.caps;
-	nl80211->can_wowlan = device_info.can_wowlan;
+	self->phy = device_info.phy;
+	self->freqs = device_info.freqs;
+	self->num_freqs = device_info.num_freqs;
+	self->parent.caps = device_info.caps;
+	self->can_wowlan = device_info.can_wowlan;
 
-	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
-	       "(%s): using nl80211 for WiFi device control",
-	       ifname);
-	return (NMWifiUtils *) g_steal_pointer (&nl80211);
+	_LOGD ("using nl80211 for Wi-Fi device control");
+	return (NMWifiUtils *) g_steal_pointer (&self);
 }
diff --git a/src/platform/wifi/nm-wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index e52ae5a3..597f3152 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -511,7 +511,7 @@ wifi_wext_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
 
 		errsv = errno;
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
-		       "(%s): error setting SSID to '%s': %s",
+		       "(%s): error setting SSID to %s: %s",
 		       ifname,
 		       (ssid_str = _nm_utils_ssid_to_string_arr (ssid, len)),
 		       strerror (errsv));
@@ -751,7 +751,7 @@ nm_wifi_utils_wext_new (int ifindex, gboolean check_scan)
 		wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
 
 	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
-	       "(%s): using WEXT for WiFi device control",
+	       "(%s): using WEXT for Wi-Fi device control",
 	       ifname);
 
 	return (NMWifiUtils *) wext;
@@ -771,7 +771,7 @@ nm_wifi_utils_wext_is_wifi (const char *iface)
 	/* performing an ioctl on a non-existing name may cause the automatic
 	 * loading of kernel modules, which should be avoided.
 	 *
-	 * Usually, we should thus make sure that an inteface with this name
+	 * Usually, we should thus make sure that an interface with this name
 	 * exists.
 	 *
 	 * Note that wifi_wext_is_wifi() has only one caller which just verified
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/platform/wifi/nm-wifi-utils.h
index 6cd178bd..36148b5a 100644
--- a/src/platform/wifi/nm-wifi-utils.h
+++ b/src/platform/wifi/nm-wifi-utils.h
@@ -62,7 +62,7 @@ gboolean nm_wifi_utils_get_bssid (NMWifiUtils *data, guint8 *out_bssid);
 /* Returns current bitrate in Kbps */
 guint32 nm_wifi_utils_get_rate (NMWifiUtils *data);
 
-/* Returns quality 0 - 100% on succes, or -1 on error */
+/* Returns quality 0 - 100% on success, or -1 on error */
 int nm_wifi_utils_get_qual (NMWifiUtils *data);
 
 /* Tells the driver DHCP or SLAAC is running */
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
index 0544539a..f8708bc6 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -21,15 +21,23 @@
 
 #include "nm-wpan-utils.h"
 
+#include <linux/if.h>
+
+#include "nm-utils/nm-errno.h"
 #include "platform/linux/nl802154.h"
 #include "platform/nm-netlink.h"
+#include "platform/nm-platform-utils.h"
 
 #define _NMLOG_PREFIX_NAME "wpan-nl802154"
 #define _NMLOG(level, domain, ...) \
 	G_STMT_START { \
-		nm_log ((level), (domain), NULL, NULL, \
-		        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-		        _NMLOG_PREFIX_NAME \
+		char _ifname_buf[IFNAMSIZ]; \
+		const char *_ifname = self ? nmp_utils_if_indextoname (self->ifindex, _ifname_buf) : NULL; \
+		\
+		nm_log ((level), (domain), _ifname ?: NULL, NULL, \
+		        "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        _NMLOG_PREFIX_NAME, \
+		        NM_PRINT_FMT_QUOTED (_ifname, " (", _ifname, ")", "") \
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
@@ -95,10 +103,10 @@ nl802154_alloc_msg (NMWpanUtils *self, guint32 cmd, guint32 flags)
 }
 
 static int
-_nl802154_send_and_recv (struct nl_sock *nl_sock,
-                         struct nl_msg *msg,
-                         int (*valid_handler) (struct nl_msg *, void *),
-                         void *valid_data)
+nl802154_send_and_recv (NMWpanUtils *self,
+                        struct nl_msg *msg,
+                        int (*valid_handler) (struct nl_msg *, void *),
+                        void *valid_data)
 {
 	int err;
 	int done = 0;
@@ -115,7 +123,7 @@ _nl802154_send_and_recv (struct nl_sock *nl_sock,
 
 	g_return_val_if_fail (msg != NULL, -ENOMEM);
 
-	err = nl_send_auto (nl_sock, msg);
+	err = nl_send_auto (self->nl_sock, msg);
 	if (err < 0)
 		return err;
 
@@ -123,10 +131,10 @@ _nl802154_send_and_recv (struct nl_sock *nl_sock,
 	 * done will be 1, on error it will be < 0.
 	 */
 	while (!done) {
-		err = nl_recvmsgs (nl_sock, &cb);
+		err = nl_recvmsgs (self->nl_sock, &cb);
 		if (err < 0 && err != -EAGAIN) {
 			_LOGW (LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s",
-			       err, nl_geterror (err));
+			       err, nm_strerror (err));
 			break;
 		}
 	}
@@ -136,16 +144,6 @@ _nl802154_send_and_recv (struct nl_sock *nl_sock,
 	return err;
 }
 
-static int
-nl802154_send_and_recv (NMWpanUtils *self,
-                        struct nl_msg *msg,
-                        int (*valid_handler) (struct nl_msg *, void *),
-                        void *valid_data)
-{
-	return _nl802154_send_and_recv (self->nl_sock, msg,
-	                                valid_handler, valid_data);
-}
-
 struct nl802154_interface {
 	guint16 pan_id;
 	guint16 short_addr;
@@ -248,6 +246,24 @@ nla_put_failure:
 	return FALSE;
 }
 
+gboolean
+nm_wpan_utils_set_channel (NMWpanUtils *self, guint8 page, guint8 channel)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int err;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_SET_CHANNEL, 0);
+	NLA_PUT_U8 (msg, NL802154_ATTR_PAGE, page);
+	NLA_PUT_U8 (msg, NL802154_ATTR_CHANNEL, channel);
+	err = nl802154_send_and_recv (self, msg, NULL, NULL);
+	return err >= 0;
+
+nla_put_failure:
+	return FALSE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -264,23 +280,22 @@ NMWpanUtils *
 nm_wpan_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan)
 {
 	NMWpanUtils *self;
-	int id;
 
 	g_return_val_if_fail (ifindex > 0, NULL);
 
 	if (!genl)
 		return NULL;
 
-	id = genl_ctrl_resolve (genl, "nl802154");
-	if (id < 0) {
-		_LOGD (LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
-		return NULL;
-	}
-
 	self = g_object_new (NM_TYPE_WPAN_UTILS, NULL);
 	self->ifindex = ifindex;
 	self->nl_sock = genl;
-	self->id = id;
+	self->id = genl_ctrl_resolve (genl, "nl802154");
+
+	if (self->id < 0) {
+		_LOGD (LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
+		g_object_unref (self);
+		return NULL;
+	}
 
 	return self;
 }
diff --git a/src/platform/wpan/nm-wpan-utils.h b/src/platform/wpan/nm-wpan-utils.h
index f7d0c03e..1b54ec49 100644
--- a/src/platform/wpan/nm-wpan-utils.h
+++ b/src/platform/wpan/nm-wpan-utils.h
@@ -44,4 +44,6 @@ gboolean nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id);
 guint16 nm_wpan_utils_get_short_addr (NMWpanUtils *self);
 gboolean nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr);
 
+gboolean nm_wpan_utils_set_channel (NMWpanUtils *self, guint8 page, guint8 channel);
+
 #endif  /* __WPAN_UTILS_H__ */