summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
committerMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
commit494f296a3baab08522617b24b1f126d8f9a17502 (patch)
treec8ef32fb0dd1c4ff35a0b38e787abb58692de0cd /src/platform
parent54f6333410ffd570e62717d9e77c5c987175e397 (diff)
Imported Upstream version 1.1.90 upstream/1.1.90
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/Makefile.in18
-rw-r--r--src/platform/nm-fake-platform.c379
-rw-r--r--src/platform/nm-linux-platform.c6473
-rw-r--r--src/platform/nm-linux-platform.h2
-rw-r--r--src/platform/nm-platform-utils.c3
-rw-r--r--src/platform/nm-platform.c1731
-rw-r--r--src/platform/nm-platform.h467
-rw-r--r--src/platform/nmp-object.c663
-rw-r--r--src/platform/nmp-object.h114
-rw-r--r--src/platform/tests/Makefile.am6
-rw-r--r--src/platform/tests/Makefile.in24
-rw-r--r--src/platform/tests/monitor.c44
-rw-r--r--src/platform/tests/test-address.c278
-rw-r--r--src/platform/tests/test-cleanup.c6
-rw-r--r--src/platform/tests/test-common.c1222
-rw-r--r--src/platform/tests/test-common.h131
-rw-r--r--src/platform/tests/test-general.c3
-rw-r--r--src/platform/tests/test-link.c1351
-rw-r--r--src/platform/tests/test-nmp-object.c6
-rw-r--r--src/platform/tests/test-route.c38
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c287
-rw-r--r--src/platform/wifi/wifi-utils-private.h8
-rw-r--r--src/platform/wifi/wifi-utils-wext.c60
-rw-r--r--src/platform/wifi/wifi-utils.c17
-rw-r--r--src/platform/wifi/wifi-utils.h7
25 files changed, 9462 insertions, 3876 deletions
diff --git a/src/platform/Makefile.in b/src/platform/Makefile.in
index e8b73294..854c6712 100644
--- a/src/platform/Makefile.in
+++ b/src/platform/Makefile.in
@@ -206,7 +206,6 @@ BLUEZ5_LIBS = @BLUEZ5_LIBS@
 CC = @CC@
 CCDEPMODE = @CCDEPMODE@
 CFLAGS = @CFLAGS@
-CKDB_PATH = @CKDB_PATH@
 CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
 CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
 CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
@@ -218,8 +217,6 @@ CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
 DBUS_CFLAGS = @DBUS_CFLAGS@
-DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
-DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
 DBUS_LIBS = @DBUS_LIBS@
 DBUS_SYS_DIR = @DBUS_SYS_DIR@
 DEFS = @DEFS@
@@ -229,6 +226,7 @@ DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
 DNSMASQ_PATH = @DNSMASQ_PATH@
+DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
 DUMPBIN = @DUMPBIN@
 ECHO_C = @ECHO_C@
@@ -283,16 +281,13 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
-IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@
-IWMX_SDK_LIBS = @IWMX_SDK_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
+LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
+LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
 LIBDL = @LIBDL@
-LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
-LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
-LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -329,6 +324,8 @@ NEWT_LIBS = @NEWT_LIBS@
 NM = @NM@
 NMEDIT = @NMEDIT@
 NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT = @NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT@
+NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT = @NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT@
+NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT = @NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT@
 NM_MAJOR_VERSION = @NM_MAJOR_VERSION@
 NM_MICRO_VERSION = @NM_MICRO_VERSION@
 NM_MINOR_VERSION = @NM_MINOR_VERSION@
@@ -357,7 +354,6 @@ POLKIT_LIBS = @POLKIT_LIBS@
 POSUB = @POSUB@
 PPPD_PATH = @PPPD_PATH@
 PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@
-PPPOE_PATH = @PPPOE_PATH@
 QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
@@ -372,6 +368,8 @@ SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@
 SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@
 SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@
 SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
 SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@
 SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@
 SYSTEM_CA_PATH = @SYSTEM_CA_PATH@
@@ -432,6 +430,7 @@ mkdir_p = @mkdir_p@
 nmbinary = @nmbinary@
 nmconfdir = @nmconfdir@
 nmdatadir = @nmdatadir@
+nmlibdir = @nmlibdir@
 nmrundir = @nmrundir@
 nmstatedir = @nmstatedir@
 oldincludedir = @oldincludedir@
@@ -439,6 +438,7 @@ pdfdir = @pdfdir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index d5aeb030..a4eae902 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -26,15 +26,41 @@
 #include <netinet/in.h>
 #include <linux/rtnetlink.h>
 
-#include "gsystem-local-alloc.h"
 #include "nm-utils.h"
+#include "nmp-object.h"
 #include "NetworkManagerUtils.h"
 #include "nm-fake-platform.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 
 #include "nm-test-utils.h"
 
-#define debug(format, ...) nm_log_dbg (LOGD_PLATFORM, format, __VA_ARGS__)
+/*********************************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "platform-fake"
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+
+#define _LOG(level, domain, self, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            char __prefix[32]; \
+            const char *__p_prefix = _NMLOG_PREFIX_NAME; \
+            const void *const __self = (self); \
+            \
+            if (__self && __self != nm_platform_try_get ()) { \
+                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
+                __p_prefix = __prefix; \
+            } \
+            _nm_log (__level, __domain, 0, \
+                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*********************************************************************************************/
 
 typedef struct {
 	GHashTable *options;
@@ -49,8 +75,7 @@ typedef struct {
 	NMPlatformLink link;
 
 	char *udi;
-	int vlan_id;
-	int ib_p_key;
+	NMPObject *lnk;
 	struct in6_addr ip6_lladdr;
 } NMFakePlatformLink;
 
@@ -62,14 +87,27 @@ G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
 
 static void link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal);
 
-static gboolean ip6_address_add (NMPlatform *platform, int ifindex,
-                                 struct in6_addr addr, struct in6_addr peer_addr,
-                                 int plen, guint32 lifetime, guint32 preferred, guint flags);
+static gboolean ip6_address_add (NMPlatform *platform,
+                                 int ifindex,
+                                 struct in6_addr addr,
+                                 int plen,
+                                 struct in6_addr peer_addr,
+                                 guint32 lifetime,
+                                 guint32 preferred,
+                                 guint flags);
 static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen);
 
 /******************************************************************/
 
 static gboolean
+_ip4_address_equal_peer_net (in_addr_t peer1, in_addr_t peer2, int plen)
+{
+	return ((peer1 ^ peer2) & nm_utils_ip4_prefix_to_netmask (plen)) == 0;
+}
+
+/******************************************************************/
+
+static gboolean
 sysctl_set (NMPlatform *platform, const char *path, const char *value)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
@@ -157,7 +195,7 @@ link_get (NMPlatform *platform, int ifindex)
 
 	return device;
 not_found:
-	debug ("link not found: %d", ifindex);
+	_LOGD ("link not found: %d", ifindex);
 	return NULL;
 }
 
@@ -222,29 +260,63 @@ _nm_platform_link_get_by_address (NMPlatform *platform,
 	return NULL;
 }
 
+static const NMPObject *
+link_get_lnk (NMPlatform *platform,
+              int ifindex,
+              NMLinkType link_type,
+              const NMPlatformLink **out_link)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return NULL;
+
+	NM_SET_OUT (out_link, &device->link);
+
+	if (!device->lnk)
+		return NULL;
+
+	if (link_type == NM_LINK_TYPE_NONE)
+		return device->lnk;
+
+	if (   link_type != device->link.type
+	    || link_type != NMP_OBJECT_GET_CLASS (device->lnk)->lnk_link_type)
+		return NULL;
+
+	return device->lnk;
+}
+
 static gboolean
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
           const void *address,
           size_t address_len,
-          NMPlatformLink *out_link)
+          const NMPlatformLink **out_link)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMFakePlatformLink device;
+	NMFakePlatformLink *new_device;
 
 	link_init (&device, priv->links->len, type, name);
 
+	if (address) {
+		g_return_val_if_fail (address_len > 0 && address_len <= sizeof (device.link.addr.data), FALSE);
+		memcpy (device.link.addr.data, address, address_len);
+		device.link.addr.len = address_len;
+	}
+
 	g_array_append_val (priv->links, device);
+	new_device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1);
 
 	if (device.link.ifindex) {
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, device.link.ifindex, &device, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, device.link.ifindex, &device, NM_PLATFORM_SIGNAL_ADDED);
 
 		link_changed (platform, &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1), FALSE);
 	}
 
 	if (out_link)
-		*out_link = device.link;
+		*out_link = &new_device->link;
 	return TRUE;
 }
 
@@ -261,6 +333,8 @@ link_delete (NMPlatform *platform, int ifindex)
 
 	memcpy (&deleted_device, &device->link, sizeof (deleted_device));
 	memset (&device->link, 0, sizeof (device->link));
+	g_clear_pointer (&device->lnk, nmp_object_unref);
+	g_clear_pointer (&device->udi, g_free);
 
 	/* Remove addresses and routes which belong to the deleted interface */
 	for (i = 0; i < priv->ip4_addresses->len; i++) {
@@ -288,7 +362,7 @@ link_delete (NMPlatform *platform, int ifindex)
 			memset (route, 0, sizeof (*route));
 	}
 
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, ifindex, &deleted_device, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, ifindex, &deleted_device, NM_PLATFORM_SIGNAL_REMOVED);
 
 	return TRUE;
 }
@@ -306,11 +380,11 @@ link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_s
 	int i;
 
 	if (raise_signal)
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, device->link.ifindex, &device->link, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, device->link.ifindex, &device->link, NM_PLATFORM_SIGNAL_CHANGED);
 
 	if (device->link.ifindex && !IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) {
 		if (device->link.connected)
-			ip6_address_add (platform, device->link.ifindex, device->ip6_lladdr, in6addr_any, 64, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
+			ip6_address_add (platform, device->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
 		else
 			ip6_address_delete (platform, device->link.ifindex, device->ip6_lladdr, 64);
 	}
@@ -345,8 +419,10 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 	if (out_no_firmware)
 		*out_no_firmware = FALSE;
 
-	if (!device)
+	if (!device) {
+		_LOGE ("failure changing link: netlink error (No such device)");
 		return FALSE;
+	}
 
 	up = TRUE;
 	connected = TRUE;
@@ -379,8 +455,10 @@ link_set_down (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
 
-	if (!device)
+	if (!device) {
+		_LOGE ("failure changing link: netlink error (No such device)");
 		return FALSE;
+	}
 
 	if (NM_FLAGS_HAS (device->link.flags, IFF_UP) || device->link.connected) {
 		device->link.flags = NM_FLAGS_UNSET (device->link.flags, IFF_UP);
@@ -397,8 +475,10 @@ link_set_arp (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
 
-	if (!device)
+	if (!device) {
+		_LOGE ("failure changing link: netlink error (No such device)");
 		return FALSE;
+	}
 
 	device->link.flags = NM_FLAGS_UNSET (device->link.flags, IFF_NOARP);
 
@@ -412,8 +492,10 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
 
-	if (!device)
+	if (!device) {
+		_LOGE ("failure changing link: netlink error (No such device)");
 		return FALSE;
+	}
 
 	device->link.flags = NM_FLAGS_SET (device->link.flags, IFF_NOARP);
 
@@ -451,7 +533,8 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 	if (device) {
 		device->link.mtu = mtu;
 		link_changed (platform, device, TRUE);
-	}
+	} else
+		_LOGE ("failure changing link: netlink error (No such device)");
 
 	return !!device;
 }
@@ -559,86 +642,69 @@ link_release (NMPlatform *platform, int master_idx, int slave_idx)
 }
 
 static gboolean
-master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
+vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
 {
-	gs_free char *path = g_strdup_printf ("master:%d:%s", master, option);
+	NMFakePlatformLink *device;
 
-	return sysctl_set (platform, path, value);
-}
+	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, out_link))
+		return FALSE;
 
-static char *
-master_get_option (NMPlatform *platform, int master, const char *option)
-{
-	gs_free char *path = g_strdup_printf ("master:%d:%s", master, option);
+	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
 
-	return sysctl_get (platform, path);
-}
+	g_return_val_if_fail (device, FALSE);
+	g_return_val_if_fail (!device->lnk, FALSE);
 
-static gboolean
-slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value)
-{
-	gs_free char *path = g_strdup_printf ("slave:%d:%s", slave, option);
+	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
+	device->lnk->lnk_vlan.id = vlan_id;
+	device->link.parent = parent;
 
-	return sysctl_set (platform, path, value);
+	if (out_link)
+		*out_link = &device->link;
+	return TRUE;
 }
 
-static char *
-slave_get_option (NMPlatform *platform, int slave, const char *option)
+static gboolean
+link_vlan_change (NMPlatform *platform,
+                  int ifindex,
+                  NMVlanFlags flags_mask,
+                  NMVlanFlags flags_set,
+                  gboolean ingress_reset_all,
+                  const NMVlanQosMapping *ingress_map,
+                  gsize n_ingress_map,
+                  gboolean egress_reset_all,
+                  const NMVlanQosMapping *egress_map,
+                  gsize n_egress_map)
 {
-	gs_free char *path = g_strdup_printf ("slave:%d:%s", slave, option);
-
-	return sysctl_get (platform, path);
+	return FALSE;
 }
 
 static gboolean
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, NMPlatformLink *out_link)
+link_vxlan_add (NMPlatform *platform,
+                const char *name,
+                const NMPlatformLnkVxlan *props,
+                const NMPlatformLink **out_link)
 {
 	NMFakePlatformLink *device;
 
-	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, NULL))
+	if (!link_add (platform, name, NM_LINK_TYPE_VXLAN, NULL, 0, out_link))
 		return FALSE;
 
 	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
 
 	g_return_val_if_fail (device, FALSE);
+	g_return_val_if_fail (!device->lnk, FALSE);
 
-	device->vlan_id = vlan_id;
-	device->link.parent = parent;
+	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
+	device->lnk->lnk_vxlan = *props;
+	device->link.parent = props->parent_ifindex;
 
 	if (out_link)
-		*out_link = device->link;
+		*out_link = &device->link;
 	return TRUE;
 }
 
 static gboolean
-vlan_get_info (NMPlatform *platform, int ifindex, int *parent, int *vlan_id)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	g_return_val_if_fail (device, FALSE);
-
-	if (parent)
-		*parent = device->link.parent;
-	if (vlan_id)
-		*vlan_id = device->vlan_id;
-
-	return TRUE;
-}
-
-static gboolean
-vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to)
-{
-	return !!link_get (platform, ifindex);
-}
-
-static gboolean
-vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to)
-{
-	return !!link_get (platform, ifindex);
-}
-
-static gboolean
-infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link)
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
 	NMFakePlatformLink *device, *parent_device;
 	gs_free char *name = NULL;
@@ -652,63 +718,17 @@ infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatfor
 
 	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
 	g_return_val_if_fail (device, FALSE);
+	g_return_val_if_fail (!device->lnk, FALSE);
 
-	device->ib_p_key = p_key;
+	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
+	device->lnk->lnk_infiniband.p_key = p_key;
+	device->lnk->lnk_infiniband.mode = "datagram";
 	device->link.parent = parent;
 
 	return TRUE;
 }
 
 static gboolean
-infiniband_get_info (NMPlatform *platform, int ifindex, int *parent, int *p_key, const char **mode)
-{
-	NMFakePlatformLink *device;
-
-	device = link_get (platform, ifindex);
-	g_return_val_if_fail (device, FALSE);
-	g_return_val_if_fail (device->link.type == NM_LINK_TYPE_INFINIBAND, FALSE);
-
-	if (parent)
-		*parent = device->link.parent;
-	if (p_key)
-		*p_key = device->ib_p_key;
-	if (mode)
-		*mode = "datagram";
-
-	return TRUE;
-}
-
-static gboolean
-veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props)
-{
-	return FALSE;
-}
-
-static gboolean
-tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
-{
-	return FALSE;
-}
-
-static gboolean
-macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props)
-{
-	return FALSE;
-}
-
-static gboolean
-vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperties *props)
-{
-	return FALSE;
-}
-
-static gboolean
-gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *props)
-{
-	return FALSE;
-}
-
-static gboolean
 wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
@@ -861,9 +881,13 @@ ip6_address_get_all (NMPlatform *platform, int ifindex)
 }
 
 static gboolean
-ip4_address_add (NMPlatform *platform, int ifindex,
-                 in_addr_t addr, in_addr_t peer_addr,
-                 int plen, guint32 lifetime, guint32 preferred,
+ip4_address_add (NMPlatform *platform,
+                 int ifindex,
+                 in_addr_t addr,
+                 int plen,
+                 in_addr_t peer_addr,
+                 guint32 lifetime,
+                 guint32 preferred,
                  const char *label)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
@@ -884,29 +908,36 @@ ip4_address_add (NMPlatform *platform, int ifindex,
 
 	for (i = 0; i < priv->ip4_addresses->len; i++) {
 		NMPlatformIP4Address *item = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+		gboolean changed;
 
-		if (item->ifindex != address.ifindex)
-			continue;
-		if (item->address != address.address)
-			continue;
-		if (item->plen != address.plen)
+		if (   item->ifindex != address.ifindex
+		    || item->address != address.address
+		    || item->plen != address.plen
+		    || !_ip4_address_equal_peer_net (item->peer_address, address.peer_address, address.plen))
 			continue;
 
+		changed = !nm_platform_ip4_address_cmp (item, &address);
+
 		memcpy (item, &address, sizeof (address));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		if (changed)
+			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip4_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED);
 
 	return TRUE;
 }
 
 static gboolean
-ip6_address_add (NMPlatform *platform, int ifindex,
-                 struct in6_addr addr, struct in6_addr peer_addr,
-                 int plen, guint32 lifetime, guint32 preferred, guint flags)
+ip6_address_add (NMPlatform *platform,
+                 int ifindex,
+                 struct in6_addr addr,
+                 int plen,
+                 struct in6_addr peer_addr,
+                 guint32 lifetime,
+                 guint32 preferred, guint flags)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMPlatformIP6Address address;
@@ -916,7 +947,7 @@ ip6_address_add (NMPlatform *platform, int ifindex,
 	address.source = NM_IP_CONFIG_SOURCE_KERNEL;
 	address.ifindex = ifindex;
 	address.address = addr;
-	address.peer_address = peer_addr;
+	address.peer_address = (IN6_IS_ADDR_UNSPECIFIED (&peer_addr) || IN6_ARE_ADDR_EQUAL (&addr, &peer_addr)) ? in6addr_any : peer_addr;
 	address.plen = plen;
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 	address.lifetime = lifetime;
@@ -925,23 +956,22 @@ ip6_address_add (NMPlatform *platform, int ifindex,
 
 	for (i = 0; i < priv->ip6_addresses->len; i++) {
 		NMPlatformIP6Address *item = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+		gboolean changed;
 
-		if (item->ifindex != address.ifindex)
-			continue;
-		if (!IN6_ARE_ADDR_EQUAL (&item->address, &address.address))
-			continue;
-		if (item->plen != address.plen)
+		if (   item->ifindex != address.ifindex
+		    || !IN6_ARE_ADDR_EQUAL (&item->address, &address.address))
 			continue;
 
-		if (nm_platform_ip6_address_cmp (item, &address) != 0) {
-			memcpy (item, &address, sizeof (address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
-		}
+		changed = !nm_platform_ip6_address_cmp (item, &address);
+
+		memcpy (item, &address, sizeof (address));
+		if (changed)
+			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip6_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED);
 
 	return TRUE;
 }
@@ -955,13 +985,15 @@ ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen,
 	for (i = 0; i < priv->ip4_addresses->len; i++) {
 		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
 
-		if (address->ifindex == ifindex && address->plen == plen && address->address == addr &&
-		    (!peer_address || address->peer_address == peer_address)) {
+		if (   address->ifindex == ifindex
+		    && address->plen == plen
+		    && address->address == addr
+		    && ((peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (plen)) == 0) {
 			NMPlatformIP4Address deleted_address;
 
 			memcpy (&deleted_address, address, sizeof (deleted_address));
 			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED);
 			return TRUE;
 		}
 	}
@@ -978,13 +1010,14 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int
 	for (i = 0; i < priv->ip6_addresses->len; i++) {
 		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
 
-		if (address->ifindex == ifindex && address->plen == plen
-				&& IN6_ARE_ADDR_EQUAL (&address->address, &addr)) {
+		if (   address->ifindex == ifindex
+		    && address->plen == plen
+		    && IN6_ARE_ADDR_EQUAL (&address->address, &addr)) {
 			NMPlatformIP6Address deleted_address;
 
 			memcpy (&deleted_address, address, sizeof (deleted_address));
 			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED);
 			return TRUE;
 		}
 	}
@@ -993,7 +1026,7 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int
 }
 
 static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	int i;
@@ -1001,7 +1034,10 @@ ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 	for (i = 0; i < priv->ip4_addresses->len; i++) {
 		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
 
-		if (address->ifindex == ifindex && address->plen == plen && address->address == addr)
+		if (   address->ifindex == ifindex
+		    && address->plen == plen
+		    && address->address == addr
+		    && _ip4_address_equal_peer_net (address->peer_address, peer_address, plen))
 			return address;
 	}
 
@@ -1105,7 +1141,7 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 
 		memcpy (&deleted_route, route, sizeof (deleted_route));
 		g_array_remove_index (priv->ip4_routes, i);
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED);
 	}
 
 	return TRUE;
@@ -1131,7 +1167,7 @@ ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, in
 
 		memcpy (&deleted_route, route, sizeof (deleted_route));
 		g_array_remove_index (priv->ip6_routes, i);
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED);
 	}
 
 	return TRUE;
@@ -1140,7 +1176,7 @@ ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, in
 static gboolean
 ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
                in_addr_t network, int plen, in_addr_t gateway,
-               guint32 pref_src, guint32 metric, guint32 mss)
+               in_addr_t pref_src, guint32 metric, guint32 mss)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMPlatformIP4Route route;
@@ -1194,12 +1230,12 @@ ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 		}
 
 		memcpy (item, &route, sizeof (route));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip4_routes, route);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED);
 
 	return TRUE;
 }
@@ -1261,12 +1297,12 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 		}
 
 		memcpy (item, &route, sizeof (route));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip6_routes, route);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED);
 
 	return TRUE;
 }
@@ -1358,6 +1394,7 @@ nm_fake_platform_finalize (GObject *object)
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
 		g_free (device->udi);
+		g_clear_pointer (&device->lnk, nmp_object_unref);
 	}
 	g_array_unref (priv->links);
 	g_array_unref (priv->ip4_addresses);
@@ -1390,6 +1427,8 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->link_delete = link_delete;
 	platform_class->link_get_type_name = link_get_type_name;
 
+	platform_class->link_get_lnk = link_get_lnk;
+
 	platform_class->link_get_udi = link_get_udi;
 
 	platform_class->link_set_up = link_set_up;
@@ -1407,24 +1446,12 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
-	platform_class->master_set_option = master_set_option;
-	platform_class->master_get_option = master_get_option;
-	platform_class->slave_set_option = slave_set_option;
-	platform_class->slave_get_option = slave_get_option;
 
 	platform_class->vlan_add = vlan_add;
-	platform_class->vlan_get_info = vlan_get_info;
-	platform_class->vlan_set_ingress_map = vlan_set_ingress_map;
-	platform_class->vlan_set_egress_map = vlan_set_egress_map;
+	platform_class->link_vlan_change = link_vlan_change;
+	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
-	platform_class->infiniband_get_info = infiniband_get_info;
-
-	platform_class->veth_get_properties = veth_get_properties;
-	platform_class->tun_get_properties = tun_get_properties;
-	platform_class->macvlan_get_properties = macvlan_get_properties;
-	platform_class->vxlan_get_properties = vxlan_get_properties;
-	platform_class->gre_get_properties = gre_get_properties;
 
 	platform_class->wifi_get_capabilities = wifi_get_capabilities;
 	platform_class->wifi_get_bssid = wifi_get_bssid;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index ee6ba421..f73c228b 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -22,8 +22,10 @@
 #include <errno.h>
 #include <unistd.h>
 #include <sys/socket.h>
+#include <sys/ioctl.h>
 #include <fcntl.h>
 #include <dlfcn.h>
+#include <arpa/inet.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
 #include <linux/ip.h>
@@ -40,24 +42,13 @@
 #include <netlink/route/route.h>
 #include <gudev/gudev.h>
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE || HAVE_LIBNL_INET6_TOKEN
-#include <netlink/route/link/inet6.h>
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE && HAVE_KERNEL_INET6_ADDR_GEN_MODE
-#include <linux/if_link.h>
-#else
-#define IN6_ADDR_GEN_MODE_EUI64 0
-#define IN6_ADDR_GEN_MODE_NONE  1
-#endif
-#endif
-
-#include "gsystem-local-alloc.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-linux-platform.h"
 #include "nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
 #include "nmp-object.h"
@@ -65,11 +56,67 @@
 /* This is only included for the translation of VLAN flags */
 #include "nm-setting-vlan.h"
 
+#define VLAN_FLAG_MVRP 0x8
+
+/*********************************************************************************************/
+
+#define IFQDISCSIZ                      32
+
+/*********************************************************************************************/
+
+#ifndef IFLA_PROMISCUITY
+#define IFLA_PROMISCUITY                30
+#endif
+#define IFLA_NUM_TX_QUEUES              31
+#define IFLA_NUM_RX_QUEUES              32
+#define IFLA_CARRIER                    33
+#define IFLA_PHYS_PORT_ID               34
+#define IFLA_LINK_NETNSID               37
+#define __IFLA_MAX                      39
+
+#define IFLA_INET6_TOKEN                7
+#define IFLA_INET6_ADDR_GEN_MODE        8
+#define __IFLA_INET6_MAX                9
+
+#define IFLA_VLAN_PROTOCOL              5
+#define __IFLA_VLAN_MAX                 6
+
+#define IFA_FLAGS                       8
+#define __IFA_MAX                       9
+
+#define IFLA_MACVLAN_FLAGS              2
+#define __IFLA_MACVLAN_MAX              3
+
+#define IFLA_IPTUN_LINK                 1
+#define IFLA_IPTUN_LOCAL                2
+#define IFLA_IPTUN_REMOTE               3
+#define IFLA_IPTUN_TTL                  4
+#define IFLA_IPTUN_TOS                  5
+#define IFLA_IPTUN_ENCAP_LIMIT          6
+#define IFLA_IPTUN_FLOWINFO             7
+#define IFLA_IPTUN_FLAGS                8
+#define IFLA_IPTUN_PROTO                9
+#define IFLA_IPTUN_PMTUDISC             10
+#define __IFLA_IPTUN_MAX                19
+#ifndef IFLA_IPTUN_MAX
+#define IFLA_IPTUN_MAX                  (__IFLA_IPTUN_MAX - 1)
+#endif
+
+#ifndef MACVLAN_FLAG_NOPROMISC
+#define MACVLAN_FLAG_NOPROMISC          1
+#endif
+
+#define IP6_FLOWINFO_TCLASS_MASK        0x0FF00000
+#define IP6_FLOWINFO_TCLASS_SHIFT       20
+#define IP6_FLOWINFO_FLOWLABEL_MASK     0x000FFFFF
+
 /*********************************************************************************************/
 
-#define _NMLOG_DOMAIN                     LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME                "platform-linux"
-#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__)
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG2_DOMAIN                    LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+#define _NMLOG2(level, ...)               _LOG(level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
 
 #define _LOG(level, domain, self, ...) \
     G_STMT_START { \
@@ -91,11 +138,7 @@
         } \
     } G_STMT_END
 
-#define trace(...)      _LOG (LOGL_TRACE, _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define debug(...)      _LOG (LOGL_DEBUG, _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define info(...)       _LOG (LOGL_INFO,  _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define warning(...)    _LOG (LOGL_WARN , _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define error(...)      _LOG (LOGL_ERR  , _NMLOG_DOMAIN, NULL, __VA_ARGS__)
+#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
 
 /******************************************************************
  * Forward declarations and enums
@@ -111,6 +154,7 @@ typedef enum {
 	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL << 5),
 	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL << 6),
 	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL << 7),
+	DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE        = (1LL << 8),
 	__DELAYED_ACTION_TYPE_MAX,
 
 	DELAYED_ACTION_TYPE_REFRESH_ALL                 = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
@@ -122,494 +166,108 @@ typedef enum {
 	DELAYED_ACTION_TYPE_MAX                         = __DELAYED_ACTION_TYPE_MAX -1,
 } DelayedActionType;
 
-static gboolean tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props);
+typedef enum {
+	/* Negative values are errors from kernel. Add dummy member to
+	 * make enum signed. */
+	_WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = -1,
+
+	WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0,
+	WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK,
+	WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING,
+} WaitForNlResponseResult;
+
+typedef void (*WaitForNlResponseCallback) (NMPlatform *platform,
+                                           guint32 seq_number,
+                                           WaitForNlResponseResult seq_result,
+                                           gpointer user_data);
+
 static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
-static void do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action);
-static void do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action);
+static void do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name);
+static void do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type);
 static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
-static gboolean event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks);
-static NMPCacheOpsType cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason);
-
-/******************************************************************
- * libnl unility functions and wrappers
- ******************************************************************/
-
-struct libnl_vtable
-{
-	void *handle;
-	void *handle_route;
-
-	int (*f_nl_has_capability) (int capability);
-	int (*f_rtnl_link_get_link_netnsid) (const struct rtnl_link *link, gint32 *out_link_netnsid);
-};
-
-static int
-_nl_f_nl_has_capability (int capability)
-{
-	return FALSE;
-}
-
-static const struct libnl_vtable *
-_nl_get_vtable (void)
-{
-	static struct libnl_vtable vtable;
-
-	if (G_UNLIKELY (!vtable.f_nl_has_capability)) {
-		vtable.handle = dlopen ("libnl-3.so.200", RTLD_LAZY | RTLD_NOLOAD);
-		if (vtable.handle) {
-			vtable.f_nl_has_capability = dlsym (vtable.handle, "nl_has_capability");
-		}
-		vtable.handle_route = dlopen ("libnl-route-3.so.200", RTLD_LAZY | RTLD_NOLOAD);
-		if (vtable.handle_route) {
-			vtable.f_rtnl_link_get_link_netnsid = dlsym (vtable.handle_route, "rtnl_link_get_link_netnsid");
-		}
-
-		if (!vtable.f_nl_has_capability)
-			vtable.f_nl_has_capability = &_nl_f_nl_has_capability;
-
-		trace ("libnl: rtnl_link_get_link_netnsid() %s", vtable.f_rtnl_link_get_link_netnsid ? "supported" : "not supported");
-
-		g_return_val_if_fail (vtable.handle, &vtable);
-		g_return_val_if_fail (vtable.handle_route, &vtable);
-	}
-
-	return &vtable;
-}
-
-static gboolean
-_nl_has_capability (int capability)
-{
-	return (_nl_get_vtable ()->f_nl_has_capability) (capability);
-}
-
-static int
-_rtnl_link_get_link_netnsid (const struct rtnl_link *link, gint32 *out_link_netnsid)
-{
-	const struct libnl_vtable *vtable;
-
-	g_return_val_if_fail (link, -NLE_INVAL);
-	g_return_val_if_fail (out_link_netnsid, -NLE_INVAL);
-
-	vtable = _nl_get_vtable ();
-	return vtable->f_rtnl_link_get_link_netnsid
-	    ? vtable->f_rtnl_link_get_link_netnsid (link, out_link_netnsid)
-	    : -NLE_OPNOTSUPP;
-}
-
-gboolean
-nm_platform_check_support_libnl_link_netnsid (void)
-{
-	return !!(_nl_get_vtable ()->f_rtnl_link_get_link_netnsid);
-}
-
-/* Automatic deallocation of local variables */
-#define auto_nl_object __attribute__((cleanup(_nl_auto_nl_object)))
-static void
-_nl_auto_nl_object (void *ptr)
-{
-	struct nl_object **object = ptr;
-
-	if (object && *object) {
-		nl_object_put (*object);
-		*object = NULL;
-	}
-}
-
-#define auto_nl_addr __attribute__((cleanup(_nl_auto_nl_addr)))
-static void
-_nl_auto_nl_addr (void *ptr)
-{
-	struct nl_addr **object = ptr;
-
-	if (object && *object) {
-		nl_addr_put (*object);
-		*object = NULL;
-	}
-}
-
-/* wrap the libnl alloc functions and abort on out-of-memory*/
-
-static struct nl_addr *
-_nl_addr_build (int family, const void *buf, size_t size)
-{
-	struct nl_addr *addr;
-
-	addr = nl_addr_build (family, (void *) buf, size);
-	if (!addr)
-		g_error ("nl_addr_build() failed with out of memory");
-
-	return addr;
-}
+static void cache_prune_candidates_prune (NMPlatform *platform);
+static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
 
-static struct rtnl_link *
-_nl_rtnl_link_alloc (int ifindex, const char*name)
-{
-	struct rtnl_link *rtnllink;
-
-	rtnllink = rtnl_link_alloc ();
-	if (!rtnllink)
-		g_error ("rtnl_link_alloc() failed with out of memory");
-
-	if (ifindex > 0)
-		rtnl_link_set_ifindex (rtnllink, ifindex);
-	if (name)
-		rtnl_link_set_name (rtnllink, name);
-	return rtnllink;
-}
-
-static struct rtnl_addr *
-_nl_rtnl_addr_alloc (int ifindex)
-{
-	struct rtnl_addr *rtnladdr;
-
-	rtnladdr = rtnl_addr_alloc ();
-	if (!rtnladdr)
-		g_error ("rtnl_addr_alloc() failed with out of memory");
-	if (ifindex > 0)
-		rtnl_addr_set_ifindex (rtnladdr, ifindex);
-	return rtnladdr;
-}
-
-static struct rtnl_route *
-_nl_rtnl_route_alloc (void)
-{
-	struct rtnl_route *rtnlroute = rtnl_route_alloc ();
-
-	if (!rtnlroute)
-		g_error ("rtnl_route_alloc() failed with out of memory");
-	return rtnlroute;
-}
-
-static struct rtnl_nexthop *
-_nl_rtnl_route_nh_alloc (void)
-{
-	struct rtnl_nexthop *nexthop;
-
-	nexthop = rtnl_route_nh_alloc ();
-	if (!nexthop)
-		g_error ("rtnl_route_nh_alloc () failed with out of memory");
-	return nexthop;
-}
-
-/* rtnl_addr_set_prefixlen fails to update the nl_addr prefixlen */
-static void
-_nl_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
-{
-	struct nl_addr *nladdr;
-
-	rtnl_addr_set_prefixlen (rtnladdr, plen);
-
-	nladdr = rtnl_addr_get_local (rtnladdr);
-	if (nladdr)
-		nl_addr_set_prefixlen (nladdr, plen);
-}
+/*****************************************************************************/
 
 static const char *
-_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
+wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size)
 {
-	const char *str_type = NULL;
+	char *buf0 = buf;
 
-	switch (type) {
-	case RTM_NEWLINK:  str_type = "NEWLINK";  break;
-	case RTM_DELLINK:  str_type = "DELLINK";  break;
-	case RTM_NEWADDR:  str_type = "NEWADDR";  break;
-	case RTM_DELADDR:  str_type = "DELADDR";  break;
-	case RTM_NEWROUTE: str_type = "NEWROUTE"; break;
-	case RTM_DELROUTE: str_type = "DELROUTE"; break;
+	switch (seq_result) {
+	case WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN:
+		nm_utils_strbuf_append_str (&buf, &buf_size, "unknown");
+		break;
+	case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK:
+		nm_utils_strbuf_append_str (&buf, &buf_size, "success");
+		break;
+	case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN:
+		nm_utils_strbuf_append_str (&buf, &buf_size, "failure");
+		break;
+	default:
+		if (seq_result < 0)
+			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s)", -((int) seq_result), g_strerror (-((int) seq_result)));
+		else
+			nm_utils_strbuf_append (&buf, &buf_size, "internal failure %d", (int) seq_result);
+		break;
 	}
-	if (str_type)
-		g_strlcpy (buf, str_type, len);
-	else
-		g_snprintf (buf, len, "(%d)", type);
-	return buf;
+	return buf0;
 }
 
-/******************************************************************/
-
-/* _nl_link_parse_info_data(): Re-fetches a link from the kernel
- * and parses its IFLA_INFO_DATA using a caller-provided parser.
- *
- * Code is stolen from rtnl_link_get_kernel(), nl_pickup(), and link_msg_parser().
- */
-
-typedef int (*NMNLInfoDataParser) (struct nlattr *info_data, gpointer parser_data);
-
-typedef struct {
-	NMNLInfoDataParser parser;
-	gpointer parser_data;
-} NMNLInfoDataClosure;
-
-static struct nla_policy info_data_link_policy[IFLA_MAX + 1] = {
-	[IFLA_LINKINFO] = { .type = NLA_NESTED },
-};
-
-static struct nla_policy info_data_link_info_policy[IFLA_INFO_MAX + 1] = {
-	[IFLA_INFO_DATA] = { .type = NLA_NESTED },
-};
-
-static int
-_nl_link_parse_info_data_cb (struct nl_msg *msg, void *arg)
-{
-	NMNLInfoDataClosure *closure = arg;
-	struct nlmsghdr *n = nlmsg_hdr (msg);
-	struct nlattr *tb[IFLA_MAX + 1];
-	struct nlattr *li[IFLA_INFO_MAX + 1];
-	int err;
-
-	if (!nlmsg_valid_hdr (n, sizeof (struct ifinfomsg)))
-		return -NLE_MSG_TOOSHORT;
-
-	err = nlmsg_parse (n, sizeof (struct ifinfomsg), tb, IFLA_MAX, info_data_link_policy);
-	if (err < 0)
-		return err;
-
-	if (!tb[IFLA_LINKINFO])
-		return -NLE_MISSING_ATTR;
-
-	err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], info_data_link_info_policy);
-	if (err < 0)
-		return err;
-
-	if (!li[IFLA_INFO_DATA])
-		return -NLE_MISSING_ATTR;
-
-	return closure->parser (li[IFLA_INFO_DATA], closure->parser_data);
-}
-
-static int
-_nl_link_parse_info_data (struct nl_sock *sk, int ifindex,
-                          NMNLInfoDataParser parser, gpointer parser_data)
-{
-	NMNLInfoDataClosure data = { .parser = parser, .parser_data = parser_data };
-	struct nl_msg *msg = NULL;
-	struct nl_cb *cb;
-	struct nl_cb *cb0;
-	int err;
-
-	err = rtnl_link_build_get_request (ifindex, NULL, &msg);
-	if (err < 0)
-		return err;
-
-	err = nl_send_auto (sk, msg);
-	nlmsg_free (msg);
-	if (err < 0)
-		return err;
-
-	cb0 = nl_socket_get_cb (sk);
-	cb = nl_cb_clone (cb0);
-	nl_cb_put (cb0);
-	if (cb == NULL)
-		return -NLE_NOMEM;
-	nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, _nl_link_parse_info_data_cb, &data);
-
-	err = nl_recvmsgs (sk, cb);
-	nl_cb_put (cb);
-	if (err < 0)
-		return err;
-
-	nl_wait_for_ack (sk);
-	return 0;
-}
-
-/******************************************************************/
-
-static int
-_nl_sock_flush_data (struct nl_sock *sk)
-{
-	int nle;
-	struct nl_cb *cb;
-	struct nl_cb *cb0;
-
-	cb0 = nl_socket_get_cb (sk);
-	cb = nl_cb_clone (cb0);
-	nl_cb_put (cb0);
-	if (cb == NULL)
-		return -NLE_NOMEM;
-
-	nl_cb_set (cb, NL_CB_VALID, NL_CB_DEFAULT, NULL, NULL);
-	nl_cb_set (cb, NL_CB_SEQ_CHECK, NL_CB_DEFAULT, NULL, NULL);
-	nl_cb_err (cb, NL_CB_DEFAULT, NULL, NULL);
-	do {
-		errno = 0;
-
-		nle = nl_recvmsgs (sk, cb);
-
-		/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-		if (nle == 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
-			nle = -NLE_AGAIN;
-	} while (nle != -NLE_AGAIN);
-
-	nl_cb_put (cb);
-	return nle;
-}
-
-static void
-_nl_msg_set_seq (struct nl_sock *sk, struct nl_msg *msg, guint32 *out_seq)
-{
-	guint32 seq;
-
-	/* choose our own sequence number, because libnl does not ensure that
-	 * it isn't zero -- which would confuse our checking for outstanding
-	 * messages. */
-	seq = nl_socket_use_seq (sk);
-	if (seq == 0)
-		seq = nl_socket_use_seq (sk);
-
-	nlmsg_hdr (msg)->nlmsg_seq = seq;
-	if (out_seq)
-		*out_seq = seq;
-}
-
-static int
-_nl_sock_request_link (NMPlatform *platform, struct nl_sock *sk, int ifindex, const char *name, guint32 *out_seq)
-{
-	struct nl_msg *msg = NULL;
-	int err;
-
-	if (name && !name[0])
-		name = NULL;
-
-	g_return_val_if_fail (ifindex > 0 || name, -NLE_INVAL);
-
-	_LOGt ("sock: request-link %d%s%s%s", ifindex, name ? ", \"" : "", name ? name : "", name ? "\"" : "");
-
-	if ((err = rtnl_link_build_get_request (ifindex, name, &msg)) < 0)
-		return err;
-
-	_nl_msg_set_seq (sk, msg, out_seq);
-
-	err = nl_send_auto (sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int
-_nl_sock_request_all (NMPlatform *platform, struct nl_sock *sk, NMPObjectType obj_type, guint32 *out_seq)
-{
-	const NMPClass *klass;
-	struct rtgenmsg gmsg = { 0 };
-	struct nl_msg *msg;
-	int err;
-
-	klass = nmp_class_from_type (obj_type);
-
-	_LOGt ("sock: request-all-%s", klass->obj_type_name);
-
-	/* reimplement
-	 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
-	 * because we need the sequence number.
-	 */
-	msg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
-	if (!msg)
-		return -NLE_NOMEM;
-
-	gmsg.rtgen_family = klass->addr_family;
-	err = nlmsg_append (msg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
-	if (err < 0)
-		goto errout;
-
-	_nl_msg_set_seq (sk, msg, out_seq);
-
-	err = nl_send_auto (sk, msg);
-errout:
-	nlmsg_free(msg);
-
-	return err >= 0 ? 0 : err;
-}
-
-/******************************************************************/
+/******************************************************************
+ * Support IFLA_INET6_ADDR_GEN_MODE
+ ******************************************************************/
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
 static int _support_user_ipv6ll = 0;
 #define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0))
-#else
-#define _support_user_ipv6ll_still_undecided() (FALSE)
-#endif
 
 static gboolean
 _support_user_ipv6ll_get (void)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
 	if (_support_user_ipv6ll_still_undecided ()) {
 		_support_user_ipv6ll = -1;
-		nm_log_warn (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support");
-	} else
-		return _support_user_ipv6ll > 0;
-#endif
+		_LOG2W ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support");
+		return FALSE;
+	}
+	return _support_user_ipv6ll > 0;
 
-	return FALSE;
 }
 
 static void
-_support_user_ipv6ll_detect (const struct rtnl_link *rtnl_link)
+_support_user_ipv6ll_detect (struct nlattr **tb)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	/* If we ever see a link with valid IPv6 link-local address
-	 * generation modes, the kernel supports it.
-	 */
 	if (_support_user_ipv6ll_still_undecided ()) {
-		uint8_t mode;
-
-		if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) rtnl_link, &mode) == 0) {
+		if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
 			_support_user_ipv6ll = 1;
-			nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected");
+			_LOG2D ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected");
 		} else {
 			_support_user_ipv6ll = -1;
-			nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected");
+			_LOG2D ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected");
 		}
 	}
-#endif
 }
 
-/******************************************************************/
-
-static int _support_kernel_extended_ifa_flags = 0;
-
-#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == 0))
-
-static void
-_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
-{
-	struct nlmsghdr *msg_hdr;
+/******************************************************************
+ * Various utilities
+ ******************************************************************/
 
-	if (!_support_kernel_extended_ifa_flags_still_undecided ())
-		return;
+const NMIPAddr nm_ip_addr_zero = NMIPAddrInit;
 
-	msg_hdr = nlmsg_hdr (msg);
-	if (msg_hdr->nlmsg_type != RTM_NEWADDR)
-		return;
-
-	/* the extended address flags are only set for AF_INET6 */
-	if (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family != AF_INET6)
-		return;
-
-	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
-	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
-	 * and IFA_F_NOPREFIXROUTE (they were added together).
-	 **/
-	_support_kernel_extended_ifa_flags =
-	    nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), 8 /* IFA_FLAGS */)
-	    ? 1 : -1;
-}
+#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
+#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
 
 static gboolean
-_support_kernel_extended_ifa_flags_get (void)
+ip4_address_is_link_local (in_addr_t addr)
 {
-	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
-		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for extended IFA_FLAGS. Assume no kernel support.");
-		_support_kernel_extended_ifa_flags = -1;
-	}
-	return _support_kernel_extended_ifa_flags > 0;
+	return (addr & IPV4LL_NETMASK) == IPV4LL_NETWORK;
 }
 
-/******************************************************************
- * Object type specific utilities
- ******************************************************************/
-
 static guint
 _nm_ip_config_source_to_rtprot (NMIPConfigSource source)
 {
@@ -649,7 +307,46 @@ _nm_ip_config_source_from_rtprot (guint rtprot)
 	}
 }
 
-/******************************************************************/
+static void
+clear_host_address (int family, const void *network, int plen, void *dst)
+{
+	g_return_if_fail (plen == (guint8)plen);
+	g_return_if_fail (network);
+
+	switch (family) {
+	case AF_INET:
+		*((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen);
+		break;
+	case AF_INET6:
+		nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static int
+_vlan_qos_mapping_cmp_from (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	const NMVlanQosMapping *map_a = a;
+	const NMVlanQosMapping *map_b = b;
+
+	if (map_a->from != map_b->from)
+		return map_a->from < map_b->from ? -1 : 1;
+	return 0;
+}
+
+static int
+_vlan_qos_mapping_cmp_from_ptr (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	return _vlan_qos_mapping_cmp_from (*((const NMVlanQosMapping **) a),
+	                                   *((const NMVlanQosMapping **) b),
+	                                   NULL);
+}
+
+/******************************************************************
+ * NMLinkType functions
+ ******************************************************************/
 
 typedef struct {
 	const NMLinkType nm_type;
@@ -687,10 +384,13 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
 	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
 	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         NULL },
+	{ NM_LINK_TYPE_IP6TNL,        "ip6tnl",      "ip6tnl",      NULL },
+	{ NM_LINK_TYPE_IPIP,          "ipip",        "ipip",        NULL },
 	{ NM_LINK_TYPE_LOOPBACK,      "loopback",    NULL,          NULL },
 	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
 	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
 	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
+	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         NULL },
 	{ NM_LINK_TYPE_TAP,           "tap",         NULL,          NULL },
 	{ NM_LINK_TYPE_TUN,           "tun",         NULL,          NULL },
 	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
@@ -728,125 +428,172 @@ nm_link_type_to_string (NMLinkType type)
 }
 
 /******************************************************************
- * NMPlatform types and functions
+ * Utilities
  ******************************************************************/
 
-typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate;
-
-struct _NMLinuxPlatformPrivate {
-	struct nl_sock *nlh;
-	struct nl_sock *nlh_event;
-	guint32 nlh_seq_expect;
-	guint32 nlh_seq_last;
-	NMPCache *cache;
-	GIOChannel *event_channel;
-	guint event_id;
-
-	GUdevClient *udev_client;
-
-	struct {
-		DelayedActionType flags;
-		GPtrArray *list_master_connected;
-		GPtrArray *list_refresh_link;
-		gint is_handling;
-		guint idle_id;
-	} delayed_action;
-
-	GHashTable *prune_candidates;
+/* _timestamp_nl_to_ms:
+ * @timestamp_nl: a timestamp from ifa_cacheinfo.
+ * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current
+ * uptime and how often timestamp_nl wrapped.
+ *
+ * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds.
+ * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts
+ * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However,
+ * the uint32 counter wraps every 497 days of uptime, so we have to compensate
+ * for that. */
+static gint64
+_timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
+{
+	const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100);
+	gint64 timestamp_nl_ms;
 
-	GHashTable *wifi_data;
-};
+	/* convert timestamp from 1/100th of a second to msec. */
+	timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100);
 
-static inline NMLinuxPlatformPrivate *
-NM_LINUX_PLATFORM_GET_PRIVATE (const void *self)
-{
-	nm_assert (NM_IS_LINUX_PLATFORM (self));
+	/* timestamp wraps every 497 days. Try to compensate for that.*/
+	if (timestamp_nl_ms > monotonic_ms) {
+		/* timestamp_nl_ms is in the future. Truncate it to *now* */
+		timestamp_nl_ms = monotonic_ms;
+	} else if (monotonic_ms >= WRAP_INTERVAL) {
+		timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL;
+		if (timestamp_nl_ms > monotonic_ms)
+			timestamp_nl_ms -= WRAP_INTERVAL;
+	}
 
-	return ((NMLinuxPlatform *) self)->priv;
+	return timestamp_nl_ms;
 }
 
-G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-void
-nm_linux_platform_setup (void)
+static guint32
+_addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 {
-	g_object_new (NM_TYPE_LINUX_PLATFORM,
-	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
-	              NULL);
-}
+	struct timespec tp;
+	gint64 now_nl, now_nm, result;
+	int err;
 
-/******************************************************************/
+	/* timestamp is unset. Default to 1. */
+	if (!timestamp) {
+		if (out_now_nm)
+			*out_now_nm = 0;
+		return 1;
+	}
 
-NMPObjectType
-_nlo_get_object_type (const struct nl_object *object)
-{
-	const char *type_str;
+	/* do all the calculations in milliseconds scale */
 
-	if (!object || !(type_str = nl_object_get_type (object)))
-		return NMP_OBJECT_TYPE_UNKNOWN;
+	err = clock_gettime (CLOCK_MONOTONIC, &tp);
+	g_assert (err == 0);
+	now_nm = nm_utils_get_monotonic_timestamp_ms ();
+	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
+	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
 
-	if (!strcmp (type_str, "route/link"))
-		return NMP_OBJECT_TYPE_LINK;
-	else if (!strcmp (type_str, "route/addr")) {
-		switch (rtnl_addr_get_family ((struct rtnl_addr *) object)) {
-		case AF_INET:
-			return NMP_OBJECT_TYPE_IP4_ADDRESS;
-		case AF_INET6:
-			return NMP_OBJECT_TYPE_IP6_ADDRESS;
-		default:
-			return NMP_OBJECT_TYPE_UNKNOWN;
-		}
-	} else if (!strcmp (type_str, "route/route")) {
-		switch (rtnl_route_get_family ((struct rtnl_route *) object)) {
-		case AF_INET:
-			return NMP_OBJECT_TYPE_IP4_ROUTE;
-		case AF_INET6:
-			return NMP_OBJECT_TYPE_IP6_ROUTE;
-		default:
-			return NMP_OBJECT_TYPE_UNKNOWN;
-		}
-	} else
-		return NMP_OBJECT_TYPE_UNKNOWN;
-}
+	result = now_nm - (now_nl - _timestamp_nl_to_ms (timestamp, now_nl));
 
-/******************************************************************/
+	if (out_now_nm)
+		*out_now_nm = now_nm / 1000;
 
-static gboolean
-check_support_kernel_extended_ifa_flags (NMPlatform *platform)
-{
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	/* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() scale is
+	 * a good guess but fails in the following situations:
+	 *
+	 * - If the address existed before start of the process, the timestamp in nm scale would
+	 *   be negative or zero. In this case we default to 1.
+	 * - during hibernation, the CLOCK_MONOTONIC/timestamp drifts from
+	 *   nm_utils_get_monotonic_timestamp_ms() scale.
+	 */
+	if (result <= 1000)
+		return 1;
 
-	return _support_kernel_extended_ifa_flags_get ();
+	if (result > now_nm)
+		return now_nm / 1000;
+
+	return result / 1000;
 }
 
-static gboolean
-check_support_user_ipv6ll (NMPlatform *platform)
+static guint32
+_addrtime_extend_lifetime (guint32 lifetime, guint32 seconds)
 {
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	guint64 v;
 
-	return _support_user_ipv6ll_get ();
+	if (   lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    || seconds == 0)
+		return lifetime;
+
+	v = (guint64) lifetime + (guint64) seconds;
+	return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
 }
 
+/* The rtnl_addr object contains relative lifetimes @valid and @preferred
+ * that count in seconds, starting from the moment when the kernel constructed
+ * the netlink message.
+ *
+ * There is also a field rtnl_addr_last_update_time(), which is the absolute
+ * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
+ * was modified (wrapping every 497 days).
+ * Immediately at the time when the address was last modified, #NOW and @last_update_time
+ * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
+ * However, this is not true in general. As time goes by, whenever kernel sends a new address
+ * via netlink, the lifetimes keep counting down.
+ **/
 static void
-process_events (NMPlatform *platform)
+_addrtime_get_lifetimes (guint32 timestamp,
+                         guint32 lifetime,
+                         guint32 preferred,
+                         guint32 *out_timestamp,
+                         guint32 *out_lifetime,
+                         guint32 *out_preferred)
 {
-	delayed_action_handle_all (platform, TRUE);
+	gint32 now;
+
+	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
+	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
+		if (preferred > lifetime)
+			preferred = lifetime;
+		timestamp = _addrtime_timestamp_to_nm (timestamp, &now);
+
+		if (now == 0) {
+			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
+			nm_assert (timestamp == 1);
+			now = nm_utils_get_monotonic_timestamp_s ();
+		}
+		if (timestamp < now) {
+			guint32 diff = now - timestamp;
+
+			lifetime = _addrtime_extend_lifetime (lifetime, diff);
+			preferred = _addrtime_extend_lifetime (preferred, diff);
+		} else
+			nm_assert (timestamp == now);
+	} else
+		timestamp = 0;
+	*out_timestamp = timestamp;
+	*out_lifetime = lifetime;
+	*out_preferred = preferred;
 }
 
 /******************************************************************/
 
-#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
-	((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
-	                                              nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \
-	                                              NULL))
+static const NMPObject *
+_lookup_cached_link (const NMPCache *cache, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
+{
+	const NMPObject *obj;
 
+	nm_assert (completed_from_cache && link_cached);
+
+	if (!*completed_from_cache) {
+		obj = ifindex > 0 && cache ? nmp_cache_lookup_link (cache, ifindex) : NULL;
+
+		if (obj && !obj->_link.netlink.is_in_netlink)
+			*link_cached = obj;
+		else
+			*link_cached = NULL;
+		*completed_from_cache = TRUE;
+	}
+	return *link_cached;
+}
 
 /******************************************************************/
 
 #define DEVTYPE_PREFIX "DEVTYPE="
 
 static char *
-read_devtype (const char *sysfs_path)
+_linktype_read_devtype (const char *sysfs_path)
 {
 	gs_free char *uevent = g_strdup_printf ("%s/uevent", sysfs_path);
 	char *contents = NULL;
@@ -868,69 +615,63 @@ read_devtype (const char *sysfs_path)
 	return NULL;
 }
 
-static const NMPObject *
-_lookup_link_cached (NMPlatform *platform, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
-{
-	const NMPObject *obj;
-
-	nm_assert (completed_from_cache && link_cached);
-
-	if (!*completed_from_cache) {
-		obj = ifindex > 0 ? nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex) : NULL;
-
-		if (obj && !obj->_link.netlink.is_in_netlink)
-			*link_cached = obj;
-		else
-			*link_cached = NULL;
-		*completed_from_cache = TRUE;
-	}
-	return *link_cached;
-}
-
 static NMLinkType
-link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *completed_from_cache, const NMPObject **link_cached, const char **out_kind)
-{
-	const char *rtnl_type, *ifname;
-	int i, arptype;
-
-	if (!rtnllink) {
-		if (out_kind)
-			*out_kind = NULL;
-		return NM_LINK_TYPE_NONE;
-	}
-
-	rtnl_type = rtnl_link_get_type (rtnllink);
-	if (!rtnl_type && completed_from_cache) {
+_linktype_get_type (NMPlatform *platform,
+                    const NMPCache *cache,
+                    const char *kind,
+                    int ifindex,
+                    const char *ifname,
+                    unsigned flags,
+                    unsigned arptype,
+                    gboolean *completed_from_cache,
+                    const NMPObject **link_cached,
+                    const char **out_kind)
+{
+	guint i;
+
+	if (completed_from_cache) {
 		const NMPObject *obj;
 
-		obj = _lookup_link_cached (platform, rtnl_link_get_ifindex (rtnllink), completed_from_cache, link_cached);
-		if (obj && obj->link.kind) {
-			rtnl_type = obj->link.kind;
-			_LOGt ("link_extract_type(): complete kind from cache: ifindex=%d, kind=%s", rtnl_link_get_ifindex (rtnllink), rtnl_type);
+		obj = _lookup_cached_link (cache, ifindex, completed_from_cache, link_cached);
+
+		/* If we detected the link type before, we stick to that
+		 * decision unless the "kind" changed.
+		 *
+		 * This way, we save edditional ethtool/sysctl lookups, but moreover,
+		 * we keep the linktype stable and don't change it as long as the link
+		 * exists.
+		 *
+		 * Note that kernel *can* reuse the ifindex (on integer overflow, and
+		 * when moving interfce to other netns). Thus here there is a tiny potential
+		 * of messing stuff up. */
+		if (   obj
+		    && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
+		    && (   !kind
+		        || !g_strcmp0 (kind, obj->link.kind))) {
+			nm_assert (obj->link.kind == g_intern_string (obj->link.kind));
+			*out_kind = obj->link.kind;
+			return obj->link.type;
 		}
 	}
-	if (out_kind)
-		*out_kind = rtnl_type;
-	if (rtnl_type) {
+
+	*out_kind = g_intern_string (kind);
+
+	if (kind) {
 		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-			if (g_strcmp0 (rtnl_type, linktypes[i].rtnl_type) == 0)
+			if (g_strcmp0 (kind, linktypes[i].rtnl_type) == 0)
 				return linktypes[i].nm_type;
 		}
 
-		if (!strcmp (rtnl_type, "tun")) {
+		if (!strcmp (kind, "tun")) {
 			NMPlatformTunProperties props;
-			guint flags;
 
-			if (tun_get_properties_ifname (platform, rtnl_link_get_name (rtnllink), &props)) {
+			if (   platform
+			    && nm_platform_link_tun_get_properties_ifname (platform, ifname, &props)) {
 				if (!g_strcmp0 (props.mode, "tap"))
 					return NM_LINK_TYPE_TAP;
 				if (!g_strcmp0 (props.mode, "tun"))
 					return NM_LINK_TYPE_TUN;
 			}
-			flags = rtnl_link_get_flags (rtnllink);
-
-			nm_log_dbg (LOGD_PLATFORM, "Failed to read tun properties for interface %d (link flags: %X)",
-			            rtnl_link_get_ifindex (rtnllink), flags);
 
 			/* try guessing the type using the link flags instead... */
 			if (flags & IFF_POINTOPOINT)
@@ -939,13 +680,15 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		}
 	}
 
-	arptype = rtnl_link_get_arptype (rtnllink);
 	if (arptype == ARPHRD_LOOPBACK)
 		return NM_LINK_TYPE_LOOPBACK;
 	else if (arptype == ARPHRD_INFINIBAND)
 		return NM_LINK_TYPE_INFINIBAND;
+	else if (arptype == ARPHRD_SIT)
+		return NM_LINK_TYPE_SIT;
+	else if (arptype == ARPHRD_TUNNEL6)
+		return NM_LINK_TYPE_IP6TNL;
 
-	ifname = rtnl_link_get_name (rtnllink);
 	if (ifname) {
 		gs_free char *driver = NULL;
 		gs_free char *sysfs_path = NULL;
@@ -971,7 +714,7 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		if (g_file_test (anycast_mask, G_FILE_TEST_EXISTS))
 			return NM_LINK_TYPE_OLPC_MESH;
 
-		devtype = read_devtype (sysfs_path);
+		devtype = _linktype_read_devtype (sysfs_path);
 		for (i = 0; devtype && i < G_N_ELEMENTS (linktypes); i++) {
 			if (g_strcmp0 (devtype, linktypes[i].devtype) == 0) {
 				if (linktypes[i].nm_type == NM_LINK_TYPE_BNEP) {
@@ -994,366 +737,1138 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		 * prevent future virtual network drivers from being treated as Ethernet
 		 * when they should be Generic instead.
 		 */
-		if (arptype == ARPHRD_ETHER && !rtnl_type && !devtype)
+		if (arptype == ARPHRD_ETHER && !kind && !devtype)
 			return NM_LINK_TYPE_ETHERNET;
 	}
 
 	return NM_LINK_TYPE_UNKNOWN;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/******************************************************************
+ * libnl unility functions and wrappers
+ ******************************************************************/
+
+#define nm_auto_nlmsg __attribute__((cleanup(_nm_auto_nl_msg_cleanup)))
+static void
+_nm_auto_nl_msg_cleanup (void *ptr)
 {
-	NMPlatformLink *obj = (NMPlatformLink *) _obj;
-	NMPObjectLink *obj_priv = (NMPObjectLink *) _obj;
-	struct rtnl_link *nlo = (struct rtnl_link *) _nlo;
-	const char *name;
-	struct nl_addr *nladdr;
-	const char *kind;
-	gboolean completed_from_cache_val = FALSE;
-	gboolean *completed_from_cache = complete_from_cache ? &completed_from_cache_val : NULL;
-	const NMPObject *link_cached = NULL;
-	int parent;
+	nlmsg_free (*((struct nl_msg **) ptr));
+}
 
-	nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
+static const char *
+_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
+{
+	const char *str_type = NULL;
 
-	if (_LOGt_ENABLED () && !NM_IN_SET (rtnl_link_get_family (nlo), AF_UNSPEC, AF_BRIDGE))
-		_LOGt ("netlink object for ifindex %d has unusual family %d", rtnl_link_get_ifindex (nlo), rtnl_link_get_family (nlo));
+	switch (type) {
+	case RTM_NEWLINK:  str_type = "NEWLINK";  break;
+	case RTM_DELLINK:  str_type = "DELLINK";  break;
+	case RTM_NEWADDR:  str_type = "NEWADDR";  break;
+	case RTM_DELADDR:  str_type = "DELADDR";  break;
+	case RTM_NEWROUTE: str_type = "NEWROUTE"; break;
+	case RTM_DELROUTE: str_type = "DELROUTE"; break;
+	}
+	if (str_type)
+		g_strlcpy (buf, str_type, len);
+	else
+		g_snprintf (buf, len, "(%d)", type);
+	return buf;
+}
 
-	obj->ifindex = rtnl_link_get_ifindex (nlo);
+/******************************************************************
+ * NMPObject/netlink functions
+ ******************************************************************/
 
-	if (id_only)
-		return TRUE;
+#define _check_addr_or_errout(tb, attr, addr_len) \
+	({ \
+	    const struct nlattr *__t = (tb)[(attr)]; \
+		\
+	    if (__t) { \
+			if (nla_len (__t) != (addr_len)) { \
+				goto errout; \
+			} \
+		} \
+		!!__t; \
+	})
 
-	name = rtnl_link_get_name (nlo);
-	if (name)
-		g_strlcpy (obj->name, name, sizeof (obj->name));
-	obj->type = link_extract_type (platform, nlo, completed_from_cache, &link_cached, &kind);
-	obj->kind = g_intern_string (kind);
-	obj->flags = rtnl_link_get_flags (nlo);
-	obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP);
-	obj->master = rtnl_link_get_master (nlo);
-	parent = rtnl_link_get_link (nlo);
-	if (parent > 0) {
-		gint32 link_netnsid;
-
-		if (_rtnl_link_get_link_netnsid (nlo, &link_netnsid) == 0)
-			obj->parent = NM_PLATFORM_LINK_OTHER_NETNS;
-		else
-			obj->parent = parent;
-	}
-	obj->mtu = rtnl_link_get_mtu (nlo);
-	obj->arptype = rtnl_link_get_arptype (nlo);
-
-	if (obj->type == NM_LINK_TYPE_VLAN) {
-		if (!g_strcmp0 (rtnl_link_get_type (nlo), "vlan"))
-			obj->vlan_id = rtnl_link_vlan_get_id (nlo);
-		else if (completed_from_cache) {
-			_lookup_link_cached (platform, obj->ifindex, completed_from_cache, &link_cached);
-			if (link_cached)
-				obj->vlan_id = link_cached->link.vlan_id;
-		}
-	}
+/*****************************************************************************/
 
-	if ((nladdr = rtnl_link_get_addr (nlo))) {
-		unsigned int l = 0;
+/* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */
+static gboolean
+_parse_af_inet6 (NMPlatform *platform,
+                 struct nlattr *attr,
+                 NMUtilsIPv6IfaceId *out_iid,
+                 guint8 *out_iid_is_valid,
+                 guint8 *out_addr_gen_mode_inv)
+{
+	static struct nla_policy policy[IFLA_INET6_MAX+1] = {
+		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
+		[IFLA_INET6_CACHEINFO]          = { .minlen = sizeof(struct ifla_cacheinfo) },
+		[IFLA_INET6_CONF]               = { .minlen = 4 },
+		[IFLA_INET6_STATS]              = { .minlen = 8 },
+		[IFLA_INET6_ICMP6STATS]         = { .minlen = 8 },
+		[IFLA_INET6_TOKEN]              = { .minlen = sizeof(struct in6_addr) },
+		[IFLA_INET6_ADDR_GEN_MODE]      = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_INET6_MAX+1];
+	int err;
+	struct in6_addr i6_token;
+	gboolean iid_is_valid = FALSE;
+	guint8 i6_addr_gen_mode_inv = 0;
+	gboolean success = FALSE;
 
-		l = nl_addr_get_len (nladdr);
-		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
-			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data));
-			memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l);
-			obj->addr.len = l;
-		}
-	}
+	err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy);
+	if (err < 0)
+		goto errout;
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	if (_support_user_ipv6ll_get ()) {
-		guint8 mode = 0;
+	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
+		goto errout;
+	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
+		goto errout;
+	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
+		goto errout;
 
-		if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0)
-			obj->inet6_addr_gen_mode_inv = _nm_platform_uint8_inv (mode);
+	if (_check_addr_or_errout (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr))) {
+		nla_memcpy (&i6_token, tb[IFLA_INET6_TOKEN], sizeof (struct in6_addr));
+		if (!IN6_IS_ADDR_UNSPECIFIED (&i6_token))
+			iid_is_valid = TRUE;
 	}
-#endif
 
-#if HAVE_LIBNL_INET6_TOKEN
-	if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) {
-		if (   nl_addr_get_family (nladdr) == AF_INET6
-		    && nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) {
-			struct in6_addr *addr;
-			NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid;
-
-			addr = nl_addr_get_binary_addr (nladdr);
-			iid->id_u8[7] = addr->s6_addr[15];
-			iid->id_u8[6] = addr->s6_addr[14];
-			iid->id_u8[5] = addr->s6_addr[13];
-			iid->id_u8[4] = addr->s6_addr[12];
-			iid->id_u8[3] = addr->s6_addr[11];
-			iid->id_u8[2] = addr->s6_addr[10];
-			iid->id_u8[1] = addr->s6_addr[9];
-			iid->id_u8[0] = addr->s6_addr[8];
-			obj->inet6_token.is_valid = TRUE;
+	/* Hack to detect support addrgenmode of the kernel. We only parse
+	 * netlink messages that we receive from kernel, hence this check
+	 * is valid. */
+	_support_user_ipv6ll_detect (tb);
+
+	if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
+		i6_addr_gen_mode_inv = _nm_platform_uint8_inv (nla_get_u8 (tb[IFLA_INET6_ADDR_GEN_MODE]));
+		if (i6_addr_gen_mode_inv == 0) {
+			/* an inverse addrgenmode of zero is unexpected. We need to reserve zero
+			 * to signal "unset". */
+			goto errout;
 		}
-		nl_addr_put (nladdr);
 	}
-#endif
 
-	obj_priv->netlink.is_in_netlink = TRUE;
+	success = TRUE;
+	if (iid_is_valid) {
+		out_iid->id_u8[7] = i6_token.s6_addr[15];
+		out_iid->id_u8[6] = i6_token.s6_addr[14];
+		out_iid->id_u8[5] = i6_token.s6_addr[13];
+		out_iid->id_u8[4] = i6_token.s6_addr[12];
+		out_iid->id_u8[3] = i6_token.s6_addr[11];
+		out_iid->id_u8[2] = i6_token.s6_addr[10];
+		out_iid->id_u8[1] = i6_token.s6_addr[9];
+		out_iid->id_u8[0] = i6_token.s6_addr[8];
+		*out_iid_is_valid = TRUE;
+	}
+	*out_addr_gen_mode_inv = i6_addr_gen_mode_inv;
+errout:
+	return success;
+}
 
-	return TRUE;
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_gre (const char *kind, struct nlattr *info_data)
+{
+	static struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+		[IFLA_GRE_LINK]     = { .type = NLA_U32 },
+		[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
+		[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
+		[IFLA_GRE_IKEY]     = { .type = NLA_U32 },
+		[IFLA_GRE_OKEY]     = { .type = NLA_U32 },
+		[IFLA_GRE_LOCAL]    = { .type = NLA_U32 },
+		[IFLA_GRE_REMOTE]   = { .type = NLA_U32 },
+		[IFLA_GRE_TTL]      = { .type = NLA_U8 },
+		[IFLA_GRE_TOS]      = { .type = NLA_U8 },
+		[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_GRE_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkGre *props;
+
+	if (!info_data || g_strcmp0 (kind, "gre"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_GRE, NULL);
+	props = &obj->lnk_gre;
+
+	props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0;
+	props->input_flags = tb[IFLA_GRE_IFLAGS] ? ntohs (nla_get_u16 (tb[IFLA_GRE_IFLAGS])) : 0;
+	props->output_flags = tb[IFLA_GRE_OFLAGS] ? ntohs (nla_get_u16 (tb[IFLA_GRE_OFLAGS])) : 0;
+	props->input_key = tb[IFLA_GRE_IKEY] ? ntohl (nla_get_u32 (tb[IFLA_GRE_IKEY])) : 0;
+	props->output_key = tb[IFLA_GRE_OKEY] ? ntohl (nla_get_u32 (tb[IFLA_GRE_OKEY])) : 0;
+	props->local = tb[IFLA_GRE_LOCAL] ? nla_get_u32 (tb[IFLA_GRE_LOCAL]) : 0;
+	props->remote = tb[IFLA_GRE_REMOTE] ? nla_get_u32 (tb[IFLA_GRE_REMOTE]) : 0;
+	props->tos = tb[IFLA_GRE_TOS] ? nla_get_u8 (tb[IFLA_GRE_TOS]) : 0;
+	props->ttl = tb[IFLA_GRE_TTL] ? nla_get_u8 (tb[IFLA_GRE_TTL]) : 0;
+	props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]);
+
+	return obj;
 }
 
-/* _timestamp_nl_to_ms:
- * @timestamp_nl: a timestamp from ifa_cacheinfo.
- * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current
- * uptime and how often timestamp_nl wrapped.
- *
- * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds.
- * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts
- * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However,
- * the uint32 counter wraps every 497 days of uptime, so we have to compensate
- * for that. */
-static gint64
-_timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
+/*****************************************************************************/
+
+/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers
+ * we're building against might not have those properties even though the
+ * running kernel might.
+ */
+#define IFLA_IPOIB_UNSPEC 0
+#define IFLA_IPOIB_PKEY   1
+#define IFLA_IPOIB_MODE   2
+#define IFLA_IPOIB_UMCAST 3
+#undef IFLA_IPOIB_MAX
+#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST
+
+#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
+#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
+
+static NMPObject *
+_parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 {
-	const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100);
-	gint64 timestamp_nl_ms;
+	static struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
+		[IFLA_IPOIB_PKEY]   = { .type = NLA_U16 },
+		[IFLA_IPOIB_MODE]   = { .type = NLA_U16 },
+		[IFLA_IPOIB_UMCAST] = { .type = NLA_U16 },
+	};
+	struct nlattr *tb[IFLA_IPOIB_MAX + 1];
+	NMPlatformLnkInfiniband *info;
+	NMPObject *obj;
+	int err;
+	const char *mode;
 
-	/* convert timestamp from 1/100th of a second to msec. */
-	timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100);
+	if (!info_data || g_strcmp0 (kind, "ipoib"))
+		return NULL;
 
-	/* timestamp wraps every 497 days. Try to compensate for that.*/
-	if (timestamp_nl_ms > monotonic_ms) {
-		/* timestamp_nl_ms is in the future. Truncate it to *now* */
-		timestamp_nl_ms = monotonic_ms;
-	} else if (monotonic_ms >= WRAP_INTERVAL) {
-		timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL;
-		if (timestamp_nl_ms > monotonic_ms)
-			timestamp_nl_ms -= WRAP_INTERVAL;
+	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
+		return NULL;
+
+	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
+	case IPOIB_MODE_DATAGRAM:
+		mode = "datagram";
+		break;
+	case IPOIB_MODE_CONNECTED:
+		mode = "connected";
+		break;
+	default:
+		return NULL;
 	}
 
-	return timestamp_nl_ms;
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL);
+	info = &obj->lnk_infiniband;
+
+	info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]);
+	info->mode = mode;
+
+	return obj;
 }
 
-static guint32
-_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out_now_nm)
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 {
-	guint32 last_update_time = rtnl_addr_get_last_update_time ((struct rtnl_addr *) rtnladdr);
-	struct timespec tp;
-	gint64 now_nl, now_nm, result;
+	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+		[IFLA_IPTUN_LINK]        = { .type = NLA_U32 },
+		[IFLA_IPTUN_LOCAL]       = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_REMOTE]      = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_TTL]         = { .type = NLA_U8 },
+		[IFLA_IPTUN_ENCAP_LIMIT] = { .type = NLA_U8 },
+		[IFLA_IPTUN_FLOWINFO]    = { .type = NLA_U32 },
+		[IFLA_IPTUN_PROTO]       = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
 	int err;
+	NMPObject *obj;
+	NMPlatformLnkIp6Tnl *props;
+	guint32 flowinfo;
 
-	/* timestamp is unset. Default to 1. */
-	if (!last_update_time) {
-		if (out_now_nm)
-			*out_now_nm = 0;
-		return 1;
+	if (!info_data || g_strcmp0 (kind, "ip6tnl"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IP6TNL, NULL);
+	props = &obj->lnk_ip6tnl;
+
+	if (tb[IFLA_IPTUN_LINK])
+		props->parent_ifindex = nla_get_u32 (tb[IFLA_IPTUN_LINK]);
+	if (tb[IFLA_IPTUN_LOCAL])
+		memcpy (&props->local, nla_data (tb[IFLA_IPTUN_LOCAL]), sizeof (props->local));
+	if (tb[IFLA_IPTUN_REMOTE])
+		memcpy (&props->remote, nla_data (tb[IFLA_IPTUN_REMOTE]), sizeof (props->remote));
+	if (tb[IFLA_IPTUN_TTL])
+		props->ttl = nla_get_u8 (tb[IFLA_IPTUN_TTL]);
+	if (tb[IFLA_IPTUN_ENCAP_LIMIT])
+		props->encap_limit = nla_get_u8 (tb[IFLA_IPTUN_ENCAP_LIMIT]);
+	if (tb[IFLA_IPTUN_FLOWINFO]) {
+		flowinfo = ntohl (nla_get_u32 (tb[IFLA_IPTUN_FLOWINFO]));
+		props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK;
+		props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT;
 	}
+	if (tb[IFLA_IPTUN_PROTO])
+		props->proto = nla_get_u8 (tb[IFLA_IPTUN_PROTO]);
 
-	/* do all the calculations in milliseconds scale */
+	return obj;
+}
 
-	err = clock_gettime (CLOCK_MONOTONIC, &tp);
-	g_assert (err == 0);
-	now_nm = nm_utils_get_monotonic_timestamp_ms ();
-	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
-	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
+/*****************************************************************************/
 
-	result = now_nm - (now_nl - _timestamp_nl_to_ms (last_update_time, now_nl));
+static NMPObject *
+_parse_lnk_ipip (const char *kind, struct nlattr *info_data)
+{
+	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
+		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
+		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
+		[IFLA_IPTUN_TTL]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_TOS]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_PMTUDISC] = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkIpIp *props;
 
-	if (out_now_nm)
-		*out_now_nm = now_nm / 1000;
+	if (!info_data || g_strcmp0 (kind, "ipip"))
+		return NULL;
 
-	/* converting the last_update_time into nm_utils_get_monotonic_timestamp_ms() scale is
-	 * a good guess but fails in the following situations:
-	 *
-	 * - If the address existed before start of the process, the timestamp in nm scale would
-	 *   be negative or zero. In this case we default to 1.
-	 * - during hibernation, the CLOCK_MONOTONIC/last_update_time drifts from
-	 *   nm_utils_get_monotonic_timestamp_ms() scale.
-	 */
-	if (result <= 1000)
-		return 1;
+	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
 
-	if (result > now_nm)
-		return now_nm / 1000;
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IPIP, NULL);
+	props = &obj->lnk_ipip;
 
-	return result / 1000;
+	props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32 (tb[IFLA_IPTUN_LINK]) : 0;
+	props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32 (tb[IFLA_IPTUN_LOCAL]) : 0;
+	props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32 (tb[IFLA_IPTUN_REMOTE]) : 0;
+	props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8 (tb[IFLA_IPTUN_TOS]) : 0;
+	props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8 (tb[IFLA_IPTUN_TTL]) : 0;
+	props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8 (tb[IFLA_IPTUN_PMTUDISC]);
+
+	return obj;
 }
 
-static guint32
-_extend_lifetime (guint32 lifetime, guint32 seconds)
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 {
-	guint64 v;
+	static struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
+		[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
+		[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
+	};
+	NMPlatformLnkMacvlan *props;
+	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	gboolean tap;
 
-	if (   lifetime == NM_PLATFORM_LIFETIME_PERMANENT
-	    || seconds == 0)
-		return lifetime;
+	if (!info_data)
+		return NULL;
 
-	v = (guint64) lifetime + (guint64) seconds;
-	return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+	if (!g_strcmp0 (kind, "macvlan"))
+		tap = FALSE;
+	else if (!g_strcmp0 (kind, "macvtap"))
+		tap = TRUE;
+	else
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[IFLA_MACVLAN_MODE])
+		return NULL;
+
+	obj = nmp_object_new (tap ? NMP_OBJECT_TYPE_LNK_MACVTAP : NMP_OBJECT_TYPE_LNK_MACVLAN, NULL);
+	props = &obj->lnk_macvlan;
+	props->mode = nla_get_u32 (tb[IFLA_MACVLAN_MODE]);
+	props->tap = tap;
+
+	if (tb[IFLA_MACVLAN_FLAGS])
+		props->no_promisc = NM_FLAGS_HAS (nla_get_u16 (tb[IFLA_MACVLAN_FLAGS]), MACVLAN_FLAG_NOPROMISC);
+
+	return obj;
 }
 
-/* The rtnl_addr object contains relative lifetimes @valid and @preferred
- * that count in seconds, starting from the moment when the kernel constructed
- * the netlink message.
- *
- * There is also a field rtnl_addr_last_update_time(), which is the absolute
- * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
- * was modified (wrapping every 497 days).
- * Immediately at the time when the address was last modified, #NOW and @last_update_time
- * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
- * However, this is not true in general. As time goes by, whenever kernel sends a new address
- * via netlink, the lifetimes keep counting down.
- **/
-static void
-_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr,
-                              guint32 *out_timestamp,
-                              guint32 *out_lifetime,
-                              guint32 *out_preferred)
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_sit (const char *kind, struct nlattr *info_data)
 {
-	guint32 timestamp = 0;
-	gint32 now;
-	guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
-	guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
+	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
+		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
+		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
+		[IFLA_IPTUN_TTL]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_TOS]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_PMTUDISC] = { .type = NLA_U8 },
+		[IFLA_IPTUN_FLAGS]    = { .type = NLA_U16 },
+		[IFLA_IPTUN_PROTO]    = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkSit *props;
 
-	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
-	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
-		if (preferred > lifetime)
-			preferred = lifetime;
-		timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now);
+	if (!info_data || g_strcmp0 (kind, "sit"))
+		return NULL;
 
-		if (now == 0) {
-			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
-			nm_assert (timestamp == 1);
-			now = nm_utils_get_monotonic_timestamp_s ();
-		}
-		if (timestamp < now) {
-			guint32 diff = now - timestamp;
+	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
 
-			lifetime = _extend_lifetime (lifetime, diff);
-			preferred = _extend_lifetime (preferred, diff);
-		} else
-			nm_assert (timestamp == now);
-	}
-	*out_timestamp = timestamp;
-	*out_lifetime = lifetime;
-	*out_preferred = preferred;
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_SIT, NULL);
+	props = &obj->lnk_sit;
+
+	props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32 (tb[IFLA_IPTUN_LINK]) : 0;
+	props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32 (tb[IFLA_IPTUN_LOCAL]) : 0;
+	props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32 (tb[IFLA_IPTUN_REMOTE]) : 0;
+	props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8 (tb[IFLA_IPTUN_TOS]) : 0;
+	props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8 (tb[IFLA_IPTUN_TTL]) : 0;
+	props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8 (tb[IFLA_IPTUN_PMTUDISC]);
+	props->flags = tb[IFLA_IPTUN_FLAGS] ? nla_get_u16 (tb[IFLA_IPTUN_FLAGS]) : 0;
+	props->proto = tb[IFLA_IPTUN_PROTO] ? nla_get_u8 (tb[IFLA_IPTUN_PROTO]) : 0;
+
+	return obj;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/*****************************************************************************/
+
+static gboolean
+_vlan_qos_mapping_from_nla (struct nlattr *nlattr,
+                            const NMVlanQosMapping **out_map,
+                            guint *out_n_map)
 {
-	NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj;
-	struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
-	struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
-	const char *label;
+	struct nlattr *nla;
+	int remaining;
+	gs_unref_ptrarray GPtrArray *array = NULL;
 
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
-		g_return_val_if_reached (FALSE);
+	G_STATIC_ASSERT (sizeof (NMVlanQosMapping) == sizeof (struct ifla_vlan_qos_mapping));
+	G_STATIC_ASSERT (sizeof (((NMVlanQosMapping *) 0)->to) == sizeof (((struct ifla_vlan_qos_mapping *) 0)->to));
+	G_STATIC_ASSERT (sizeof (((NMVlanQosMapping *) 0)->from) == sizeof (((struct ifla_vlan_qos_mapping *) 0)->from));
+	G_STATIC_ASSERT (sizeof (NMVlanQosMapping) == sizeof (((NMVlanQosMapping *) 0)->from) + sizeof (((NMVlanQosMapping *) 0)->to));
 
-	obj->ifindex = rtnl_addr_get_ifindex (nlo);
-	obj->plen = rtnl_addr_get_prefixlen (nlo);
-	memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
+	nm_assert (out_map && !*out_map);
+	nm_assert (out_n_map && !*out_n_map);
 
-	if (id_only)
+	if (!nlattr)
 		return TRUE;
 
-	obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	_nlo_rtnl_addr_get_lifetimes (nlo,
-	                              &obj->timestamp,
-	                              &obj->lifetime,
-	                              &obj->preferred);
-	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
-			g_warn_if_reached ();
-		else
-			memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
+	array = g_ptr_array_new ();
+	nla_for_each_nested (nla, nlattr, remaining) {
+		if (nla_len (nla) < sizeof(NMVlanQosMapping))
+			return FALSE;
+		g_ptr_array_add (array, nla_data (nla));
+	}
+
+	if (array->len > 0) {
+		NMVlanQosMapping *list;
+		guint i, j;
+
+		/* The sorting is necessary, because for egress mapping, kernel
+		 * doesn't sent the items strictly sorted by the from field. */
+		g_ptr_array_sort_with_data (array, _vlan_qos_mapping_cmp_from_ptr, NULL);
+
+		list = g_new (NMVlanQosMapping,  array->len);
+
+		for (i = 0, j = 0; i < array->len; i++) {
+			NMVlanQosMapping *map;
+
+			map = array->pdata[i];
+
+			/* kernel doesn't really send us duplicates. Just be extra cautious
+			 * because we want strong guarantees about the sort order and uniqueness
+			 * of our mapping list (for simpler equality comparison). */
+			if (   j > 0
+			    && list[j - 1].from == map->from)
+				list[j - 1] = *map;
+			else
+				list[j++] = *map;
+		}
+
+		*out_n_map = j;
+		*out_map = list;
 	}
-	label = rtnl_addr_get_label (nlo);
-	/* Check for ':'; we're only interested in labels used as interface aliases */
-	if (label && strchr (label, ':'))
-		g_strlcpy (obj->label, label, sizeof (obj->label));
 
 	return TRUE;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/* Copied and heavily modified from libnl3's vlan_parse() */
+static NMPObject *
+_parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 {
-	NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj;
-	struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
-	struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
+	static struct nla_policy policy[IFLA_VLAN_MAX+1] = {
+		[IFLA_VLAN_ID]          = { .type = NLA_U16 },
+		[IFLA_VLAN_FLAGS]       = { .minlen = sizeof(struct ifla_vlan_flags) },
+		[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
+		[IFLA_VLAN_EGRESS_QOS]  = { .type = NLA_NESTED },
+		[IFLA_VLAN_PROTOCOL]    = { .type = NLA_U16 },
+	};
+	struct nlattr *tb[IFLA_VLAN_MAX+1];
+	int err;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result;
 
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
-		g_return_val_if_reached (FALSE);
+	if (!info_data || g_strcmp0 (kind, "vlan"))
+		return NULL;
+
+	if ((err = nla_parse_nested (tb, IFLA_VLAN_MAX, info_data, policy)) < 0)
+		return NULL;
+
+	if (!tb[IFLA_VLAN_ID])
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
+	obj->lnk_vlan.id = nla_get_u16 (tb[IFLA_VLAN_ID]);
+
+	if (tb[IFLA_VLAN_FLAGS]) {
+		struct ifla_vlan_flags flags;
+
+		nla_memcpy (&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
+
+		obj->lnk_vlan.flags = flags.flags;
+	}
+
+	if (!_vlan_qos_mapping_from_nla (tb[IFLA_VLAN_INGRESS_QOS],
+	                                 &obj->_lnk_vlan.ingress_qos_map,
+	                                 &obj->_lnk_vlan.n_ingress_qos_map))
+		return NULL;
+
+	if (!_vlan_qos_mapping_from_nla (tb[IFLA_VLAN_EGRESS_QOS],
+	                                 &obj->_lnk_vlan.egress_qos_map,
+	                                 &obj->_lnk_vlan.n_egress_qos_map))
+		return NULL;
 
-	obj->ifindex = rtnl_addr_get_ifindex (nlo);
-	obj->plen = rtnl_addr_get_prefixlen (nlo);
-	memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
+
+	obj_result = obj;
+	obj = NULL;
+	return obj_result;
+}
+
+/*****************************************************************************/
+
+/* The installed kernel headers might not have VXLAN stuff at all, or
+ * they might have the original properties, but not PORT, GROUP6, or LOCAL6.
+ * So until we depend on kernel >= 3.11, we just ignore the actual enum
+ * in if_link.h and define the values ourselves.
+ */
+#define IFLA_VXLAN_UNSPEC      0
+#define IFLA_VXLAN_ID          1
+#define IFLA_VXLAN_GROUP       2
+#define IFLA_VXLAN_LINK        3
+#define IFLA_VXLAN_LOCAL       4
+#define IFLA_VXLAN_TTL         5
+#define IFLA_VXLAN_TOS         6
+#define IFLA_VXLAN_LEARNING    7
+#define IFLA_VXLAN_AGEING      8
+#define IFLA_VXLAN_LIMIT       9
+#define IFLA_VXLAN_PORT_RANGE 10
+#define IFLA_VXLAN_PROXY      11
+#define IFLA_VXLAN_RSC        12
+#define IFLA_VXLAN_L2MISS     13
+#define IFLA_VXLAN_L3MISS     14
+#define IFLA_VXLAN_PORT       15
+#define IFLA_VXLAN_GROUP6     16
+#define IFLA_VXLAN_LOCAL6     17
+#undef IFLA_VXLAN_MAX
+#define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6
+
+/* older kernel header might not contain 'struct ifla_vxlan_port_range'.
+ * Redefine it. */
+struct nm_ifla_vxlan_port_range {
+	guint16 low;
+	guint16 high;
+};
+
+static NMPObject *
+_parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
+{
+	static struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
+		[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
+		[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
+		[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
+		                            .minlen = sizeof (struct in6_addr) },
+		[IFLA_VXLAN_LINK]       = { .type = NLA_U32 },
+		[IFLA_VXLAN_LOCAL]      = { .type = NLA_U32 },
+		[IFLA_VXLAN_LOCAL6]     = { .type = NLA_UNSPEC,
+		                            .minlen = sizeof (struct in6_addr) },
+		[IFLA_VXLAN_TOS]        = { .type = NLA_U8 },
+		[IFLA_VXLAN_TTL]        = { .type = NLA_U8 },
+		[IFLA_VXLAN_LEARNING]   = { .type = NLA_U8 },
+		[IFLA_VXLAN_AGEING]     = { .type = NLA_U32 },
+		[IFLA_VXLAN_LIMIT]      = { .type = NLA_U32 },
+		[IFLA_VXLAN_PORT_RANGE] = { .type = NLA_UNSPEC,
+		                            .minlen  = sizeof (struct nm_ifla_vxlan_port_range) },
+		[IFLA_VXLAN_PROXY]      = { .type = NLA_U8 },
+		[IFLA_VXLAN_RSC]        = { .type = NLA_U8 },
+		[IFLA_VXLAN_L2MISS]     = { .type = NLA_U8 },
+		[IFLA_VXLAN_L3MISS]     = { .type = NLA_U8 },
+		[IFLA_VXLAN_PORT]       = { .type = NLA_U16 },
+	};
+	NMPlatformLnkVxlan *props;
+	struct nlattr *tb[IFLA_VXLAN_MAX + 1];
+	struct nm_ifla_vxlan_port_range *range;
+	int err;
+	NMPObject *obj;
+
+	if (!info_data || g_strcmp0 (kind, "vxlan"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
+
+	props = &obj->lnk_vxlan;
+
+	if (tb[IFLA_VXLAN_LINK])
+		props->parent_ifindex = nla_get_u32 (tb[IFLA_VXLAN_LINK]);
+	if (tb[IFLA_VXLAN_ID])
+		props->id = nla_get_u32 (tb[IFLA_VXLAN_ID]);
+	if (tb[IFLA_VXLAN_GROUP])
+		props->group = nla_get_u32 (tb[IFLA_VXLAN_GROUP]);
+	if (tb[IFLA_VXLAN_LOCAL])
+		props->local = nla_get_u32 (tb[IFLA_VXLAN_LOCAL]);
+	if (tb[IFLA_VXLAN_GROUP6])
+		memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6));
+	if (tb[IFLA_VXLAN_LOCAL6])
+		memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6));
+
+	if (tb[IFLA_VXLAN_AGEING])
+		props->ageing = nla_get_u32 (tb[IFLA_VXLAN_AGEING]);
+	if (tb[IFLA_VXLAN_LIMIT])
+		props->limit = nla_get_u32 (tb[IFLA_VXLAN_LIMIT]);
+	if (tb[IFLA_VXLAN_TOS])
+		props->tos = nla_get_u8 (tb[IFLA_VXLAN_TOS]);
+	if (tb[IFLA_VXLAN_TTL])
+		props->ttl = nla_get_u8 (tb[IFLA_VXLAN_TTL]);
+
+	if (tb[IFLA_VXLAN_PORT])
+		props->dst_port = ntohs (nla_get_u16 (tb[IFLA_VXLAN_PORT]));
+
+	if (tb[IFLA_VXLAN_PORT_RANGE]) {
+		range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]);
+		props->src_port_min = ntohs (range->low);
+		props->src_port_max = ntohs (range->high);
+	}
+
+	if (tb[IFLA_VXLAN_LEARNING])
+		props->learning = !!nla_get_u8 (tb[IFLA_VXLAN_LEARNING]);
+	if (tb[IFLA_VXLAN_PROXY])
+		props->proxy = !!nla_get_u8 (tb[IFLA_VXLAN_PROXY]);
+	if (tb[IFLA_VXLAN_RSC])
+		props->rsc = !!nla_get_u8 (tb[IFLA_VXLAN_RSC]);
+	if (tb[IFLA_VXLAN_L2MISS])
+		props->l2miss = !!nla_get_u8 (tb[IFLA_VXLAN_L2MISS]);
+	if (tb[IFLA_VXLAN_L3MISS])
+		props->l3miss = !!nla_get_u8 (tb[IFLA_VXLAN_L3MISS]);
+
+	return obj;
+}
+
+/*****************************************************************************/
+
+/* Copied and heavily modified from libnl3's link_msg_parser(). */
+static NMPObject *
+_new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
+{
+	static struct nla_policy policy[IFLA_MAX+1] = {
+		[IFLA_IFNAME]           = { .type = NLA_STRING,
+		                            .maxlen = IFNAMSIZ },
+		[IFLA_MTU]              = { .type = NLA_U32 },
+		[IFLA_TXQLEN]           = { .type = NLA_U32 },
+		[IFLA_LINK]             = { .type = NLA_U32 },
+		[IFLA_WEIGHT]           = { .type = NLA_U32 },
+		[IFLA_MASTER]           = { .type = NLA_U32 },
+		[IFLA_OPERSTATE]        = { .type = NLA_U8 },
+		[IFLA_LINKMODE]         = { .type = NLA_U8 },
+		[IFLA_LINKINFO]         = { .type = NLA_NESTED },
+		[IFLA_QDISC]            = { .type = NLA_STRING,
+		                            .maxlen = IFQDISCSIZ },
+		[IFLA_STATS]            = { .minlen = sizeof(struct rtnl_link_stats) },
+		[IFLA_STATS64]          = { .minlen = sizeof(struct rtnl_link_stats64)},
+		[IFLA_MAP]              = { .minlen = sizeof(struct rtnl_link_ifmap) },
+		[IFLA_IFALIAS]          = { .type = NLA_STRING, .maxlen = IFALIASZ },
+		[IFLA_NUM_VF]           = { .type = NLA_U32 },
+		[IFLA_AF_SPEC]          = { .type = NLA_NESTED },
+		[IFLA_PROMISCUITY]      = { .type = NLA_U32 },
+		[IFLA_NUM_TX_QUEUES]    = { .type = NLA_U32 },
+		[IFLA_NUM_RX_QUEUES]    = { .type = NLA_U32 },
+		[IFLA_GROUP]            = { .type = NLA_U32 },
+		[IFLA_CARRIER]          = { .type = NLA_U8 },
+		[IFLA_PHYS_PORT_ID]     = { .type = NLA_UNSPEC },
+		[IFLA_NET_NS_PID]       = { .type = NLA_U32 },
+		[IFLA_NET_NS_FD]        = { .type = NLA_U32 },
+	};
+	static struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
+		[IFLA_INFO_KIND]        = { .type = NLA_STRING },
+		[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
+		[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
+	};
+	const struct ifinfomsg *ifi;
+	struct nlattr *tb[IFLA_MAX+1];
+	struct nlattr *li[IFLA_INFO_MAX+1];
+	struct nlattr *nl_info_data = NULL;
+	const char *nl_info_kind = NULL;
+	int err;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result = NULL;
+	gboolean completed_from_cache_val = FALSE;
+	gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL;
+	const NMPObject *link_cached = NULL;
+	NMPObject *lnk_data = NULL;
+
+	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
+		return NULL;
+	ifi = nlmsg_data(nlh);
+
+	obj = nmp_object_new_link (ifi->ifi_index);
 
 	if (id_only)
-		return TRUE;
+		goto id_only_handled;
+
+	err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy);
+	if (err < 0)
+		goto errout;
+
+	if (!tb[IFLA_IFNAME])
+		goto errout;
+	nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
+	if (!obj->link.name[0])
+		goto errout;
 
-	obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	_nlo_rtnl_addr_get_lifetimes (nlo,
-	                              &obj->timestamp,
-	                              &obj->lifetime,
-	                              &obj->preferred);
-	obj->flags = rtnl_addr_get_flags (nlo);
+	if (tb[IFLA_LINKINFO]) {
+		err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info);
+		if (err < 0)
+			goto errout;
 
-	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
-			g_warn_if_reached ();
+		if (li[IFLA_INFO_KIND])
+			nl_info_kind = nla_get_string (li[IFLA_INFO_KIND]);
+
+		nl_info_data = li[IFLA_INFO_DATA];
+	}
+
+	obj->link.flags = ifi->ifi_flags;
+	obj->link.connected = NM_FLAGS_HAS (obj->link.flags, IFF_LOWER_UP);
+	obj->link.arptype = ifi->ifi_type;
+
+	obj->link.type = _linktype_get_type (platform,
+	                                     cache,
+	                                     nl_info_kind,
+	                                     obj->link.ifindex,
+	                                     obj->link.name,
+	                                     obj->link.flags,
+	                                     obj->link.arptype,
+	                                     completed_from_cache,
+	                                     &link_cached,
+	                                     &obj->link.kind);
+
+	if (tb[IFLA_MASTER])
+		obj->link.master = nla_get_u32 (tb[IFLA_MASTER]);
+
+	if (tb[IFLA_LINK]) {
+		if (!tb[IFLA_LINK_NETNSID])
+			obj->link.parent = nla_get_u32 (tb[IFLA_LINK]);
 		else
-			memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
+			obj->link.parent = NM_PLATFORM_LINK_OTHER_NETNS;
 	}
 
-	return TRUE;
+	if (tb[IFLA_ADDRESS]) {
+		int l = nla_len (tb[IFLA_ADDRESS]);
+
+		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
+			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->link.addr.data));
+			memcpy (obj->link.addr.data, nla_data (tb[IFLA_ADDRESS]), l);
+			obj->link.addr.len = l;
+		}
+	}
+
+	if (tb[IFLA_AF_SPEC]) {
+		struct nlattr *af_attr;
+		int remaining;
+
+		nla_for_each_nested (af_attr, tb[IFLA_AF_SPEC], remaining) {
+			switch (nla_type (af_attr)) {
+			case AF_INET6:
+				_parse_af_inet6 (platform,
+				                 af_attr,
+				                 &obj->link.inet6_token.iid,
+				                 &obj->link.inet6_token.is_valid,
+				                 &obj->link.inet6_addr_gen_mode_inv);
+				break;
+			}
+		}
+	}
+
+	if (tb[IFLA_MTU])
+		obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]);
+
+	switch (obj->link.type) {
+	case NM_LINK_TYPE_GRE:
+		lnk_data = _parse_lnk_gre (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_INFINIBAND:
+		lnk_data = _parse_lnk_infiniband (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_IP6TNL:
+		lnk_data = _parse_lnk_ip6tnl (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_IPIP:
+		lnk_data = _parse_lnk_ipip (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_MACVLAN:
+	case NM_LINK_TYPE_MACVTAP:
+		lnk_data = _parse_lnk_macvlan (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_SIT:
+		lnk_data = _parse_lnk_sit (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_VLAN:
+		lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_VXLAN:
+		lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
+		break;
+	default:
+		goto lnk_data_handled;
+	}
+
+	/* We always try to look into the cache and reuse the object there.
+	 * We do that, because we consider the lnk object as immutable and don't
+	 * modify it after creating. Hence we can share it and reuse.
+	 *
+	 * Also, sometimes the info-data is missing for updates. In this case
+	 * we want to keep the previously received lnk_data. */
+	if (completed_from_cache) {
+		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
+		if (   link_cached
+		    && link_cached->link.type == obj->link.type
+		    && (   !lnk_data
+		        || nmp_object_equal (lnk_data, link_cached->_link.netlink.lnk))) {
+			nmp_object_unref (lnk_data);
+			lnk_data = nmp_object_ref (link_cached->_link.netlink.lnk);
+		}
+	}
+
+lnk_data_handled:
+	obj->_link.netlink.lnk = lnk_data;
+
+	obj->_link.netlink.is_in_netlink = TRUE;
+id_only_handled:
+	obj_result = obj;
+	obj = NULL;
+errout:
+	return obj_result;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/* Copied and heavily modified from libnl3's addr_msg_parser(). */
+static NMPObject *
+_new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 {
-	NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj;
-	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
-	struct nl_addr *dst, *gw;
-	struct rtnl_nexthop *nexthop;
-	struct nl_addr *pref_src;
+	static struct nla_policy policy[IFA_MAX+1] = {
+		[IFA_LABEL]     = { .type = NLA_STRING,
+		                     .maxlen = IFNAMSIZ },
+		[IFA_CACHEINFO] = { .minlen = sizeof(struct ifa_cacheinfo) },
+	};
+	const struct ifaddrmsg *ifa;
+	struct nlattr *tb[IFA_MAX+1];
+	int err;
+	gboolean is_v4;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result = NULL;
+	int addr_len;
+	guint32 lifetime, preferred, timestamp;
 
-	if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
-	    rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
-	    rtnl_route_get_tos (nlo) != 0 ||
-	    rtnl_route_get_nnexthops (nlo) != 1)
-		return FALSE;
+	if (!nlmsg_valid_hdr (nlh, sizeof (*ifa)))
+		return NULL;
+	ifa = nlmsg_data(nlh);
 
-	nexthop = rtnl_route_nexthop_n (nlo, 0);
-	if (!nexthop)
-		g_return_val_if_reached (FALSE);
+	if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6))
+		goto errout;
+	is_v4 = ifa->ifa_family == AF_INET;
 
-	dst = rtnl_route_get_dst (nlo);
-	if (!dst)
-		g_return_val_if_reached (FALSE);
+	err = nlmsg_parse(nlh, sizeof(*ifa), tb, IFA_MAX, policy);
+	if (err < 0)
+		goto errout;
+
+	addr_len = is_v4
+	           ? sizeof (in_addr_t)
+	           : sizeof (struct in6_addr);
+
+	/*****************************************************************/
+
+	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL);
+
+	obj->ip_address.ifindex = ifa->ifa_index;
+	obj->ip_address.plen = ifa->ifa_prefixlen;
+
+	_check_addr_or_errout (tb, IFA_ADDRESS, addr_len);
+	_check_addr_or_errout (tb, IFA_LOCAL, addr_len);
+	if (is_v4) {
+		/* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they
+		 * are effectively 0.0.0.0 (all-zero). */
+		if (tb[IFA_LOCAL])
+			memcpy (&obj->ip4_address.address, nla_data (tb[IFA_LOCAL]), addr_len);
+		if (tb[IFA_ADDRESS])
+			memcpy (&obj->ip4_address.peer_address, nla_data (tb[IFA_ADDRESS]), addr_len);
+	} else {
+		/* For IPv6, IFA_ADDRESS is always present.
+		 *
+		 * If IFA_LOCAL is missing, IFA_ADDRESS is @address and @peer_address
+		 * is :: (all-zero).
+		 *
+		 * If unexpectely IFA_ADDRESS is missing, make the best of it -- but it _should_
+		 * actually be there. */
+		if (tb[IFA_ADDRESS] || tb[IFA_LOCAL]) {
+			if (tb[IFA_LOCAL]) {
+				memcpy (&obj->ip6_address.address, nla_data (tb[IFA_LOCAL]), addr_len);
+				if (tb[IFA_ADDRESS])
+					memcpy (&obj->ip6_address.peer_address, nla_data (tb[IFA_ADDRESS]), addr_len);
+				else
+					obj->ip6_address.peer_address = obj->ip6_address.address;
+			} else
+				memcpy (&obj->ip6_address.address, nla_data (tb[IFA_ADDRESS]), addr_len);
+		}
+	}
 
-	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (obj->network))
-			g_return_val_if_reached (FALSE);
-		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
+	obj->ip_address.source = NM_IP_CONFIG_SOURCE_KERNEL;
+
+	if (!is_v4) {
+		obj->ip6_address.flags = tb[IFA_FLAGS]
+		                         ? nla_get_u32 (tb[IFA_FLAGS])
+		                         : ifa->ifa_flags;
 	}
-	obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	obj->plen = nl_addr_get_prefixlen (dst);
-	obj->metric = rtnl_route_get_priority (nlo);
-	obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo));
-
-	gw = rtnl_route_nh_get_gateway (nexthop);
-	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (obj->gateway))
-			g_warn_if_reached ();
-		else
-			memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+
+	if (is_v4) {
+		if (tb[IFA_LABEL]) {
+			char label[IFNAMSIZ];
+
+			nla_strlcpy (label, tb[IFA_LABEL], IFNAMSIZ);
+
+			/* Check for ':'; we're only interested in labels used as interface aliases */
+			if (strchr (label, ':'))
+				g_strlcpy (obj->ip4_address.label, label, sizeof (obj->ip4_address.label));
+		}
 	}
-	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
-	if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) {
+
+	lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
+	preferred = NM_PLATFORM_LIFETIME_PERMANENT;
+	timestamp = 0;
+	/* IPv6 only */
+	if (tb[IFA_CACHEINFO]) {
+		const struct ifa_cacheinfo *ca = nla_data(tb[IFA_CACHEINFO]);
+
+		lifetime = ca->ifa_valid;
+		preferred = ca->ifa_prefered;
+		timestamp = ca->tstamp;
+	}
+	_addrtime_get_lifetimes (timestamp,
+	                         lifetime,
+	                         preferred,
+	                         &obj->ip_address.timestamp,
+	                         &obj->ip_address.lifetime,
+	                         &obj->ip_address.preferred);
+
+	obj_result = obj;
+	obj = NULL;
+errout:
+	return obj_result;
+}
+
+/* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */
+static NMPObject *
+_new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
+{
+	static struct nla_policy policy[RTA_MAX+1] = {
+		[RTA_IIF]       = { .type = NLA_U32 },
+		[RTA_OIF]       = { .type = NLA_U32 },
+		[RTA_PRIORITY]  = { .type = NLA_U32 },
+		[RTA_FLOW]      = { .type = NLA_U32 },
+		[RTA_CACHEINFO] = { .minlen = sizeof(struct rta_cacheinfo) },
+		[RTA_METRICS]   = { .type = NLA_NESTED },
+		[RTA_MULTIPATH] = { .type = NLA_NESTED },
+	};
+	const struct rtmsg *rtm;
+	struct nlattr *tb[RTA_MAX + 1];
+	int err;
+	gboolean is_v4;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result = NULL;
+	int addr_len;
+	struct {
+		gboolean is_present;
+		int ifindex;
+		NMIPAddr gateway;
+	} nh;
+	guint32 mss;
+	guint32 table;
+
+	if (!nlmsg_valid_hdr (nlh, sizeof (*rtm)))
+		return NULL;
+	rtm = nlmsg_data(nlh);
+
+	/*****************************************************************
+	 * only handle ~normal~ routes.
+	 *****************************************************************/
+
+	if (!NM_IN_SET (rtm->rtm_family, AF_INET, AF_INET6))
+		goto errout;
+
+	if (   rtm->rtm_type != RTN_UNICAST
+	    || rtm->rtm_tos != 0)
+		goto errout;
+
+	err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy);
+	if (err < 0)
+		goto errout;
+
+	table = tb[RTA_TABLE]
+	        ? nla_get_u32 (tb[RTA_TABLE])
+	        : (guint32) rtm->rtm_table;
+	if (table != RT_TABLE_MAIN)
+		goto errout;
+
+	/*****************************************************************/
+
+	is_v4 = rtm->rtm_family == AF_INET;
+	addr_len = is_v4
+	           ? sizeof (in_addr_t)
+	           : sizeof (struct in6_addr);
+
+	/*****************************************************************
+	 * parse nexthops. Only handle routes with one nh.
+	 *****************************************************************/
+
+	memset (&nh, 0, sizeof (nh));
+
+	if (tb[RTA_MULTIPATH]) {
+		struct rtnexthop *rtnh = nla_data (tb[RTA_MULTIPATH]);
+		size_t tlen = nla_len(tb[RTA_MULTIPATH]);
+
+		while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) {
+
+			if (nh.is_present) {
+				/* we don't support multipath routes. */
+				goto errout;
+			}
+			nh.is_present = TRUE;
+
+			nh.ifindex = rtnh->rtnh_ifindex;
+
+			if (rtnh->rtnh_len > sizeof(*rtnh)) {
+				struct nlattr *ntb[RTA_MAX + 1];
+
+				err = nla_parse (ntb, RTA_MAX, (struct nlattr *)
+				                 RTNH_DATA(rtnh),
+				                 rtnh->rtnh_len - sizeof (*rtnh),
+				                 policy);
+				if (err < 0)
+					goto errout;
+
+				if (_check_addr_or_errout (ntb, RTA_GATEWAY, addr_len))
+					memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len);
+			}
+
+			tlen -= RTNH_ALIGN(rtnh->rtnh_len);
+			rtnh = RTNH_NEXT(rtnh);
+		}
+	}
+
+	if (   tb[RTA_OIF]
+	    || tb[RTA_GATEWAY]
+	    || tb[RTA_FLOW]) {
+		int ifindex = 0;
+		NMIPAddr gateway = NMIPAddrInit;
+
+		if (tb[RTA_OIF])
+			ifindex = nla_get_u32 (tb[RTA_OIF]);
+		if (_check_addr_or_errout (tb, RTA_GATEWAY, addr_len))
+			memcpy (&gateway, nla_data (tb[RTA_GATEWAY]), addr_len);
+
+		if (!nh.is_present) {
+			/* If no nexthops have been provided via RTA_MULTIPATH
+			 * we add it as regular nexthop to maintain backwards
+			 * compatibility */
+			nh.ifindex = ifindex;
+			nh.gateway = gateway;
+		} else {
+			/* Kernel supports new style nexthop configuration,
+			 * verify that it is a duplicate and ignore old-style nexthop. */
+			if (   nh.ifindex != ifindex
+			    || memcmp (&nh.gateway, &gateway, addr_len) != 0)
+				goto errout;
+		}
+	} else if (!nh.is_present)
+		goto errout;
+
+	/*****************************************************************/
+
+	mss = 0;
+	if (tb[RTA_METRICS]) {
+		struct nlattr *mtb[RTAX_MAX + 1];
+		int i;
+
+		err = nla_parse_nested(mtb, RTAX_MAX, tb[RTA_METRICS], NULL);
+		if (err < 0)
+			goto errout;
+
+		for (i = 1; i <= RTAX_MAX; i++) {
+			if (mtb[i]) {
+				if (i == RTAX_ADVMSS) {
+					if (nla_len (mtb[i]) >= sizeof (uint32_t))
+						mss = nla_get_u32(mtb[i]);
+					break;
+				}
+			}
+		}
+	}
+
+	/*****************************************************************/
+
+	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+
+	obj->ip_route.ifindex = nh.ifindex;
+
+	if (_check_addr_or_errout (tb, RTA_DST, addr_len))
+		memcpy (obj->ip_route.network_ptr, nla_data (tb[RTA_DST]), addr_len);
+
+	obj->ip_route.plen = rtm->rtm_dst_len;
+
+	if (tb[RTA_PRIORITY])
+		obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]);
+
+	if (is_v4)
+		obj->ip4_route.gateway = nh.gateway.addr4;
+	else
+		obj->ip6_route.gateway = nh.gateway.addr6;
+
+	if (is_v4)
+		obj->ip4_route.scope_inv = nm_platform_route_scope_inv (rtm->rtm_scope);
+
+	if (is_v4) {
+		if (_check_addr_or_errout (tb, RTA_PREFSRC, addr_len))
+			memcpy (&obj->ip4_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len);
+	}
+
+	obj->ip_route.mss = mss;
+
+	if (NM_FLAGS_HAS (rtm->rtm_flags, RTM_F_CLONED)) {
 		/* we must not straight way reject cloned routes, because we might have cached
 		 * a non-cloned route. If we now receive an update of the route with the route
 		 * being cloned, we must still return the object, so that we can remove the old
@@ -1361,75 +1876,739 @@ _nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject
 		 *
 		 * This happens, because this route is not nmp_object_is_alive().
 		 * */
-		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+		obj->ip_route.source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
 	} else
-		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
+		obj->ip_route.source = _nm_ip_config_source_from_rtprot (rtm->rtm_protocol);
 
-	pref_src = rtnl_route_get_pref_src (nlo);
-	if (pref_src) {
-		if (nl_addr_get_len (pref_src) != sizeof (obj->pref_src))
-			g_warn_if_reached ();
-		else
-			memcpy (&obj->pref_src, nl_addr_get_binary_addr (pref_src), sizeof (obj->pref_src));
+	obj_result = obj;
+	obj = NULL;
+errout:
+	return obj_result;
+}
+
+/**
+ * nmp_object_new_from_nl:
+ * @platform: (allow-none): for creating certain objects, the constructor wants to check
+ *   sysfs. For this the platform instance is needed. If missing, the object might not
+ *   be correctly detected.
+ * @cache: (allow-none): for certain objects, the netlink message doesn't contain all the information.
+ *   If a cache is given, the object is completed with information from the cache.
+ * @nlh: the netlink message header
+ * @id_only: whether only to create an empty object with only the ID fields set.
+ *
+ * Returns: %NULL or a newly created NMPObject instance.
+ **/
+static NMPObject *
+nmp_object_new_from_nl (NMPlatform *platform, const NMPCache *cache, struct nl_msg *msg, gboolean id_only)
+{
+	struct nlmsghdr *msghdr;
+
+	if (nlmsg_get_proto (msg) != NETLINK_ROUTE)
+		return NULL;
+
+	msghdr = nlmsg_hdr (msg);
+
+	switch (msghdr->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_DELLINK:
+	case RTM_GETLINK:
+	case RTM_SETLINK:
+		return _new_from_nl_link (platform, cache, msghdr, id_only);
+	case RTM_NEWADDR:
+	case RTM_DELADDR:
+	case RTM_GETADDR:
+		return _new_from_nl_addr (msghdr, id_only);
+	case RTM_NEWROUTE:
+	case RTM_DELROUTE:
+	case RTM_GETROUTE:
+		return _new_from_nl_route (msghdr, id_only);
+	default:
+		return NULL;
+	}
+}
+
+/******************************************************************/
+
+static gboolean
+_nl_msg_new_link_set_afspec (struct nl_msg *msg,
+                             int addr_gen_mode)
+{
+	struct nlattr *af_spec;
+	struct nlattr *af_attr;
+
+	nm_assert (msg);
+
+	if (!(af_spec = nla_nest_start (msg, IFLA_AF_SPEC)))
+		goto nla_put_failure;
+
+	if (addr_gen_mode >= 0) {
+		if (!(af_attr = nla_nest_start (msg, AF_INET6)))
+			goto nla_put_failure;
+
+		NLA_PUT_U8 (msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode);
+
+		nla_nest_end (msg, af_attr);
 	}
 
+	nla_nest_end (msg, af_spec);
+
 	return TRUE;
+nla_put_failure:
+	return FALSE;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+static gboolean
+_nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
+                               NMLinkType link_type)
 {
-	NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj;
-	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
-	struct nl_addr *dst, *gw;
-	struct rtnl_nexthop *nexthop;
+	struct nlattr *info;
+	const char *kind;
+
+	nm_assert (msg);
+
+	kind = nm_link_type_to_rtnl_type_string (link_type);
+	if (!kind)
+		goto nla_put_failure;
+
+	if (!(info = nla_nest_start (msg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
+
+	nla_nest_end (msg, info);
+
+	return TRUE;
+nla_put_failure:
+	return FALSE;
+}
+
+static gboolean
+_nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
+                                    int vlan_id,
+                                    guint32 flags_mask,
+                                    guint32 flags_set,
+                                    const NMVlanQosMapping *ingress_qos,
+                                    int ingress_qos_len,
+                                    const NMVlanQosMapping *egress_qos,
+                                    int egress_qos_len)
+{
+	struct nlattr *info;
+	struct nlattr *data;
+	guint i;
+	gboolean has_any_vlan_properties = FALSE;
+
+#define VLAN_XGRESS_PRIO_VALID(from) (((from) & ~(guint32) 0x07) == 0)
+
+	nm_assert (msg);
+
+	/* We must not create an empty IFLA_LINKINFO section. Otherwise, kernel
+	 * rejects the request as invalid. */
+	if (   flags_mask != 0
+	    || vlan_id >= 0)
+		has_any_vlan_properties = TRUE;
+	if (   !has_any_vlan_properties
+	    && ingress_qos && ingress_qos_len > 0) {
+		for (i = 0; i < ingress_qos_len; i++) {
+			if (VLAN_XGRESS_PRIO_VALID (ingress_qos[i].from)) {
+				has_any_vlan_properties = TRUE;
+				break;
+			}
+		}
+	}
+	if (   !has_any_vlan_properties
+	    && egress_qos && egress_qos_len > 0) {
+		for (i = 0; i < egress_qos_len; i++) {
+			if (VLAN_XGRESS_PRIO_VALID (egress_qos[i].to)) {
+				has_any_vlan_properties = TRUE;
+				break;
+			}
+		}
+	}
+	if (!has_any_vlan_properties)
+		return TRUE;
+
+	if (!(info = nla_nest_start (msg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, IFLA_INFO_KIND, "vlan");
+
+	if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
+
+	if (vlan_id >= 0)
+		NLA_PUT_U16 (msg, IFLA_VLAN_ID, vlan_id);
+
+	if (flags_mask != 0) {
+		struct ifla_vlan_flags flags = {
+			.flags = flags_mask & flags_set,
+			.mask = flags_mask,
+		};
+
+		NLA_PUT (msg, IFLA_VLAN_FLAGS, sizeof (flags), &flags);
+	}
+
+	if (ingress_qos && ingress_qos_len > 0) {
+		struct nlattr *qos = NULL;
+
+		for (i = 0; i < ingress_qos_len; i++) {
+			/* Silently ignore invalid mappings. Kernel would truncate
+			 * them and modify the wrong mapping. */
+			if (VLAN_XGRESS_PRIO_VALID (ingress_qos[i].from)) {
+				if (!qos) {
+					if (!(qos = nla_nest_start (msg, IFLA_VLAN_INGRESS_QOS)))
+						goto nla_put_failure;
+				}
+				NLA_PUT (msg, i, sizeof (ingress_qos[i]), &ingress_qos[i]);
+			}
+		}
+
+		if (qos)
+			nla_nest_end (msg, qos);
+	}
+
+	if (egress_qos && egress_qos_len > 0) {
+		struct nlattr *qos = NULL;
+
+		for (i = 0; i < egress_qos_len; i++) {
+			if (VLAN_XGRESS_PRIO_VALID (egress_qos[i].to)) {
+				if (!qos) {
+					if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
+						goto nla_put_failure;
+				}
+				NLA_PUT (msg, i, sizeof (egress_qos[i]), &egress_qos[i]);
+			}
+		}
+
+		if (qos)
+			nla_nest_end(msg, qos);
+	}
+
+	nla_nest_end (msg, data);
+	nla_nest_end (msg, info);
+
+	return TRUE;
+nla_put_failure:
+	return FALSE;
+}
+
+static struct nl_msg *
+_nl_msg_new_link (int nlmsg_type,
+                  int nlmsg_flags,
+                  int ifindex,
+                  const char *ifname,
+                  unsigned flags_mask,
+                  unsigned flags_set)
+{
+	struct nl_msg *msg;
+	struct ifinfomsg ifi = {
+		.ifi_change = flags_mask,
+		.ifi_flags = flags_set,
+		.ifi_index = ifindex,
+	};
+
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK));
+
+	if (!(msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags)))
+		g_return_val_if_reached (NULL);
+
+	if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (ifname)
+		NLA_PUT_STRING (msg, IFLA_IFNAME, ifname);
+
+	return msg;
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
+/* Copied and modified from libnl3's build_addr_msg(). */
+static struct nl_msg *
+_nl_msg_new_address (int nlmsg_type,
+                     int nlmsg_flags,
+                     int family,
+                     int ifindex,
+                     gconstpointer address,
+                     int plen,
+                     gconstpointer peer_address,
+                     guint32 flags,
+                     int scope,
+                     guint32 lifetime,
+                     guint32 preferred,
+                     const char *label)
+{
+	struct nl_msg *msg;
+	struct ifaddrmsg am = {
+		.ifa_family = family,
+		.ifa_index = ifindex,
+		.ifa_prefixlen = plen,
+		.ifa_flags = flags,
+	};
+	gsize addr_len;
+
+	nm_assert (NM_IN_SET (family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWADDR, RTM_DELADDR));
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		g_return_val_if_reached (NULL);
+
+	if (scope == -1) {
+		/* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */
+		if (   family == AF_INET
+		    && address
+		    && *((char *) address) == 127)
+			scope = RT_SCOPE_HOST;
+		else
+			scope = RT_SCOPE_UNIVERSE;
+	}
+	am.ifa_scope = scope,
+
+	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
+
+	if (nlmsg_append (msg, &am, sizeof (am), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (address)
+		NLA_PUT (msg, IFA_LOCAL, addr_len, address);
+
+	if (peer_address)
+		NLA_PUT (msg, IFA_ADDRESS, addr_len, peer_address);
+	else if (address)
+		NLA_PUT (msg, IFA_ADDRESS, addr_len, address);
+
+	if (label && label[0])
+		NLA_PUT_STRING (msg, IFA_LABEL, label);
+
+	if (   family == AF_INET
+	    && nlmsg_type != RTM_DELADDR
+	    && address
+	    && *((in_addr_t *) address) != 0) {
+		in_addr_t broadcast;
+
+		broadcast = *((in_addr_t *) address) | ~nm_utils_ip4_prefix_to_netmask (plen);
+		NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast);
+	}
+
+	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
+	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
+		struct ifa_cacheinfo ca = {
+			.ifa_valid = lifetime,
+			.ifa_prefered = preferred,
+		};
+
+		NLA_PUT (msg, IFA_CACHEINFO, sizeof(ca), &ca);
+	}
+
+	if (flags & ~0xFF) {
+		/* only set the IFA_FLAGS attribute, if they actually contain additional
+		 * flags that are not already set to am.ifa_flags.
+		 *
+		 * Older kernels refuse RTM_NEWADDR and RTM_NEWROUTE messages with EINVAL
+		 * if they contain unknown netlink attributes. See net/core/rtnetlink.c, which
+		 * was fixed by kernel commit 661d2967b3f1b34eeaa7e212e7b9bbe8ee072b59. */
+		NLA_PUT_U32 (msg, IFA_FLAGS, flags);
+	}
+
+	return msg;
+
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
+/* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */
+static struct nl_msg *
+_nl_msg_new_route (int nlmsg_type,
+                   int nlmsg_flags,
+                   int family,
+                   int ifindex,
+                   NMIPConfigSource source,
+                   unsigned char scope,
+                   gconstpointer network,
+                   int plen,
+                   gconstpointer gateway,
+                   guint32 metric,
+                   guint32 mss,
+                   gconstpointer pref_src)
+{
+	struct nl_msg *msg;
+	struct rtmsg rtmsg = {
+		.rtm_family = family,
+		.rtm_tos = 0,
+		.rtm_table = RT_TABLE_MAIN, /* omit setting RTA_TABLE attribute */
+		.rtm_protocol = _nm_ip_config_source_to_rtprot (source),
+		.rtm_scope = scope,
+		.rtm_type = RTN_UNICAST,
+		.rtm_flags = 0,
+		.rtm_dst_len = plen,
+		.rtm_src_len = 0,
+	};
+	NMIPAddr network_clean;
+
+	gsize addr_len;
+
+	nm_assert (NM_IN_SET (family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
+	nm_assert (network);
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		g_return_val_if_reached (NULL);
+
+	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
+
+	clear_host_address (family, network, plen, &network_clean);
+	NLA_PUT (msg, RTA_DST, addr_len, &network_clean);
+
+	NLA_PUT_U32 (msg, RTA_PRIORITY, metric);
+
+	if (pref_src)
+		NLA_PUT (msg, RTA_PREFSRC, addr_len, pref_src);
+
+	if (mss > 0) {
+		struct nlattr *metrics;
+
+		metrics = nla_nest_start (msg, RTA_METRICS);
+		if (!metrics)
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, RTAX_ADVMSS, mss);
+
+		nla_nest_end(msg, metrics);
+	}
+
+	/* We currently don't have need for multi-hop routes... */
+	if (   gateway
+	    && memcmp (gateway, &nm_ip_addr_zero, addr_len) != 0)
+		NLA_PUT (msg, RTA_GATEWAY, addr_len, gateway);
+	NLA_PUT_U32 (msg, RTA_OIF, ifindex);
+
+	return msg;
+
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
+/******************************************************************/
+
+static int _support_kernel_extended_ifa_flags = -1;
+
+#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == -1))
+
+static void
+_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
+{
+	struct nlmsghdr *msg_hdr;
+
+	if (!_support_kernel_extended_ifa_flags_still_undecided ())
+		return;
+
+	msg_hdr = nlmsg_hdr (msg);
+	if (msg_hdr->nlmsg_type != RTM_NEWADDR)
+		return;
+
+	/* the extended address flags are only set for AF_INET6 */
+	if (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family != AF_INET6)
+		return;
+
+	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
+	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
+	 * and IFA_F_NOPREFIXROUTE (they were added together).
+	 **/
+	_support_kernel_extended_ifa_flags = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), 8 /* IFA_FLAGS */);
+	_LOG2D ("support: kernel-extended-ifa-flags: %ssupported", _support_kernel_extended_ifa_flags ? "" : "not ");
+}
+
+static gboolean
+_support_kernel_extended_ifa_flags_get (void)
+{
+	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
+		_LOG2W ("support: kernel-extended-ifa-flags: unable to detect kernel support for handling IPv6 temporary addresses. Assume none");
+		_support_kernel_extended_ifa_flags = 0;
+	}
+	return _support_kernel_extended_ifa_flags;
+}
+
+/******************************************************************
+ * NMPlatform types and functions
+ ******************************************************************/
+
+typedef struct {
+	guint32 seq_number;
+	gint64 timeout_abs_ns;
+	WaitForNlResponseResult seq_result;
+	WaitForNlResponseResult *out_seq_result;
+} DelayedActionWaitForNlResponseData;
+
+typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate;
+
+struct _NMLinuxPlatformPrivate {
+	struct nl_sock *nlh;
+	guint32 nlh_seq_next;
+	guint32 nlh_seq_last_handled;
+	NMPCache *cache;
+	GIOChannel *event_channel;
+	guint event_id;
+
+	gboolean sysctl_get_warned;
+	GHashTable *sysctl_get_prev_values;
+
+	GUdevClient *udev_client;
+
+	struct {
+		DelayedActionType flags;
+		GPtrArray *list_master_connected;
+		GPtrArray *list_refresh_link;
+		GArray *list_wait_for_nl_response;
+		gint is_handling;
+	} delayed_action;
+
+	GHashTable *prune_candidates;
+
+	GHashTable *wifi_data;
+};
+
+static inline NMLinuxPlatformPrivate *
+NM_LINUX_PLATFORM_GET_PRIVATE (const void *self)
+{
+	nm_assert (NM_IS_LINUX_PLATFORM (self));
+
+	return ((NMLinuxPlatform *) self)->priv;
+}
+
+G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
+
+void
+nm_linux_platform_setup (void)
+{
+	g_object_new (NM_TYPE_LINUX_PLATFORM,
+	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
+	              NULL);
+}
 
-	if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
-	    rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
-	    rtnl_route_get_tos (nlo) != 0 ||
-	    rtnl_route_get_nnexthops (nlo) != 1)
+/******************************************************************/
+
+static void
+_log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *value)
+{
+	GError *error = NULL;
+	char *contents, *contents_escaped;
+	char *value_escaped = g_strescape (value, NULL);
+
+	if (!g_file_get_contents (path, &contents, NULL, &error)) {
+		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", path, value_escaped, error->message);
+		g_clear_error (&error);
+	} else {
+		g_strstrip (contents);
+		contents_escaped = g_strescape (contents, NULL);
+		if (strcmp (contents, value) == 0)
+			_LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", path, value_escaped);
+		else
+			_LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", path, value_escaped, contents_escaped);
+		g_free (contents);
+		g_free (contents_escaped);
+	}
+	g_free (value_escaped);
+}
+
+#define _log_dbg_sysctl_set(platform, path, value) \
+	G_STMT_START { \
+		if (_LOGD_ENABLED ()) { \
+			_log_dbg_sysctl_set_impl (platform, path, value); \
+		} \
+	} G_STMT_END
+
+static gboolean
+sysctl_set (NMPlatform *platform, const char *path, const char *value)
+{
+	int fd, len, nwrote, tries;
+	char *actual;
+
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	/* Don't write outside known locations */
+	g_assert (g_str_has_prefix (path, "/proc/sys/")
+	          || g_str_has_prefix (path, "/sys/"));
+	/* Don't write to suspicious locations */
+	g_assert (!strstr (path, "/../"));
+
+	fd = open (path, O_WRONLY | O_TRUNC);
+	if (fd == -1) {
+		if (errno == ENOENT) {
+			_LOGD ("sysctl: failed to open '%s': (%d) %s",
+			       path, errno, strerror (errno));
+		} else {
+			_LOGE ("sysctl: failed to open '%s': (%d) %s",
+			       path, errno, strerror (errno));
+		}
 		return FALSE;
+	}
 
-	nexthop = rtnl_route_nexthop_n (nlo, 0);
-	if (!nexthop)
-		g_return_val_if_reached (FALSE);
+	_log_dbg_sysctl_set (platform, path, value);
 
-	dst = rtnl_route_get_dst (nlo);
-	if (!dst)
-		g_return_val_if_reached (FALSE);
+	/* Most sysfs and sysctl options don't care about a trailing LF, while some
+	 * (like infiniband) do.  So always add the LF.  Also, neither sysfs nor
+	 * sysctl support partial writes so the LF must be added to the string we're
+	 * about to write.
+	 */
+	actual = g_strdup_printf ("%s\n", value);
 
-	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (obj->network))
-			g_return_val_if_reached (FALSE);
-		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
+	/* Try to write the entire value three times if a partial write occurs */
+	len = strlen (actual);
+	for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) {
+		nwrote = write (fd, actual, len);
+		if (nwrote == -1) {
+			if (errno == EINTR) {
+				_LOGD ("sysctl: interrupted, will try again");
+				continue;
+			}
+			break;
+		}
+	}
+	if (nwrote == -1 && errno != EEXIST) {
+		_LOGE ("sysctl: failed to set '%s' to '%s': (%d) %s",
+		       path, value, errno, strerror (errno));
+	} else if (nwrote < len) {
+		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
+		       path, value);
 	}
-	obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	obj->plen = nl_addr_get_prefixlen (dst);
-	obj->metric = rtnl_route_get_priority (nlo);
 
-	if (id_only)
-		return TRUE;
+	g_free (actual);
+	close (fd);
+	return (nwrote == len);
+}
+
+static GSList *sysctl_clear_cache_list;
+
+void
+_nm_linux_platform_sysctl_clear_cache (void)
+{
+	while (sysctl_clear_cache_list) {
+		NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (sysctl_clear_cache_list->data);
+
+		sysctl_clear_cache_list = g_slist_delete_link (sysctl_clear_cache_list, sysctl_clear_cache_list);
+
+		g_hash_table_destroy (priv->sysctl_get_prev_values);
+		priv->sysctl_get_prev_values = NULL;
+		priv->sysctl_get_warned = FALSE;
+	}
+}
+
+static void
+_log_dbg_sysctl_get_impl (NMPlatform *platform, const char *path, const char *contents)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const char *prev_value = NULL;
+
+	if (!priv->sysctl_get_prev_values) {
+		sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform);
+		priv->sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	} else
+		prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, path);
+
+	if (prev_value) {
+		if (strcmp (prev_value, contents) != 0) {
+			char *contents_escaped = g_strescape (contents, NULL);
+			char *prev_value_escaped = g_strescape (prev_value, NULL);
+
+			_LOGD ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", path, contents_escaped, prev_value_escaped);
+			g_free (contents_escaped);
+			g_free (prev_value_escaped);
+			g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
+		}
+	} else {
+		char *contents_escaped = g_strescape (contents, NULL);
 
-	gw = rtnl_route_nh_get_gateway (nexthop);
-	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (obj->gateway))
-			g_warn_if_reached ();
+		_LOGD ("sysctl: reading '%s': '%s'", path, contents_escaped);
+		g_free (contents_escaped);
+		g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
+	}
+
+	if (   !priv->sysctl_get_warned
+	    && g_hash_table_size (priv->sysctl_get_prev_values) > 50000) {
+		_LOGW ("sysctl: the internal cache for debug-logging of sysctl values grew pretty large. You can clear it by disabling debug-logging: `nmcli general logging level KEEP domains PLATFORM:INFO`.");
+		priv->sysctl_get_warned = TRUE;
+	}
+}
+
+#define _log_dbg_sysctl_get(platform, path, contents) \
+	G_STMT_START { \
+		if (_LOGD_ENABLED ()) \
+			_log_dbg_sysctl_get_impl (platform, path, contents); \
+	} G_STMT_END
+
+static char *
+sysctl_get (NMPlatform *platform, const char *path)
+{
+	GError *error = NULL;
+	char *contents;
+
+	/* Don't write outside known locations */
+	g_assert (g_str_has_prefix (path, "/proc/sys/")
+	          || g_str_has_prefix (path, "/sys/"));
+	/* Don't write to suspicious locations */
+	g_assert (!strstr (path, "/../"));
+
+	if (!g_file_get_contents (path, &contents, NULL, &error)) {
+		/* We assume FAILED means EOPNOTSUP */
+		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
+		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED))
+			_LOGD ("error reading %s: %s", path, error->message);
 		else
-			memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+			_LOGE ("error reading %s: %s", path, error->message);
+		g_clear_error (&error);
+		return NULL;
 	}
-	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
-	if (rtnl_route_get_flags (nlo) & RTM_F_CLONED)
-		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
-	else
-		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
 
-	return TRUE;
+	g_strstrip (contents);
+
+	_log_dbg_sysctl_get (platform, path, contents);
+
+	return contents;
 }
 
 /******************************************************************/
 
+static gboolean
+check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+
+	return _support_kernel_extended_ifa_flags_get ();
+}
+
+static gboolean
+check_support_user_ipv6ll (NMPlatform *platform)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+
+	return _support_user_ipv6ll_get ();
+}
+
 static void
-do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible, NMPlatformReason reason)
+process_events (NMPlatform *platform)
+{
+	delayed_action_handle_all (platform, TRUE);
+}
+
+/******************************************************************/
+
+#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
+	((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
+	                                              nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \
+	                                              NULL))
+
+/******************************************************************/
+
+static void
+do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible)
 {
 	gboolean is_visible;
 	NMPObject obj_clone;
@@ -1476,16 +2655,21 @@ do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cach
 
 	klass = NMP_OBJECT_GET_CLASS (obj);
 
-	_LOGt ("emit signal %s %s: %s (%ld)",
+	_LOGt ("emit signal %s %s: %s",
 	       klass->signal_type,
 	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
-	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0),
-	       (long) reason);
+	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
 	/* don't expose @obj directly, but clone the public fields. A signal handler might
 	 * call back into NMPlatform which could invalidate (or modify) @obj. */
 	memcpy (&obj_clone.object, &obj->object, klass->sizeof_public);
-	g_signal_emit_by_name (platform, klass->signal_type, klass->obj_type, obj_clone.object.ifindex, &obj_clone.object, (NMPlatformSignalChangeType) cache_op, reason);
+	g_signal_emit (platform,
+	               _nm_platform_signal_id_get (klass->signal_type_id),
+	               0,
+	               klass->obj_type,
+	               obj_clone.object.ifindex,
+	               &obj_clone.object,
+	               (NMPlatformSignalChangeType) cache_op);
 }
 
 /******************************************************************/
@@ -1528,42 +2712,140 @@ delayed_action_to_string (DelayedActionType action_type)
 	case DELAYED_ACTION_TYPE_REFRESH_LINK                   : return "refresh-link";
 	case DELAYED_ACTION_TYPE_MASTER_CONNECTED               : return "master-connected";
 	case DELAYED_ACTION_TYPE_READ_NETLINK                   : return "read-netlink";
+	case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE           : return "wait-for-nl-response";
 	default:
 		return "unknown";
 	}
 }
 
-#define _LOGt_delayed_action(action_type, arg, operation) \
-    _LOGt ("delayed-action: %s %s (%d) [%p / %d]", ""operation, delayed_action_to_string (action_type), (int) action_type, arg, GPOINTER_TO_INT (arg))
+static const char *
+delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data, char *buf, gsize buf_size)
+{
+	char *buf0 = buf;
+	const DelayedActionWaitForNlResponseData *data;
+
+	nm_utils_strbuf_append_str (&buf, &buf_size, delayed_action_to_string (action_type));
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
+		nm_utils_strbuf_append (&buf, &buf_size, " (master-ifindex %d)", GPOINTER_TO_INT (user_data));
+		break;
+	case DELAYED_ACTION_TYPE_REFRESH_LINK:
+		nm_utils_strbuf_append (&buf, &buf_size, " (ifindex %d)", GPOINTER_TO_INT (user_data));
+		break;
+	case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
+		data = user_data;
+
+		if (data) {
+			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_ns ();
+			char b[255];
+
+			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT"%s%s)",
+			                        data->seq_number,
+			                        timeout < 0 ? "-" : "",
+			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND,
+			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
+			                        data->seq_result ? ", " : "",
+			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : "");
+		} else
+			nm_utils_strbuf_append_str (&buf, &buf_size, " (any)");
+		break;
+	default:
+		nm_assert (!user_data);
+		break;
+	}
+	return buf0;
+}
+
+#define _LOGt_delayed_action(action_type, user_data, operation) \
+    G_STMT_START { \
+        char _buf[255]; \
+        \
+        _LOGt ("delayed-action: %s %s", \
+               ""operation, \
+               delayed_action_to_string_full (action_type, user_data, _buf, sizeof (_buf))); \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void
+delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
+                                              guint idx,
+                                              WaitForNlResponseResult seq_result)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	DelayedActionWaitForNlResponseData *data;
+	WaitForNlResponseResult *out_seq_result;
+
+	nm_assert (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+	nm_assert (idx < priv->delayed_action.list_wait_for_nl_response->len);
+	nm_assert (seq_result);
+
+	data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, idx);
+
+	_LOGt_delayed_action (DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, data, "complete");
+
+	out_seq_result = data->out_seq_result;
+
+	g_array_remove_index_fast (priv->delayed_action.list_wait_for_nl_response, idx);
+	/* Note: @data is invalidated at this point */
+
+	if (priv->delayed_action.list_wait_for_nl_response->len <= 0)
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE;
+
+	if (out_seq_result)
+		*out_seq_result = seq_result;
+}
+
+static void
+delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
+                                                  WaitForNlResponseResult result)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		while (priv->delayed_action.list_wait_for_nl_response->len > 0)
+			delayed_action_wait_for_nl_response_complete (platform, priv->delayed_action.list_wait_for_nl_response->len - 1, result);
+	}
+	nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+	nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
+}
+
+/*****************************************************************************/
 
 static void
 delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nmp_obj NMPObject *obj_cache = NULL;
+	nm_auto_nmpobj NMPObject *obj_cache = NULL;
 	gboolean was_visible;
 	NMPCacheOpsType cache_op;
 
 	cache_op = nmp_cache_update_link_master_connected (priv->cache, master_ifindex, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible);
 }
 
 static void
 delayed_action_handle_REFRESH_LINK (NMPlatform *platform, int ifindex)
 {
-	do_request_link (platform, ifindex, NULL, FALSE);
+	do_request_link_no_delayed_actions (platform, ifindex, NULL);
 }
 
 static void
 delayed_action_handle_REFRESH_ALL (NMPlatform *platform, DelayedActionType flags)
 {
-	do_request_all (platform, flags, FALSE);
+	do_request_all_no_delayed_actions (platform, flags);
 }
 
 static void
 delayed_action_handle_READ_NETLINK (NMPlatform *platform)
 {
-	event_handler_read_netlink_all (platform, TRUE);
+	event_handler_read_netlink (platform, FALSE);
+}
+
+static void
+delayed_action_handle_WAIT_FOR_NL_RESPONSE (NMPlatform *platform)
+{
+	event_handler_read_netlink (platform, TRUE);
 }
 
 static gboolean
@@ -1572,10 +2854,8 @@ delayed_action_handle_one (NMPlatform *platform)
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	gpointer user_data;
 
-	if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE) {
-		nm_clear_g_source (&priv->delayed_action.idle_id);
+	if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE)
 		return FALSE;
-	}
 
 	/* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions.
 	 * This type of action is entirely cache-internal and is here to resolve a
@@ -1622,20 +2902,30 @@ delayed_action_handle_one (NMPlatform *platform)
 		return TRUE;
 	}
 
-	nm_assert (priv->delayed_action.flags == DELAYED_ACTION_TYPE_REFRESH_LINK);
-	nm_assert (priv->delayed_action.list_refresh_link->len > 0);
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
+		nm_assert (priv->delayed_action.list_refresh_link->len > 0);
 
-	user_data = priv->delayed_action.list_refresh_link->pdata[0];
-	g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, 0);
-	if (priv->delayed_action.list_refresh_link->len == 0)
-		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
-	nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0);
+		user_data = priv->delayed_action.list_refresh_link->pdata[0];
+		g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, 0);
+		if (priv->delayed_action.list_refresh_link->len == 0)
+			priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+		nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0);
 
-	_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
+		_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
 
-	delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
+		delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
 
-	return TRUE;
+		return TRUE;
+	}
+
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		nm_assert (priv->delayed_action.list_wait_for_nl_response->len > 0);
+		_LOGt_delayed_action (DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, NULL, "handle");
+		delayed_action_handle_WAIT_FOR_NL_RESPONSE (platform);
+		return TRUE;
+	}
+
+	return FALSE;
 }
 
 static gboolean
@@ -1644,22 +2934,18 @@ delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink)
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	gboolean any = FALSE;
 
-	nm_clear_g_source (&priv->delayed_action.idle_id);
+	g_return_val_if_fail (priv->delayed_action.is_handling == 0, FALSE);
+
 	priv->delayed_action.is_handling++;
 	if (read_netlink)
 		delayed_action_schedule (platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL);
 	while (delayed_action_handle_one (platform))
 		any = TRUE;
 	priv->delayed_action.is_handling--;
-	return any;
-}
 
-static gboolean
-delayed_action_handle_idle (gpointer user_data)
-{
-	NM_LINUX_PLATFORM_GET_PRIVATE (user_data)->delayed_action.idle_id = 0;
-	delayed_action_handle_all (user_data, FALSE);
-	return G_SOURCE_REMOVE;
+	cache_prune_candidates_prune (platform);
+
+	return any;
 }
 
 static void
@@ -1670,16 +2956,25 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 
 	nm_assert (action_type != DELAYED_ACTION_TYPE_NONE);
 
-	if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
-		nm_assert (nm_utils_is_power_of_two (action_type));
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_REFRESH_LINK:
 		if (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0)
 			g_ptr_array_add (priv->delayed_action.list_refresh_link, user_data);
-	} else if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) {
-		nm_assert (nm_utils_is_power_of_two (action_type));
+		break;
+	case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
 		if (_nm_utils_ptrarray_find_first (priv->delayed_action.list_master_connected->pdata, priv->delayed_action.list_master_connected->len, user_data) < 0)
 			g_ptr_array_add (priv->delayed_action.list_master_connected, user_data);
-	} else
+		break;
+	case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
+		g_array_append_vals (priv->delayed_action.list_wait_for_nl_response, user_data, 1);
+		break;
+	default:
 		nm_assert (!user_data);
+		nm_assert (!NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_REFRESH_LINK));
+		nm_assert (!NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED));
+		nm_assert (!NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+		break;
+	}
 
 	priv->delayed_action.flags |= action_type;
 
@@ -1689,9 +2984,22 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 				_LOGt_delayed_action (iflags, user_data, "schedule");
 		}
 	}
+}
 
-	if (priv->delayed_action.is_handling == 0 && priv->delayed_action.idle_id == 0)
-		priv->delayed_action.idle_id = g_idle_add (delayed_action_handle_idle, platform);
+static void
+delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
+                                              guint32 seq_number,
+                                              WaitForNlResponseResult *out_seq_result)
+{
+	DelayedActionWaitForNlResponseData data = {
+		.seq_number = seq_number,
+		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
+		.out_seq_result = out_seq_result,
+	};
+
+	delayed_action_schedule (platform,
+	                         DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE,
+	                         &data);
 }
 
 /******************************************************************/
@@ -1760,11 +3068,11 @@ cache_prune_candidates_prune (NMPlatform *platform)
 
 	g_hash_table_iter_init (&iter, prune_candidates);
 	while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) {
-		auto_nmp_obj NMPObject *obj_cache = NULL;
+		nm_auto_nmpobj NMPObject *obj_cache = NULL;
 
 		_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
 		cache_op = nmp_cache_remove (priv->cache, obj, TRUE, &obj_cache, &was_visible, cache_pre_hook, platform);
-		do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+		do_emit_signal (platform, obj_cache, cache_op, was_visible);
 	}
 
 	g_hash_table_unref (prune_candidates);
@@ -1776,8 +3084,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 	NMPlatform *platform = NM_PLATFORM (user_data);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPClass *klass;
-	char str_buf[sizeof (_nm_platform_to_string_buffer)];
-	char str_buf2[sizeof (_nm_platform_to_string_buffer)];
+	char str_buf[sizeof (_nm_utils_to_string_buffer)];
+	char str_buf2[sizeof (_nm_utils_to_string_buffer)];
 
 	nm_assert (old || new);
 	nm_assert (NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_REMOVED, NMP_CACHE_OPS_UPDATED));
@@ -1850,7 +3158,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 
 			/* removal of a link could be caused by moving the link to another netns.
 			 * In this case, we potentially have to update other links that have this link as parent.
-			 * Currently, kernel misses to sent us a notification in this case (rh #1262908). */
+			 * Currently, kernel misses to sent us a notification in this case
+			 * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */
 
 			if (   ops_type == NMP_CACHE_OPS_REMOVED
 			    && old /* <-- nonsensical, make coverity happy */
@@ -1894,6 +3203,29 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 		    && (new && new->_link.netlink.is_in_netlink)
 		    && (!old || !old->_link.netlink.is_in_netlink))
 		{
+			if (!new->_link.netlink.lnk) {
+				/* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that
+				 * kernel didn't send this notification, thus when we first learn about a link
+				 * that lacks an lnk_data we re-request it again.
+				 *
+				 * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */
+				switch (new->link.type) {
+				case NM_LINK_TYPE_GRE:
+				case NM_LINK_TYPE_IP6TNL:
+				case NM_LINK_TYPE_INFINIBAND:
+				case NM_LINK_TYPE_MACVLAN:
+				case NM_LINK_TYPE_MACVTAP:
+				case NM_LINK_TYPE_SIT:
+				case NM_LINK_TYPE_VLAN:
+				case NM_LINK_TYPE_VXLAN:
+					delayed_action_schedule (platform,
+					                         DELAYED_ACTION_TYPE_REFRESH_LINK,
+					                         GINT_TO_POINTER (new->link.ifindex));
+					break;
+				default:
+					break;
+				}
+			}
 			if (   new->link.type == NM_LINK_TYPE_VETH
 			    && new->link.parent == 0) {
 				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
@@ -1959,100 +3291,74 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 	}
 }
 
-static NMPCacheOpsType
-cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPObject *obj_cache;
-	gboolean was_visible;
-	NMPCacheOpsType cache_op;
-
-	cache_op = nmp_cache_remove_netlink (priv->cache, obj_needle, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
-
-	if (out_obj_cache)
-		*out_obj_cache = obj_cache;
-	else
-		nmp_object_unref (obj_cache);
-	if (out_was_visible)
-		*out_was_visible = was_visible;
-
-	return cache_op;
-}
+/******************************************************************/
 
-static NMPCacheOpsType
-cache_update_netlink (NMPlatform *platform, NMPObject *obj, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason)
+static int
+_nl_send_auto_with_seq (NMPlatform *platform,
+                        struct nl_msg *nlmsg,
+                        WaitForNlResponseResult *out_seq_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPObject *obj_cache;
-	gboolean was_visible;
-	NMPCacheOpsType cache_op;
+	guint32 seq;
+	int nle;
 
-	/* This is basically a convenience method to call nmp_cache_update() and do_emit_signal()
-	 * at once. */
+	/* complete the message with a sequence number (ensuring it's not zero). */
+	seq = priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
 
-	cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, reason);
+	nlmsg_hdr (nlmsg)->nlmsg_seq = seq;
 
-	if (out_obj_cache)
-		*out_obj_cache = obj_cache;
-	else
-		nmp_object_unref (obj_cache);
-	if (out_was_visible)
-		*out_was_visible = was_visible;
+	nle = nl_send_auto (priv->nlh, nlmsg);
 
-	return cache_op;
-}
+	if (nle >= 0) {
+		nle = 0;
+		delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result);
+	} else
+		_LOGD ("netlink: send: failed sending message: %s (%d)", nl_geterror (nle), nle);
 
-/******************************************************************/
+	return nle;
+}
 
 static void
-_new_sequence_number (NMPlatform *platform, guint32 seq)
+do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGt ("_new_sequence_number(): new sequence number %u", seq);
-
-	priv->nlh_seq_expect = seq;
-}
+	if (name && !name[0])
+		name = NULL;
 
-static void
-do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	guint32 seq;
+	g_return_if_fail (ifindex > 0 || name);
 
-	_LOGt ("do_request_link (%d,%s)", ifindex, name ? name : "");
+	_LOGD ("do-request-link: %d %s", ifindex, name ? name : "");
 
 	if (ifindex > 0) {
 		cache_prune_candidates_record_one (platform,
 		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
 	}
 
-	event_handler_read_netlink_all (platform, FALSE);
-
-	if (_nl_sock_request_link (platform, priv->nlh_event, ifindex, name, &seq) == 0)
-		_new_sequence_number (platform, seq);
+	event_handler_read_netlink (platform, FALSE);
 
-	event_handler_read_netlink_all (platform, TRUE);
-
-	cache_prune_candidates_prune (platform);
-
-	if (handle_delayed_action)
-		delayed_action_handle_all (platform, FALSE);
+	nlmsg = _nl_msg_new_link (RTM_GETLINK,
+	                          0,
+	                          ifindex,
+	                          name,
+	                          0,
+	                          0);
+	if (nlmsg)
+		_nl_send_auto_with_seq (platform, nlmsg, NULL);
 }
 
 static void
-do_request_one_type (NMPlatform *platform, NMPObjectType obj_type, gboolean handle_delayed_action)
+do_request_link (NMPlatform *platform, int ifindex, const char *name)
 {
-	do_request_all (platform, delayed_action_refresh_from_object_type (obj_type), handle_delayed_action);
+	do_request_link_no_delayed_actions (platform, ifindex, name);
+	delayed_action_handle_all (platform, FALSE);
 }
 
 static void
-do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action)
+do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	guint32 seq;
 	DelayedActionType iflags;
 
 	nm_assert (!NM_FLAGS_ANY (action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
@@ -2066,6 +3372,12 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha
 	for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
 		if (NM_FLAGS_HAS (action_type, iflags)) {
 			NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
+			const NMPClass *klass = nmp_class_from_type (obj_type);
+			nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+			struct rtgenmsg gmsg = {
+				.rtgen_family = klass->addr_family,
+			};
+			int nle;
 
 			/* clear any delayed action that request a refresh of this object type. */
 			priv->delayed_action.flags &= ~iflags;
@@ -2076,496 +3388,129 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha
 				_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, NULL, "clear (do-request-all)");
 			}
 
-			event_handler_read_netlink_all (platform, FALSE);
+			event_handler_read_netlink (platform, FALSE);
 
-			if (_nl_sock_request_all (platform, priv->nlh_event, obj_type, &seq) == 0)
-				_new_sequence_number (platform, seq);
-		}
-	}
-	event_handler_read_netlink_all (platform, TRUE);
-
-	cache_prune_candidates_prune (platform);
-
-	if (handle_delayed_action)
-		delayed_action_handle_all (platform, FALSE);
-}
-
-static gboolean
-kernel_add_object (NMPlatform *platform, NMPObjectType obj_type, const struct nl_object *nlo)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int nle;
-
-	g_return_val_if_fail (nlo, FALSE);
+			/* reimplement
+			 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
+			 * because we need the sequence number.
+			 */
+			nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+			if (!nlmsg)
+				goto next;
 
-	switch (obj_type) {
-	case NMP_OBJECT_TYPE_LINK:
-		nle = rtnl_link_add (priv->nlh, (struct rtnl_link *) nlo, NLM_F_CREATE);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ADDRESS:
-	case NMP_OBJECT_TYPE_IP6_ADDRESS:
-		nle = rtnl_addr_add (priv->nlh, (struct rtnl_addr *) nlo, NLM_F_CREATE | NLM_F_REPLACE);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ROUTE:
-	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		nle = rtnl_route_add (priv->nlh, (struct rtnl_route *) nlo, NLM_F_CREATE | NLM_F_REPLACE);
-		break;
-	default:
-		g_return_val_if_reached (-NLE_INVAL);
-	}
+			nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
+			if (nle < 0)
+				goto next;
 
-	_LOGt ("kernel-add-%s: returned %s (%d)",
-	       nmp_class_from_type (obj_type)->obj_type_name, nl_geterror (nle), -nle);
-
-	switch (nle) {
-	case -NLE_SUCCESS:
-		return -NLE_SUCCESS;
-	case -NLE_EXIST:
-		/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-		 * never know when something sends an identical object just before
-		 * NetworkManager. */
-		if (obj_type != NMP_OBJECT_TYPE_LINK)
-			return -NLE_SUCCESS;
-		/* fall-through */
-	default:
-		return nle;
+			_nl_send_auto_with_seq (platform, nlmsg, NULL);
+		}
+next:
+		;
 	}
 }
 
-static int
-kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const struct nl_object *object)
+static void
+do_request_one_type (NMPlatform *platform, NMPObjectType obj_type)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int nle;
-
-	switch (object_type) {
-	case NMP_OBJECT_TYPE_LINK:
-		nle = rtnl_link_delete (priv->nlh, (struct rtnl_link *) object);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ADDRESS:
-	case NMP_OBJECT_TYPE_IP6_ADDRESS:
-		nle = rtnl_addr_delete (priv->nlh, (struct rtnl_addr *) object, 0);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ROUTE:
-	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		nle = rtnl_route_delete (priv->nlh, (struct rtnl_route *) object, 0);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-
-	switch (nle) {
-	case -NLE_SUCCESS:
-		return NLE_SUCCESS;
-	case -NLE_OBJ_NOTFOUND:
-		_LOGt ("kernel-delete-%s: failed with \"%s\" (%d), meaning the object was already removed",
-		       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-		return -NLE_SUCCESS;
-	case -NLE_FAILURE:
-		if (object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
-			/* On RHEL7 kernel, deleting a non existing address fails with ENXIO (which libnl maps to NLE_FAILURE) */
-			_LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
-			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-			return NLE_SUCCESS;
-		}
-		break;
-	case -NLE_NOADDR:
-		if (object_type == NMP_OBJECT_TYPE_IP4_ADDRESS || object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
-			_LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
-			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-			return -NLE_SUCCESS;
-		}
-		break;
-	default:
-		break;
-	}
-	_LOGt ("kernel-delete-%s: failed with %s (%d)",
-	       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-	return nle;
+	do_request_all_no_delayed_actions (platform, delayed_action_refresh_from_object_type (obj_type));
+	delayed_action_handle_all (platform, FALSE);
 }
 
-static int
-kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *complete_from_cache)
+static void
+event_seq_check (NMPlatform *platform, struct nl_msg *msg, WaitForNlResponseResult seq_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_msg *msg;
-	int nle;
-	const int nlflags = 0;
-	int ifindex;
-
-	ifindex = rtnl_link_get_ifindex (nlo);
+	DelayedActionWaitForNlResponseData *data;
+	guint32 seq_number;
+	guint i;
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
+	seq_number = nlmsg_hdr (msg)->nlmsg_seq;
 
-	/* Previously, we were using rtnl_link_change(), which builds a request based
-	 * on the diff with an original link instance.
-	 *
-	 * The diff only reused ifi_family, ifi_index, ifi_flags, and name from
-	 * the original link (see rtnl_link_build_change_request()).
-	 *
-	 * We don't do that anymore as we don't have an "orig" netlink instance that
-	 * we can use. Instead the caller must ensure to properly initialize @nlo,
-	 * especially it must set family, ifindex (or ifname) and flags.
-	 * ifname should be set *only* if the caller wishes to change the name.
-	 *
-	 * @complete_from_cache is a convenience to copy the link flags over the link inside
-	 * the platform cache. */
-
-	if (*complete_from_cache) {
-		const NMPObject *obj_cache;
+	if (seq_number == 0)
+		return;
 
-		obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
-		if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) {
-			_LOGt ("kernel-change-link: failure changing link %d: cannot complete link", ifindex);
-			*complete_from_cache = FALSE;
-			return -NLE_INVAL;
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		nm_assert (priv->delayed_action.list_wait_for_nl_response->len > 0);
+
+		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
+			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+			if (data->seq_number == seq_number) {
+				/* We potentially receive many parts partial responses for the same sequence number.
+				 * Thus, we only remember the result, and collect it later. */
+				if (data->seq_result < 0) {
+					/* we already saw an error for this seqence number.
+					 * Preserve it. */
+				} else if (   seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
+				           || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
+					data->seq_result = seq_result;
+				return;
+			}
 		}
-
-		rtnl_link_set_flags (nlo, obj_cache->link.flags);
-
-		/* If the caller wants to rename the link, he should explicitly set
-		 * rtnl_link_set_name(). In all other cases, it should leave the name
-		 * unset. Unfortunately, there is not public API in libnl to modify the
-		 * attribute mask and clear (link->ce_mask = ~LINK_ATTR_IFNAME), so we
-		 * require the caller to do the right thing -- i.e. don't set the name.
-		 */
-	}
-
-	/* We don't use rtnl_link_change() because we have no original rtnl_link object
-	 * at hand. We also don't use rtnl_link_add() because that doesn't have the
-	 * hack to retry with RTM_SETLINK. Reimplement a mix of both. */
-
-	nle = rtnl_link_build_add_request (nlo, nlflags, &msg);
-	if (nle < 0) {
-		_LOGt ("kernel-change-link: failure changing link %d: cannot construct message (%s, %d)",
-		       ifindex, nl_geterror (nle), -nle);
-		return nle;
 	}
 
-retry:
-	nle = nl_send_auto_complete (priv->nlh, msg);
-	if (nle < 0)
-		goto errout;
-
-	nle = nl_wait_for_ack(priv->nlh);
-	if (nle == -NLE_OPNOTSUPP && nlmsg_hdr (msg)->nlmsg_type == RTM_NEWLINK) {
-		nlmsg_hdr (msg)->nlmsg_type = RTM_SETLINK;
-		goto retry;
-	}
-
-errout:
-	nlmsg_free(msg);
-
-	/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-	 * never know when something sends an identical object just before
-	 * NetworkManager.
-	 *
-	 * When netlink returns NLE_OBJ_NOTFOUND, it usually means it failed to find
-	 * firmware for the device, especially on nm_platform_link_set_up ().
-	 * This is basically the same check as in the original code and could
-	 * potentially be improved.
-	 */
-	switch (nle) {
-	case -NLE_SUCCESS:
-		_LOGt ("kernel-change-link: success changing link %d", ifindex);
-		break;
-	case -NLE_EXIST:
-		_LOGt ("kernel-change-link: success changing link %d: %s (%d)",
-		       ifindex, nl_geterror (nle), -nle);
-		break;
-	case -NLE_OBJ_NOTFOUND:
-		_LOGt ("kernel-change-link: failure changing link %d: firmware not found (%s, %d)",
-		       ifindex, nl_geterror (nle), -nle);
-		break;
-	default:
-		_LOGt ("kernel-change-link: failure changing link %d: netlink error (%s, %d)",
-		       ifindex, nl_geterror (nle), -nle);
-		break;
-	}
-
-	return nle;
+	if (seq_number != priv->nlh_seq_last_handled)
+		_LOGt ("netlink: recvmsg: unwaited sequence number %u", seq_number);
+	priv->nlh_seq_last_handled = seq_number;
 }
 
 static void
-ref_object (struct nl_object *obj, void *data)
-{
-	struct nl_object **out = data;
-
-	nl_object_get (obj);
-	*out = obj;
-}
-
-static int
-event_seq_check (struct nl_msg *msg, gpointer user_data)
+event_valid_msg (NMPlatform *platform, struct nl_msg *msg)
 {
-	NMPlatform *platform = NM_PLATFORM (user_data);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nlmsghdr *hdr;
-
-	hdr = nlmsg_hdr (msg);
-
-	if (hdr->nlmsg_seq == 0)
-		return NL_OK;
-
-	priv->nlh_seq_last = hdr->nlmsg_seq;
-
-	if (priv->nlh_seq_expect == 0)
-		_LOGt ("event_seq_check(): seq %u received (not waited)", hdr->nlmsg_seq);
-	else if (hdr->nlmsg_seq == priv->nlh_seq_expect) {
-		_LOGt ("event_seq_check(): seq %u received", hdr->nlmsg_seq);
-
-		priv->nlh_seq_expect = 0;
-	} else
-		_LOGt ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last);
-
-	return NL_OK;
-}
-
-static int
-event_err (struct sockaddr_nl *nla, struct nlmsgerr *nlerr, gpointer platform)
-{
-	_LOGt ("event_err(): error from kernel: %s (%d) for request %d",
-	       strerror (nlerr ? -nlerr->error : 0),
-	       nlerr ? -nlerr->error : 0,
-	       NM_LINUX_PLATFORM_GET_PRIVATE (platform)->nlh_seq_last);
-	return NL_OK;
-}
-
-/* This function does all the magic to avoid race conditions caused
- * by concurrent usage of synchronous commands and an asynchronous cache. This
- * might be a nice future addition to libnl but it requires to do all operations
- * through the cache manager. In this case, nm-linux-platform serves as the
- * cache manager instead of the one provided by libnl.
- */
-static int
-event_notification (struct nl_msg *msg, gpointer user_data)
-{
-	NMPlatform *platform = NM_PLATFORM (user_data);
-	auto_nl_object struct nl_object *nlo = NULL;
-	auto_nmp_obj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj_cache = NULL;
+	NMPCacheOpsType cache_op;
 	struct nlmsghdr *msghdr;
 	char buf_nlmsg_type[16];
+	gboolean id_only = FALSE;
+	gboolean was_visible;
 
 	msghdr = nlmsg_hdr (msg);
 
 	if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR)
 		_support_kernel_extended_ifa_flags_detect (msg);
 
-	nl_msg_parse (msg, ref_object, &nlo);
-	if (!nlo)
-		return NL_OK;
-
-	if (_support_user_ipv6ll_still_undecided() && msghdr->nlmsg_type == RTM_NEWLINK)
-		_support_user_ipv6ll_detect ((struct rtnl_link *) nlo);
-
-	switch (msghdr->nlmsg_type) {
-	case RTM_DELADDR:
-	case RTM_DELLINK:
-	case RTM_DELROUTE:
-		/* The event notifies about a deleted object. We don't need to initialize all the
-		 * fields of the nmp-object. Shortcut nmp_object_from_nl(). */
-		obj = nmp_object_from_nl (platform, nlo, TRUE, TRUE);
-		_LOGT ("event-notification: %s, seq %u: %s",
-		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
-		       msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-		break;
-	default:
-		obj = nmp_object_from_nl (platform, nlo, FALSE, TRUE);
-		_LOGT ("event-notification: %s, seq %u: %s",
-		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
-		       msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-		break;
-	}
-
-	if (obj) {
-		auto_nmp_obj NMPObject *obj_cache = NULL;
-
-		switch (msghdr->nlmsg_type) {
-
-		case RTM_NEWLINK:
-		case RTM_NEWADDR:
-		case RTM_NEWROUTE:
-			cache_update_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
-			break;
-
-		case RTM_DELLINK:
-		case RTM_DELADDR:
-		case RTM_DELROUTE:
-			cache_remove_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
-			break;
-
-		default:
-			break;
-		}
-
-		cache_prune_candidates_drop (platform, obj_cache);
+	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK, RTM_DELADDR, RTM_DELROUTE)) {
+		/* The event notifies about a deleted object. We don't need to initialize all
+		 * fields of the object. */
+		id_only = TRUE;
 	}
 
-	return NL_OK;
-}
-
-/******************************************************************/
-
-static void
-_log_dbg_sysctl_set_impl (const char *path, const char *value)
-{
-	GError *error = NULL;
-	char *contents, *contents_escaped;
-	char *value_escaped = g_strescape (value, NULL);
-
-	if (!g_file_get_contents (path, &contents, NULL, &error)) {
-		debug ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", path, value_escaped, error->message);
-		g_clear_error (&error);
-	} else {
-		g_strstrip (contents);
-		contents_escaped = g_strescape (contents, NULL);
-		if (strcmp (contents, value) == 0)
-			debug ("sysctl: setting '%s' to '%s' (current value is identical)", path, value_escaped);
-		else
-			debug ("sysctl: setting '%s' to '%s' (current value is '%s')", path, value_escaped, contents_escaped);
-		g_free (contents);
-		g_free (contents_escaped);
-	}
-	g_free (value_escaped);
-}
-
-#define _log_dbg_sysctl_set(path, value) \
-	G_STMT_START { \
-		if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) { \
-			_log_dbg_sysctl_set_impl (path, value); \
-		} \
-	} G_STMT_END
-
-static gboolean
-sysctl_set (NMPlatform *platform, const char *path, const char *value)
-{
-	int fd, len, nwrote, tries;
-	char *actual;
-
-	g_return_val_if_fail (path != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	/* Don't write outside known locations */
-	g_assert (g_str_has_prefix (path, "/proc/sys/")
-	          || g_str_has_prefix (path, "/sys/"));
-	/* Don't write to suspicious locations */
-	g_assert (!strstr (path, "/../"));
-
-	fd = open (path, O_WRONLY | O_TRUNC);
-	if (fd == -1) {
-		if (errno == ENOENT) {
-			debug ("sysctl: failed to open '%s': (%d) %s",
-			       path, errno, strerror (errno));
-		} else {
-			error ("sysctl: failed to open '%s': (%d) %s",
-			       path, errno, strerror (errno));
-		}
-		return FALSE;
+	obj = nmp_object_new_from_nl (platform, priv->cache, msg, id_only);
+	if (!obj) {
+		_LOGT ("event-notification: %s, seq %u: ignore",
+		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
+		       msghdr->nlmsg_seq);
+		return;
 	}
 
-	_log_dbg_sysctl_set (path, value);
-
-	/* Most sysfs and sysctl options don't care about a trailing LF, while some
-	 * (like infiniband) do.  So always add the LF.  Also, neither sysfs nor
-	 * sysctl support partial writes so the LF must be added to the string we're
-	 * about to write.
-	 */
-	actual = g_strdup_printf ("%s\n", value);
-
-	/* Try to write the entire value three times if a partial write occurs */
-	len = strlen (actual);
-	for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) {
-		nwrote = write (fd, actual, len);
-		if (nwrote == -1) {
-			if (errno == EINTR) {
-				debug ("sysctl: interrupted, will try again");
-				continue;
-			}
-			break;
-		}
-	}
-	if (nwrote == -1 && errno != EEXIST) {
-		error ("sysctl: failed to set '%s' to '%s': (%d) %s",
-		       path, value, errno, strerror (errno));
-	} else if (nwrote < len) {
-		error ("sysctl: failed to set '%s' to '%s' after three attempts",
-		       path, value);
-	}
+	_LOGT ("event-notification: %s, seq %u: %s",
+	       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
+	       msghdr->nlmsg_seq, nmp_object_to_string (obj,
+	           id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
-	g_free (actual);
-	close (fd);
-	return (nwrote == len);
-}
-
-static GHashTable *sysctl_get_prev_values;
-
-static void
-_log_dbg_sysctl_get_impl (const char *path, const char *contents)
-{
-	const char *prev_value = NULL;
-
-	if (!sysctl_get_prev_values)
-		sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-	else
-		prev_value = g_hash_table_lookup (sysctl_get_prev_values, path);
-
-	if (prev_value) {
-		if (strcmp (prev_value, contents) != 0) {
-			char *contents_escaped = g_strescape (contents, NULL);
-			char *prev_value_escaped = g_strescape (prev_value, NULL);
-
-			debug ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", path, contents_escaped, prev_value_escaped);
-			g_free (contents_escaped);
-			g_free (prev_value_escaped);
-			g_hash_table_insert (sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
-		}
-	} else {
-		char *contents_escaped = g_strescape (contents, NULL);
-
-		debug ("sysctl: reading '%s': '%s'", path, contents_escaped);
-		g_free (contents_escaped);
-		g_hash_table_insert (sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
-	}
-}
-
-#define _log_dbg_sysctl_get(path, contents) \
-	G_STMT_START { \
-		if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) { \
-			_log_dbg_sysctl_get_impl (path, contents); \
-		} else if (sysctl_get_prev_values) { \
-			g_hash_table_destroy (sysctl_get_prev_values); \
-			sysctl_get_prev_values = NULL; \
-		} \
-	} G_STMT_END
+	switch (msghdr->nlmsg_type) {
 
-static char *
-sysctl_get (NMPlatform *platform, const char *path)
-{
-	GError *error = NULL;
-	char *contents;
+	case RTM_NEWLINK:
+	case RTM_NEWADDR:
+	case RTM_NEWROUTE:
+		cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
+		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		break;
 
-	/* Don't write outside known locations */
-	g_assert (g_str_has_prefix (path, "/proc/sys/")
-	          || g_str_has_prefix (path, "/sys/"));
-	/* Don't write to suspicious locations */
-	g_assert (!strstr (path, "/../"));
+	case RTM_DELLINK:
+	case RTM_DELADDR:
+	case RTM_DELROUTE:
+		cache_op = nmp_cache_remove_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
+		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		break;
 
-	if (!g_file_get_contents (path, &contents, NULL, &error)) {
-		/* We assume FAILED means EOPNOTSUP */
-		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
-		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED))
-			debug ("error reading %s: %s", path, error->message);
-		else
-			error ("error reading %s: %s", path, error->message);
-		g_clear_error (&error);
-		return NULL;
+	default:
+		break;
 	}
 
-	g_strstrip (contents);
-
-	_log_dbg_sysctl_get (path, contents);
-
-	return contents;
+	cache_prune_candidates_drop (platform, obj_cache);
 }
 
 /******************************************************************/
@@ -2647,195 +3592,261 @@ _nm_platform_link_get_by_address (NMPlatform *platform,
 	return obj ? &obj->link : NULL;
 }
 
-static struct nl_object *
-build_rtnl_link (int ifindex, const char *name, NMLinkType type)
+/*****************************************************************************/
+
+static const NMPObject *
+link_get_lnk (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
 {
-	struct rtnl_link *rtnllink;
-	int nle;
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
 
-	rtnllink = _nl_rtnl_link_alloc (ifindex, name);
-	if (type) {
-		nle = rtnl_link_set_type (rtnllink, nm_link_type_to_rtnl_type_string (type));
-		g_assert (!nle);
-	}
-	return (struct nl_object *) rtnllink;
-}
+	if (!obj)
+		return NULL;
 
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformLink *obj = (const NMPlatformLink *) _obj;
+	NM_SET_OUT (out_link, &obj->link);
+
+	if (!obj->_link.netlink.lnk)
+		return NULL;
+	if (   link_type != NM_LINK_TYPE_NONE
+	    && (   link_type != obj->link.type
+	        || link_type != NMP_OBJECT_GET_CLASS (obj->_link.netlink.lnk)->lnk_link_type))
+		return NULL;
 
-	return build_rtnl_link (obj->ifindex,
-	                        obj->name[0] ? obj->name : NULL,
-	                        obj->type);
+	return obj->_link.netlink.lnk;
 }
 
+/*****************************************************************************/
+
 static gboolean
-do_add_link (NMPlatform *platform, const char *name, const struct rtnl_link *nlo)
+do_add_link_with_lookup (NMPlatform *platform,
+                         NMLinkType link_type,
+                         const char *name,
+                         struct nl_msg *nlmsg,
+                         const NMPlatformLink **out_link)
 {
-	NMPObject obj_needle;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj = NULL;
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
+	char s_buf[256];
+
+	event_handler_read_netlink (platform, FALSE);
 
-	event_handler_read_netlink_all (platform, FALSE);
+	if (nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
+		/* hm, a link with such a name already exists. Try reloading first. */
+		do_request_link (platform, 0, name);
 
-	nle = kernel_add_object (platform, NMP_OBJECT_TYPE_LINK, (const struct nl_object *) nlo);
+		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
+		if (obj) {
+			_LOGE ("do-add-link[%s/%s]: link already exists: %s",
+			       name,
+			       nm_link_type_to_string (link_type),
+			       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			return FALSE;
+		}
+	}
+
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
 	if (nle < 0) {
-		_LOGE ("do-add-link: failure adding link '%s': %s", name, nl_geterror (nle));
+		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
+		       name,
+		       nm_link_type_to_string (link_type),
+		       nl_geterror (nle), -nle);
 		return FALSE;
 	}
-	_LOGD ("do-add-link: success adding link '%s'", name);
 
-	nmp_object_stackinit_id_link (&obj_needle, 0);
-	g_strlcpy (obj_needle.link.name, name, sizeof (obj_needle.link.name));
+	delayed_action_handle_all (platform, FALSE);
 
-	delayed_action_handle_all (platform, TRUE);
+	nm_assert (seq_result);
 
-	/* FIXME: we add the link object via the second netlink socket. Sometimes,
-	 * the notification is not yet ready via nlh_event, so we have to re-request the
-	 * link so that it is in the cache. A better solution would be to do everything
-	 * via one netlink socket. */
-	if (!nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, 0, obj_needle.link.name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
-		_LOGt ("do-add-link: reload: the added link is not yet ready. Request %s", obj_needle.link.name);
-		do_request_link (platform, 0, obj_needle.link.name, TRUE);
-	}
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_ERR,
+	        "do-add-link[%s/%s]: %s",
+	        name,
+	        nm_link_type_to_string (link_type),
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
-	/* Return true, because kernel_add_object() succeeded. This doesn't indicate that the
-	 * object is now actuall in the cache, because there could be a race.
-	 *
-	 * For that, you'd have to look at @out_obj. */
-	return TRUE;
-}
-
-static gboolean
-do_add_link_with_lookup (NMPlatform *platform, const char *name, const struct rtnl_link *nlo, NMLinkType expected_link_type, NMPlatformLink *out_link)
-{
-	const NMPObject *obj;
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
+		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL);
 
-	do_add_link (platform, name, nlo);
+	if (!obj) {
+		/* either kernel signaled failure, or it signaled success and the link object
+		 * is not (yet) in the cache. Try to reload it... */
+		do_request_link (platform, 0, name);
+		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL);
+	}
 
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                                  0, name, FALSE, expected_link_type, NULL, NULL);
-	if (out_link && obj)
-		*out_link = obj->link;
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
 	return !!obj;
 }
 
 static gboolean
-do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
+do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
+	char s_buf[256];
+	const NMPObject *obj;
 
 	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
 	                      NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS,
 	                      NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	event_handler_read_netlink_all (platform, FALSE);
+	event_handler_read_netlink (platform, FALSE);
 
-	nle = kernel_add_object (platform, NMP_OBJECT_GET_CLASS (obj_id)->obj_type, (const struct nl_object *) nlo);
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
 	if (nle < 0) {
-		_LOGW ("do-add-%s: failure adding %s '%s': %s (%d)",
-		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+		_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 FALSE;
 	}
-	_LOGD ("do-add-%s: success adding object %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 
-	delayed_action_handle_all (platform, TRUE);
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_ERR,
+	        "do-add-%s[%s]: %s",
+	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
-	/* FIXME: instead of re-requesting the added object, add it via nlh_event
-	 * so that the events are in sync. */
-	if (!nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj_id)) {
-		_LOGt ("do-add-%s: reload: the added object is not yet ready. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
+	/* In rare cases, the object is not yet ready as we received the ACK from
+	 * kernel. Need to refetch.
+	 *
+	 * We want to safe the expensive refetch, thus we look first into the cache
+	 * whether the object exists.
+	 *
+	 * FIXME: if the object already existed previously, we might not notice a
+	 * missing update. It's not clear how to fix that reliably without refechting
+	 * all the time. */
+	obj = nmp_cache_lookup_obj (priv->cache, obj_id);
+	if (!obj) {
+		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
+		obj = nmp_cache_lookup_obj (priv->cache, obj_id);
 	}
 
-	/* The return value doesn't say, whether the object is in the platform cache after adding
-	 * it.
-	 * Instead the return value says, whether kernel_add_object() succeeded. */
-	return TRUE;
+	/* Adding is only successful, if kernel reported success *and* we have the
+	 * expected object in cache afterwards. */
+	return obj && seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 }
 
-
 static gboolean
-do_delete_object (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
+do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct nl_object *nlo_free = NULL;
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
+	char s_buf[256];
+	gboolean success = TRUE;
+	const char *log_detail = "";
 
-	event_handler_read_netlink_all (platform, FALSE);
+	event_handler_read_netlink (platform, FALSE);
 
-	if (!nlo)
-		nlo = nlo_free = nmp_object_to_nl (platform, obj_id, FALSE);
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
+	if (nle < 0) {
+		_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);
+		goto out;
+	}
 
-	nle = kernel_delete_object (platform, NMP_OBJECT_GET_TYPE (obj_id), nlo);
-	if (nle < 0)
-		_LOGE ("do-delete-%s: failure deleting '%s': %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);
-	else
-		_LOGD ("do-delete-%s: success deleting '%s'", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+	delayed_action_handle_all (platform, FALSE);
 
-	delayed_action_handle_all (platform, TRUE);
+	nm_assert (seq_result);
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+		/* ok */
+	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT))
+		log_detail = ", meaning the object was already removed";
+	else if (   NM_IN_SET (-((int) seq_result), ENXIO)
+	         && NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) {
+		/* On RHEL7 kernel, deleting a non existing address fails with ENXIO */
+		log_detail = ", meaning the address was already removed";
+	} else if (   NM_IN_SET (-((int) seq_result), EADDRNOTAVAIL)
+	           && NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS))
+		log_detail = ", meaning the address was already removed";
+	else
+		success = FALSE;
 
-	/* FIXME: instead of re-requesting the deleted object, add it via nlh_event
-	 * so that the events are in sync. */
-	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_LINK) {
-		const NMPObject *obj;
+	_NMLOG (success ? LOGL_DEBUG : LOGL_ERR,
+	        "do-delete-%s[%s]: %s%s",
+	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
+	        log_detail);
 
-		obj = nmp_cache_lookup_link_full (priv->cache, obj_id->link.ifindex, obj_id->link.ifindex <= 0 && obj_id->link.name[0] ? obj_id->link.name : NULL, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
-		if (obj && obj->_link.netlink.is_in_netlink) {
-			_LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-			do_request_link (platform, obj_id->link.ifindex, obj_id->link.name, TRUE);
-		}
-	} else {
-		if (nmp_cache_lookup_obj (priv->cache, obj_id)) {
-			_LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
-		}
-	}
+out:
+	if (!nmp_cache_lookup_obj (priv->cache, obj_id))
+		return TRUE;
 
-	/* The return value doesn't say, whether the object is in the platform cache after adding
-	 * it.
-	 * Instead the return value says, whether kernel_add_object() succeeded. */
-	return nle >= 0;
+	/* such an object still exists in the cache. To be sure, refetch it (and
+	 * hope it's gone) */
+	do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
+	return !!nmp_cache_lookup_obj (priv->cache, obj_id);
 }
 
 static NMPlatformError
-do_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean complete_from_cache)
+do_change_link (NMPlatform *platform,
+                int ifindex,
+                struct nl_msg *nlmsg)
 {
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
-	int ifindex;
-	gboolean complete_from_cache2 = complete_from_cache;
+	char s_buf[256];
+	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
+	NMLogLevel log_level = LOGL_DEBUG;
+	const char *log_result = "failure", *log_detail = "";
 
-	ifindex = rtnl_link_get_ifindex (nlo);
-	if (ifindex <= 0)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+retry:
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
+	if (nle < 0) {
+		_LOGE ("do-change-link[%d]: failure sending netlink request \"%s\" (%d)",
+		       ifindex,
+		       nl_geterror (nle), -nle);
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	}
 
-	nle = kernel_change_link (platform, nlo, &complete_from_cache2);
+	/* always refetch the link after changing it. There seems to be issues
+	 * and we sometimes lack events. Nuke it from the orbit... */
+	delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex));
 
-	switch (nle) {
-	case -NLE_SUCCESS:
-		_LOGD ("do-change-link: success changing link %d", ifindex);
-		break;
-	case -NLE_EXIST:
-		_LOGD ("do-change-link: success changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle);
-		break;
-	case -NLE_OBJ_NOTFOUND:
-		/* fall-through */
-	default:
-		if (complete_from_cache != complete_from_cache2)
-			_LOGD ("do-change-link: failure changing link %d: link does not exist in cache", ifindex);
-		else
-			_LOGE ("do-change-link: failure changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle);
-		return nle == -NLE_OBJ_NOTFOUND ? NM_PLATFORM_ERROR_NO_FIRMWARE : NM_PLATFORM_ERROR_UNSPECIFIED;
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	if (   NM_IN_SET (-((int) seq_result), EOPNOTSUPP)
+	    && nlmsg_hdr (nlmsg)->nlmsg_type == RTM_NEWLINK) {
+		nlmsg_hdr (nlmsg)->nlmsg_type = RTM_SETLINK;
+		goto retry;
+	}
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+		log_result = "success";
+	} else if (NM_IN_SET (-((int) seq_result), EEXIST, EADDRINUSE)) {
+		/* */
+	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
+		log_detail = ", firmware not found";
+		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+	} else {
+		log_level = LOGL_ERR;
+		result = NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
+	_NMLOG (log_level,
+	        "do-change-link[%d]: %s changing link: %s%s",
+	        ifindex,
+	        log_result,
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
+	        log_detail);
 
-	/* FIXME: as we modify the link via a separate socket, the cache is not in
-	 * sync and we have to refetch the link. */
-	do_request_link (platform, ifindex, NULL, TRUE);
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return result;
 }
 
 static gboolean
@@ -2844,9 +3855,9 @@ link_add (NMPlatform *platform,
           NMLinkType type,
           const void *address,
           size_t address_len,
-          NMPlatformLink *out_link)
+          const NMPlatformLink **out_link)
 {
-	auto_nl_object struct nl_object *l = NULL;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
 	if (type == NM_LINK_TYPE_BOND) {
 		/* When the kernel loads the bond module, either via explicit modprobe
@@ -2860,34 +3871,50 @@ link_add (NMPlatform *platform,
 			nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
-	debug ("link: add link '%s' of type '%s' (%d)",
+	_LOGD ("link: add link '%s' of type '%s' (%d)",
 	       name, nm_link_type_to_string (type), (int) type);
 
-	l = build_rtnl_link (0, name, type);
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	g_assert ( (address != NULL) ^ (address_len == 0) );
-	if (address) {
-		auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, address_len);
+	if (address && address_len)
+		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
-		rtnl_link_set_addr ((struct rtnl_link *) l, nladdr);
-	}
+	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type))
+		return FALSE;
 
-	return do_add_link_with_lookup (platform, name, (struct rtnl_link *) l, type, out_link);
+	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
 link_delete (NMPlatform *platform, int ifindex)
 {
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
 	obj = nmp_cache_lookup_link (priv->cache, ifindex);
 	if (!obj || !obj->_link.netlink.is_in_netlink)
 		return FALSE;
 
-	nmp_object_stackinit_id_link (&obj_needle, ifindex);
-	return do_delete_object (platform, &obj_needle, NULL);
+	nlmsg = _nl_msg_new_link (RTM_DELLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+
+	nmp_object_stackinit_id_link (&obj_id, ifindex);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static const char *
@@ -2933,33 +3960,35 @@ link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged)
 static gboolean
 link_refresh (NMPlatform *platform, int ifindex)
 {
-	do_request_link (platform, ifindex, NULL, TRUE);
+	do_request_link (platform, ifindex, NULL);
 	return !!cache_lookup_link (platform, ifindex);
 }
 
 static NMPlatformError
-link_change_flags (NMPlatform *platform, int ifindex, unsigned int flags, gboolean value)
-{
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
-	const NMPObject *obj_cache;
-	char buf[256];
-
-	obj_cache = cache_lookup_link (platform, ifindex);
-	if (!obj_cache)
-		return NM_PLATFORM_ERROR_NOT_FOUND;
-
-	rtnl_link_set_flags (change, obj_cache->link.flags);
-	if (value)
-		rtnl_link_set_flags (change, flags);
-	else
-		rtnl_link_unset_flags (change, flags);
-
-	_LOGD ("link: change %d: flags %s '%s' (%d)", ifindex,
-	       value ? "set" : "unset",
-	       rtnl_link_flags2str (flags, buf, sizeof (buf)),
-	       flags);
-
-	return do_change_link (platform, change, FALSE);
+link_change_flags (NMPlatform *platform,
+                   int ifindex,
+                   unsigned flags_mask,
+                   unsigned flags_set)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	char s_flags[100];
+
+	_LOGD ("link: change %d: flags: set 0x%x/0x%x ([%s] / [%s])",
+	       ifindex,
+	       flags_set,
+	       flags_mask,
+	       nm_platform_link_flags2str (flags_set, s_flags, sizeof (s_flags)),
+	       nm_platform_link_flags2str (flags_mask, NULL, 0));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          flags_mask,
+	                          flags_set);
+	if (!nlmsg)
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return do_change_link (platform, ifindex, nlmsg);
 }
 
 static gboolean
@@ -2967,7 +3996,7 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
 	NMPlatformError plerr;
 
-	plerr = link_change_flags (platform, ifindex, IFF_UP, TRUE);
+	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;
@@ -2976,19 +4005,19 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 static gboolean
 link_set_down (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return link_change_flags (platform, ifindex, IFF_UP, 0) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_arp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return link_change_flags (platform, ifindex, IFF_NOARP, 0) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_noarp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	return link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static const char *
@@ -3020,19 +4049,30 @@ link_get_udev_device (NMPlatform *platform, int ifindex)
 static gboolean
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	if (_support_user_ipv6ll_get ()) {
-		auto_nl_object struct rtnl_link *nlo = _nl_rtnl_link_alloc (ifindex, NULL);
-		guint8 mode = enabled ? IN6_ADDR_GEN_MODE_NONE : IN6_ADDR_GEN_MODE_EUI64;
-		char buf[32];
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64;
 
-		rtnl_link_inet6_set_addr_gen_mode (nlo, mode);
-		debug ("link: change %d: set IPv6 address generation mode to %s",
-		       ifindex, rtnl_link_inet6_addrgenmode2str (mode, buf, sizeof (buf)));
-		return do_change_link (platform, nlo, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	if (!_support_user_ipv6ll_get ()) {
+		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
+		return FALSE;
 	}
-#endif
-	return FALSE;
+
+	_LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
+	       ifindex,
+	       nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (   !nlmsg
+	    || !_nl_msg_new_link_set_afspec (nlmsg,
+	                                     mode))
+		g_return_val_if_reached (FALSE);
+
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -3067,16 +4107,30 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 static gboolean
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
-	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, length);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	gs_free char *mac = NULL;
 
-	rtnl_link_set_addr (change, nladdr);
+	if (!address || !length)
+		g_return_val_if_reached (FALSE);
 
-	_LOGD ("link: change %d: address %s (%lu bytes)", ifindex,
+	_LOGD ("link: change %d: address: %s (%lu bytes)", ifindex,
 	       (mac = nm_utils_hwaddr_ntoa (address, length)),
 	       (unsigned long) length);
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
+
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -3091,12 +4145,24 @@ link_get_permanent_address (NMPlatform *platform,
 static gboolean
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	rtnl_link_set_mtu (change, mtu);
-	debug ("link: change %d: mtu %lu", ifindex, (unsigned long)mtu);
+	_LOGD ("link: change %d: mtu: %u", ifindex, (unsigned) mtu);
 
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	NLA_PUT_U32 (nlmsg, IFLA_MTU, mtu);
+
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static char *
@@ -3148,649 +4214,680 @@ vlan_add (NMPlatform *platform,
           int parent,
           int vlan_id,
           guint32 vlan_flags,
-          NMPlatformLink *out_link)
+          const NMPlatformLink **out_link)
 {
-	auto_nl_object struct rtnl_link *rtnllink = (struct rtnl_link *) build_rtnl_link (0, name, NM_LINK_TYPE_VLAN);
-	unsigned int kernel_flags;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	kernel_flags = 0;
-	if (vlan_flags & NM_VLAN_FLAG_REORDER_HEADERS)
-		kernel_flags |= VLAN_FLAG_REORDER_HDR;
-	if (vlan_flags & NM_VLAN_FLAG_GVRP)
-		kernel_flags |= VLAN_FLAG_GVRP;
-	if (vlan_flags & NM_VLAN_FLAG_LOOSE_BINDING)
-		kernel_flags |= VLAN_FLAG_LOOSE_BINDING;
+	G_STATIC_ASSERT (NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
 
-	rtnl_link_set_link (rtnllink, parent);
-	rtnl_link_vlan_set_id (rtnllink, vlan_id);
-	rtnl_link_vlan_set_flags (rtnllink, kernel_flags);
+	vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL;
 
-	debug ("link: add vlan '%s', parent %d, vlan id %d, flags %X (native: %X)",
-	       name, parent, vlan_id, (unsigned int) vlan_flags, kernel_flags);
+	_LOGD ("link: add vlan '%s', parent %d, vlan id %d, flags %X",
+	       name, parent, vlan_id, (unsigned int) vlan_flags);
 
-	return do_add_link_with_lookup (platform, name, rtnllink, NM_LINK_TYPE_VLAN, out_link);
-}
-
-static gboolean
-vlan_get_info (NMPlatform *platform, int ifindex, int *parent, int *vlan_id)
-{
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
-	int p = 0, v = 0;
-
-	if (obj) {
-		p = obj->link.parent;
-		v = obj->link.vlan_id;
-	}
-	if (parent)
-		*parent = p;
-	if (vlan_id)
-		*vlan_id = v;
-	return !!obj;
-}
-
-static gboolean
-vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to)
-{
-	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
-
-	rtnl_link_set_type (change, "vlan");
-	rtnl_link_vlan_set_ingress_map (change, from, to);
-
-	debug ("link: change %d: vlan ingress map %d -> %d", ifindex, from, to);
-
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to)
-{
-	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	rtnl_link_set_type (change, "vlan");
-	rtnl_link_vlan_set_egress_map (change, from, to);
+	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
 
-	debug ("link: change %d: vlan egress map %d -> %d", ifindex, from, to);
+	if (!_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
+	                                         vlan_id,
+	                                         NM_VLAN_FLAGS_ALL,
+	                                         vlan_flags,
+	                                         NULL,
+	                                         0,
+	                                         NULL,
+	                                         0))
+		return FALSE;
 
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-link_enslave (NMPlatform *platform, int master, int slave)
-{
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (slave, NULL);
+static int
+link_gre_add (NMPlatform *platform,
+              const char *name,
+              const NMPlatformLnkGre *props,
+              const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "gre",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	rtnl_link_set_master (change, master);
-	debug ("link: change %d: enslave to master %d", slave, master);
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
-}
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "gre");
 
-static gboolean
-link_release (NMPlatform *platform, int master, int slave)
-{
-	return link_enslave (platform, 0, slave);
-}
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-static char *
-link_option_path (NMPlatform *platform, int master, const char *category, const char *option)
-{
-	const char *name = nm_platform_link_get_name (platform, master);
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_LOCAL, props->local);
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_REMOTE, props->remote);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
 
-	if (!name || !category || !option)
-		return NULL;
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	return g_strdup_printf ("/sys/class/net/%s/%s/%s",
-	                        ASSERT_VALID_PATH_COMPONENT (name),
-	                        ASSERT_VALID_PATH_COMPONENT (category),
-	                        ASSERT_VALID_PATH_COMPONENT (option));
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_GRE, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-link_set_option (NMPlatform *platform, int master, const char *category, const char *option, const char *value)
-{
-	gs_free char *path = link_option_path (platform, master, category, option);
+static int
+link_ip6tnl_add (NMPlatform *platform,
+                 const char *name,
+                 const NMPlatformLnkIp6Tnl *props,
+                 const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+	guint32 flowinfo;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ip6tnl",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet6_ntop (&props->local, NULL),
+	       nm_utils_inet6_ntop (&props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	return path && nm_platform_sysctl_set (platform, path, value);
-}
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static char *
-link_get_option (NMPlatform *platform, int master, const char *category, const char *option)
-{
-	gs_free char *path = link_option_path (platform, master, category, option);
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ip6tnl");
 
-	return path ? nm_platform_sysctl_get (platform, path) : NULL;
-}
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-static const char *
-master_category (NMPlatform *platform, int master)
-{
-	switch (nm_platform_link_get_type (platform, master)) {
-	case NM_LINK_TYPE_BRIDGE:
-		return "bridge";
-	case NM_LINK_TYPE_BOND:
-		return "bonding";
-	default:
-		return NULL;
-	}
-}
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
 
-static const char *
-slave_category (NMPlatform *platform, int slave)
-{
-	int master = nm_platform_link_get_master (platform, slave);
+	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local);
+	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote);
 
-	if (master <= 0)
-		return NULL;
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit);
 
-	switch (nm_platform_link_get_type (platform, master)) {
-	case NM_LINK_TYPE_BRIDGE:
-		return "brport";
-	default:
-		return NULL;
-	}
-}
+	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+	            & IP6_FLOWINFO_TCLASS_MASK;
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto);
 
-static gboolean
-master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
-{
-	return link_set_option (platform, master, master_category (platform, master), option, value);
-}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-static char *
-master_get_option (NMPlatform *platform, int master, const char *option)
-{
-	return link_get_option (platform, master, master_category (platform, master), option);
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value)
-{
-	return link_set_option (platform, slave, slave_category (platform, slave), option, value);
-}
+static int
+link_ipip_add (NMPlatform *platform,
+               const char *name,
+               const NMPlatformLnkIpIp *props,
+               const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ipip",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-static char *
-slave_get_option (NMPlatform *platform, int slave, const char *option)
-{
-	return link_get_option (platform, slave, slave_category (platform, slave), option);
-}
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static gboolean
-infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPObject *obj_parent;
-	const NMPObject *obj;
-	gs_free char *path = NULL;
-	gs_free char *id = NULL;
-	gs_free char *ifname = NULL;
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ipip");
 
-	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
-	if (!obj_parent || !obj_parent->link.name[0])
-		g_return_val_if_reached (FALSE);
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-	ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
 
-	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
-	id = g_strdup_printf ("0x%04x", p_key);
-	if (!nm_platform_sysctl_set (platform, path, id))
-		return FALSE;
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	do_request_link (platform, 0, ifname, TRUE);
-
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                                  0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL);
-	if (out_link && obj)
-		*out_link = obj->link;
-	return !!obj;
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-typedef struct {
-	int p_key;
-	const char *mode;
-} IpoibInfo;
-
-/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers
- * we're building against might not have those properties even though the
- * running kernel might.
- */
-#define IFLA_IPOIB_UNSPEC 0
-#define IFLA_IPOIB_PKEY   1
-#define IFLA_IPOIB_MODE   2
-#define IFLA_IPOIB_UMCAST 3
-#undef IFLA_IPOIB_MAX
-#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST
+static int
+link_macvlan_add (NMPlatform *platform,
+                  const char *name,
+                  int parent,
+                  const NMPlatformLnkMacvlan *props,
+                  const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+
+	_LOGD ("adding %s '%s' parent %u mode %u",
+	       props->tap ? "macvtap" : "macvlan",
+	       name,
+	       parent,
+	       props->mode);
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
-#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
+	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
 
-static const struct nla_policy infiniband_info_policy[IFLA_IPOIB_MAX + 1] = {
-	[IFLA_IPOIB_PKEY]	= { .type = NLA_U16 },
-	[IFLA_IPOIB_MODE]	= { .type = NLA_U16 },
-	[IFLA_IPOIB_UMCAST]	= { .type = NLA_U16 },
-};
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static int
-infiniband_info_data_parser (struct nlattr *info_data, gpointer parser_data)
-{
-	IpoibInfo *info = parser_data;
-	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
-	int err;
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->tap ? "macvtap" : "macvlan");
 
-	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data,
-	                        (struct nla_policy *) infiniband_info_policy);
-	if (err < 0)
-		return err;
-	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
-		return -EINVAL;
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-	info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]);
+	NLA_PUT_U32 (nlmsg, IFLA_MACVLAN_MODE, props->mode);
+	NLA_PUT_U16 (nlmsg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0);
 
-	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
-	case IPOIB_MODE_DATAGRAM:
-		info->mode = "datagram";
-		break;
-	case IPOIB_MODE_CONNECTED:
-		info->mode = "connected";
-		break;
-	default:
-		return -NLE_PARSE_ERR;
-	}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	return 0;
+	return do_add_link_with_lookup (platform,
+	                                props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
+	                                name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-infiniband_get_info (NMPlatform *platform, int ifindex, int *parent, int *p_key, const char **mode)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPObject *obj;
-	IpoibInfo info = { -1, NULL };
-
-	obj = cache_lookup_link (platform, ifindex);
-	if (!obj)
+static int
+link_sit_add (NMPlatform *platform,
+              const char *name,
+              const NMPlatformLnkSit *props,
+              const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "sit",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
 		return FALSE;
 
-	if (parent)
-		*parent = obj->link.parent;
-
-	if (_nl_link_parse_info_data (priv->nlh,
-	                              ifindex,
-	                              infiniband_info_data_parser,
-	                              &info) != 0) {
-		const char *iface = obj->link.name;
-		char *path, *contents = NULL;
-
-		/* Fall back to reading sysfs */
-		path = g_strdup_printf ("/sys/class/net/%s/mode", ASSERT_VALID_PATH_COMPONENT (iface));
-		contents = nm_platform_sysctl_get (platform, path);
-		g_free (path);
-		if (!contents)
-			return FALSE;
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-		if (strstr (contents, "datagram"))
-			info.mode = "datagram";
-		else if (strstr (contents, "connected"))
-			info.mode = "connected";
-		g_free (contents);
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "sit");
 
-		path = g_strdup_printf ("/sys/class/net/%s/pkey", ASSERT_VALID_PATH_COMPONENT (iface));
-		contents = nm_platform_sysctl_get (platform, path);
-		g_free (path);
-		if (!contents)
-			return FALSE;
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-		info.p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
-		g_free (contents);
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
 
-		if (info.p_key < 0)
-			return FALSE;
-	}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	if (p_key)
-		*p_key = info.p_key;
-	if (mode)
-		*mode = info.mode;
-	return TRUE;
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
-veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props)
+link_vxlan_add (NMPlatform *platform,
+                const char *name,
+                const NMPlatformLnkVxlan *props,
+                const NMPlatformLink **out_link)
 {
-	const char *ifname;
-	int peer_ifindex;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	struct nm_ifla_vxlan_port_range port_range;
 
-	ifname = nm_platform_link_get_name (platform, ifindex);
-	if (!ifname)
-		return FALSE;
+	g_return_val_if_fail (props, FALSE);
+
+	_LOGD ("link: add vxlan '%s', parent %d, vxlan id %d",
+	       name, props->parent_ifindex, props->id);
 
-	peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (ifname);
-	if (peer_ifindex <= 0)
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
 		return FALSE;
 
-	props->peer = peer_ifindex;
-	return TRUE;
-}
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static gboolean
-tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props)
-{
-	char *path, *val;
-	gboolean success = TRUE;
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "vxlan");
 
-	g_return_val_if_fail (props, FALSE);
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-	memset (props, 0, sizeof (*props));
-	props->owner = -1;
-	props->group = -1;
+	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_ID, props->id);
 
-	if (!ifname || !nm_utils_iface_valid_name (ifname))
-		return FALSE;
-	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+	if (props->group)
+		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group);
+	else if (memcmp (&props->group6, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6);
 
-	path = g_strdup_printf ("/sys/class/net/%s/owner", ifname);
-	val = nm_platform_sysctl_get (platform, path);
-	g_free (path);
-	if (val) {
-		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
-		if (errno)
-			success = FALSE;
-		g_free (val);
-	} else
-		success = FALSE;
+	if (props->local)
+		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local);
+	else if (memcmp (&props->local6, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6);
 
-	path = g_strdup_printf ("/sys/class/net/%s/group", ifname);
-	val = nm_platform_sysctl_get (platform, path);
-	g_free (path);
-	if (val) {
-		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
-		if (errno)
-			success = FALSE;
-		g_free (val);
-	} else
-		success = FALSE;
+	if (props->parent_ifindex >= 0)
+		NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LINK, props->parent_ifindex);
 
-	path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname);
-	val = nm_platform_sysctl_get (platform, path);
-	g_free (path);
-	if (val) {
-		gint64 flags;
+	if (props->src_port_min || props->src_port_max) {
+		port_range.low = htons (props->src_port_min);
+		port_range.high = htons (props->src_port_max);
+		NLA_PUT (nlmsg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range);
+	}
 
-		flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
-		if (!errno) {
-#ifndef IFF_MULTI_QUEUE
-			const int IFF_MULTI_QUEUE = 0x0100;
-#endif
-			props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
-			props->no_pi = !!(flags & IFF_NO_PI);
-			props->vnet_hdr = !!(flags & IFF_VNET_HDR);
-			props->multi_queue = !!(flags & IFF_MULTI_QUEUE);
-		} else
-			success = FALSE;
-		g_free (val);
-	} else
-		success = FALSE;
+	NLA_PUT_U16 (nlmsg, IFLA_VXLAN_PORT, htons (props->dst_port));
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TTL, props->ttl);
+	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_AGEING, props->ageing);
+	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LIMIT, props->limit);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_LEARNING, !!props->learning);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_PROXY, !!props->proxy);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_RSC, !!props->rsc);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L2MISS, !!props->l2miss);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L3MISS, !!props->l3miss);
 
-	return success;
-}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-static gboolean
-tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
-{
-	return tun_get_properties_ifname (platform, nm_platform_link_get_name (platform, ifindex), props);
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static const struct nla_policy macvlan_info_policy[IFLA_MACVLAN_MAX + 1] = {
-	[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
-#ifdef MACVLAN_FLAG_NOPROMISC
-	[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
-#endif
-};
-
-static int
-macvlan_info_data_parser (struct nlattr *info_data, gpointer parser_data)
-{
-	NMPlatformMacvlanProperties *props = parser_data;
-	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
-	int err;
+static void
+_vlan_change_vlan_qos_mapping_create (gboolean is_ingress_map,
+                                      gboolean reset_all,
+                                      const NMVlanQosMapping *current_map,
+                                      guint current_n_map,
+                                      const NMVlanQosMapping *set_map,
+                                      guint set_n_map,
+                                      NMVlanQosMapping **out_map,
+                                      guint *out_n_map)
+{
+	NMVlanQosMapping *map;
+	guint i, j, len;
+	const guint INGRESS_RANGE_LEN = 8;
+
+	nm_assert (out_map && !*out_map);
+	nm_assert (out_n_map && !*out_n_map);
+
+	if (!reset_all)
+		current_n_map = 0;
+	else if (is_ingress_map)
+		current_n_map = INGRESS_RANGE_LEN;
+
+	len = current_n_map + set_n_map;
+
+	if (len == 0)
+		return;
 
-	err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data,
-	                        (struct nla_policy *) macvlan_info_policy);
-	if (err < 0)
-		return err;
+	map = g_new (NMVlanQosMapping, len);
 
-	switch (nla_get_u32 (tb[IFLA_MACVLAN_MODE])) {
-	case MACVLAN_MODE_PRIVATE:
-		props->mode = "private";
-		break;
-	case MACVLAN_MODE_VEPA:
-		props->mode = "vepa";
-		break;
-	case MACVLAN_MODE_BRIDGE:
-		props->mode = "bridge";
-		break;
-	case MACVLAN_MODE_PASSTHRU:
-		props->mode = "passthru";
-		break;
-	default:
-		return -NLE_PARSE_ERR;
+	if (current_n_map) {
+		if (is_ingress_map) {
+			/* For the ingress-map, there are only 8 entries (0 to 7).
+			 * When the user requests to reset all entires, we don't actually
+			 * need the cached entries, we can just explicitly clear all possible
+			 * ones.
+			 *
+			 * That makes only a real difference in case our cache is out-of-date.
+			 *
+			 * For the egress map we cannot do that, because there are far too
+			 * many. There we can only clear the entries that we know about. */
+			for (i = 0; i < INGRESS_RANGE_LEN; i++) {
+				map[i].from = i;
+				map[i].to = 0;
+			}
+		} else {
+			for (i = 0; i < current_n_map; i++) {
+				map[i].from = current_map[i].from;
+				map[i].to = 0;
+			}
+		}
+	}
+	if (set_n_map)
+		memcpy (&map[current_n_map], set_map, sizeof (*set_map) * set_n_map);
+
+	g_qsort_with_data (map,
+	                   len,
+	                   sizeof (*map),
+	                   _vlan_qos_mapping_cmp_from,
+	                   NULL);
+
+	for (i = 0, j = 0; i < len; i++) {
+		if (   ( is_ingress_map && !VLAN_XGRESS_PRIO_VALID (map[i].from))
+		    || (!is_ingress_map && !VLAN_XGRESS_PRIO_VALID (map[i].to)))
+			continue;
+		if (   j > 0
+		    && map[j - 1].from == map[i].from)
+			map[j - 1] = map[i];
+		else
+			map[j++] = map[i];
 	}
 
-#ifdef MACVLAN_FLAG_NOPROMISC
-	props->no_promisc = !!(nla_get_u16 (tb[IFLA_MACVLAN_FLAGS]) & MACVLAN_FLAG_NOPROMISC);
-#else
-	props->no_promisc = FALSE;
-#endif
-
-	return 0;
+	*out_map = map;
+	*out_n_map = j;
 }
 
 static gboolean
-macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props)
+link_vlan_change (NMPlatform *platform,
+                  int ifindex,
+                  NMVlanFlags flags_mask,
+                  NMVlanFlags flags_set,
+                  gboolean ingress_reset_all,
+                  const NMVlanQosMapping *ingress_map,
+                  gsize n_ingress_map,
+                  gboolean egress_reset_all,
+                  const NMVlanQosMapping *egress_map,
+                  gsize n_egress_map)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int err;
-	const NMPObject *obj;
-
-	obj = cache_lookup_link (platform, ifindex);
-	if (!obj)
+	const NMPObject *obj_cache;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	unsigned flags;
+	const NMPObjectLnkVlan *lnk;
+	guint new_n_ingress_map = 0;
+	guint new_n_egress_map = 0;
+	gs_free NMVlanQosMapping *new_ingress_map = NULL;
+	gs_free NMVlanQosMapping *new_egress_map = NULL;
+	char s_flags[64];
+	char s_ingress[256];
+	char s_egress[256];
+
+	obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
+	if (   !obj_cache
+	    || !obj_cache->_link.netlink.is_in_netlink) {
+		_LOGD ("link: change %d: %s: link does not exist", ifindex, "vlan");
 		return FALSE;
-
-	props->parent_ifindex = obj->link.parent;
-
-	err = _nl_link_parse_info_data (priv->nlh, ifindex,
-	                                macvlan_info_data_parser, props);
-	if (err != 0) {
-		warning ("(%s) could not read properties: %s",
-		         obj->link.name, nl_geterror (err));
 	}
-	return (err == 0);
-}
-
-/* The installed kernel headers might not have VXLAN stuff at all, or
- * they might have the original properties, but not PORT, GROUP6, or LOCAL6.
- * So until we depend on kernel >= 3.11, we just ignore the actual enum
- * in if_link.h and define the values ourselves.
- */
-#define IFLA_VXLAN_UNSPEC      0
-#define IFLA_VXLAN_ID          1
-#define IFLA_VXLAN_GROUP       2
-#define IFLA_VXLAN_LINK        3
-#define IFLA_VXLAN_LOCAL       4
-#define IFLA_VXLAN_TTL         5
-#define IFLA_VXLAN_TOS         6
-#define IFLA_VXLAN_LEARNING    7
-#define IFLA_VXLAN_AGEING      8
-#define IFLA_VXLAN_LIMIT       9
-#define IFLA_VXLAN_PORT_RANGE 10
-#define IFLA_VXLAN_PROXY      11
-#define IFLA_VXLAN_RSC        12
-#define IFLA_VXLAN_L2MISS     13
-#define IFLA_VXLAN_L3MISS     14
-#define IFLA_VXLAN_PORT       15
-#define IFLA_VXLAN_GROUP6     16
-#define IFLA_VXLAN_LOCAL6     17
-#undef IFLA_VXLAN_MAX
-#define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6
 
-/* older kernel header might not contain 'struct ifla_vxlan_port_range'.
- * Redefine it. */
-struct nm_ifla_vxlan_port_range {
-	guint16 low;
-	guint16 high;
-};
+	lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL;
+	flags = obj_cache->link.flags;
+
+	flags_set &= flags_mask;
+
+	_vlan_change_vlan_qos_mapping_create (TRUE,
+	                                      ingress_reset_all,
+	                                      lnk ? lnk->ingress_qos_map : NULL,
+	                                      lnk ? lnk->n_ingress_qos_map : 0,
+	                                      ingress_map,
+	                                      n_ingress_map,
+	                                      &new_ingress_map,
+	                                      &new_n_ingress_map);
+
+	_vlan_change_vlan_qos_mapping_create (FALSE,
+	                                      egress_reset_all,
+	                                      lnk ? lnk->egress_qos_map : NULL,
+	                                      lnk ? lnk->n_egress_qos_map : 0,
+	                                      egress_map,
+	                                      n_egress_map,
+	                                      &new_egress_map,
+	                                      &new_n_egress_map);
+
+	_LOGD ("link: change %d: vlan:%s%s%s",
+	       ifindex,
+	       flags_mask
+	           ? nm_sprintf_buf (s_flags, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask)
+	           : "",
+	       new_n_ingress_map
+	           ? nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map",
+	                                                     new_ingress_map,
+	                                                     new_n_ingress_map,
+	                                                     s_ingress,
+	                                                     sizeof (s_ingress))
+	           : "",
+	       new_n_egress_map
+	           ? nm_platform_vlan_qos_mapping_to_string (" egress-qos-map",
+	                                                     new_egress_map,
+	                                                     new_n_egress_map,
+	                                                     s_egress,
+	                                                     sizeof (s_egress))
+	           : "");
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (   !nlmsg
+	    || !_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
+	                                            -1,
+	                                            flags_mask,
+	                                            flags_set,
+	                                            new_ingress_map,
+	                                            new_n_ingress_map,
+	                                            new_egress_map,
+	                                            new_n_egress_map))
+		g_return_val_if_reached (FALSE);
 
-static const struct nla_policy vxlan_info_policy[IFLA_VXLAN_MAX + 1] = {
-	[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
-	[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
-	[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
-	                            .minlen = sizeof (struct in6_addr) },
-	[IFLA_VXLAN_LINK]       = { .type = NLA_U32 },
-	[IFLA_VXLAN_LOCAL]      = { .type = NLA_U32 },
-	[IFLA_VXLAN_LOCAL6]     = { .type = NLA_UNSPEC,
-	                            .minlen = sizeof (struct in6_addr) },
-	[IFLA_VXLAN_TOS]        = { .type = NLA_U8 },
-	[IFLA_VXLAN_TTL]        = { .type = NLA_U8 },
-	[IFLA_VXLAN_LEARNING]   = { .type = NLA_U8 },
-	[IFLA_VXLAN_AGEING]     = { .type = NLA_U32 },
-	[IFLA_VXLAN_LIMIT]      = { .type = NLA_U32 },
-	[IFLA_VXLAN_PORT_RANGE] = { .type = NLA_UNSPEC,
-	                            .minlen  = sizeof (struct nm_ifla_vxlan_port_range) },
-	[IFLA_VXLAN_PROXY]      = { .type = NLA_U8 },
-	[IFLA_VXLAN_RSC]        = { .type = NLA_U8 },
-	[IFLA_VXLAN_L2MISS]     = { .type = NLA_U8 },
-	[IFLA_VXLAN_L3MISS]     = { .type = NLA_U8 },
-	[IFLA_VXLAN_PORT]       = { .type = NLA_U16 },
-};
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+}
 
 static int
-vxlan_info_data_parser (struct nlattr *info_data, gpointer parser_data)
+tun_add (NMPlatform *platform, const char *name, gboolean tap,
+         gint64 owner, gint64 group, gboolean pi, gboolean vnet_hdr,
+         gboolean multi_queue, const NMPlatformLink **out_link)
 {
-	NMPlatformVxlanProperties *props = parser_data;
-	struct nlattr *tb[IFLA_VXLAN_MAX + 1];
-	struct nm_ifla_vxlan_port_range *range;
-	int err;
+	const NMPObject *obj;
+	struct ifreq ifr = { };
+	int fd;
 
-	err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data,
-	                        (struct nla_policy *) vxlan_info_policy);
-	if (err < 0)
-		return err;
+	_LOGD ("link: add %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
+	       tap ? "tap" : "tun", name, owner, group);
 
-	memset (props, 0, sizeof (*props));
+	fd = open ("/dev/net/tun", O_RDWR);
+	if (fd < 0)
+		return FALSE;
 
-	if (tb[IFLA_VXLAN_LINK])
-		props->parent_ifindex = nla_get_u32 (tb[IFLA_VXLAN_LINK]);
-	if (tb[IFLA_VXLAN_ID])
-		props->id = nla_get_u32 (tb[IFLA_VXLAN_ID]);
-	if (tb[IFLA_VXLAN_GROUP])
-		props->group = nla_get_u32 (tb[IFLA_VXLAN_GROUP]);
-	if (tb[IFLA_VXLAN_LOCAL])
-		props->local = nla_get_u32 (tb[IFLA_VXLAN_LOCAL]);
-	if (tb[IFLA_VXLAN_GROUP6])
-		memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6));
-	if (tb[IFLA_VXLAN_LOCAL6])
-		memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6));
+	strncpy (ifr.ifr_name, name, IFNAMSIZ);
+	ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN;
 
-	if (tb[IFLA_VXLAN_AGEING])
-		props->ageing = nla_get_u32 (tb[IFLA_VXLAN_AGEING]);
-	if (tb[IFLA_VXLAN_LIMIT])
-		props->limit = nla_get_u32 (tb[IFLA_VXLAN_LIMIT]);
-	if (tb[IFLA_VXLAN_TOS])
-		props->tos = nla_get_u8 (tb[IFLA_VXLAN_TOS]);
-	if (tb[IFLA_VXLAN_TTL])
-		props->ttl = nla_get_u8 (tb[IFLA_VXLAN_TTL]);
+	if (!pi)
+		ifr.ifr_flags |= IFF_NO_PI;
+	if (vnet_hdr)
+		ifr.ifr_flags |= IFF_VNET_HDR;
+	if (multi_queue)
+		ifr.ifr_flags |= NM_IFF_MULTI_QUEUE;
 
-	if (tb[IFLA_VXLAN_PORT])
-		props->dst_port = nla_get_u16 (tb[IFLA_VXLAN_PORT]);
+	if (ioctl (fd, TUNSETIFF, &ifr)) {
+		close (fd);
+		return FALSE;
+	}
 
-	if (tb[IFLA_VXLAN_PORT_RANGE]) {
-		range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]);
-		props->src_port_min = range->low;
-		props->src_port_max = range->high;
+	if (owner >= 0 && owner < G_MAXINT32) {
+		if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) {
+			close (fd);
+			return FALSE;
+		}
 	}
 
-	if (tb[IFLA_VXLAN_LEARNING])
-		props->learning = !!nla_get_u8 (tb[IFLA_VXLAN_LEARNING]);
-	if (tb[IFLA_VXLAN_PROXY])
-		props->proxy = !!nla_get_u8 (tb[IFLA_VXLAN_PROXY]);
-	if (tb[IFLA_VXLAN_RSC])
-		props->rsc = !!nla_get_u8 (tb[IFLA_VXLAN_RSC]);
-	if (tb[IFLA_VXLAN_L2MISS])
-		props->l2miss = !!nla_get_u8 (tb[IFLA_VXLAN_L2MISS]);
-	if (tb[IFLA_VXLAN_L3MISS])
-		props->l3miss = !!nla_get_u8 (tb[IFLA_VXLAN_L3MISS]);
+	if (group >= 0 && group < G_MAXINT32) {
+		if (ioctl (fd, TUNSETGROUP, (gid_t) group)) {
+			close (fd);
+			return FALSE;
+		}
+	}
 
-	return 0;
+	if (ioctl (fd, TUNSETPERSIST, 1)) {
+		close (fd);
+		return FALSE;
+	}
+	do_request_link (platform, 0, name);
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, name, FALSE,
+	                                  tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN,
+	                                  NULL, NULL);
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
+
+	return !!obj;
 }
 
 static gboolean
-vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperties *props)
+link_enslave (NMPlatform *platform, int master, int slave)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int err;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	int ifindex = slave;
 
-	err = _nl_link_parse_info_data (priv->nlh, ifindex,
-	                                vxlan_info_data_parser, props);
-	if (err != 0) {
-		warning ("(%s) could not read vxlan properties: %s",
-		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
-	}
-	return (err == 0);
-}
-
-static const struct nla_policy gre_info_policy[IFLA_GRE_MAX + 1] = {
-	[IFLA_GRE_LINK]     = { .type = NLA_U32 },
-	[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
-	[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
-	[IFLA_GRE_IKEY]     = { .type = NLA_U32 },
-	[IFLA_GRE_OKEY]     = { .type = NLA_U32 },
-	[IFLA_GRE_LOCAL]    = { .type = NLA_U32 },
-	[IFLA_GRE_REMOTE]   = { .type = NLA_U32 },
-	[IFLA_GRE_TTL]      = { .type = NLA_U8 },
-	[IFLA_GRE_TOS]      = { .type = NLA_U8 },
-	[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
-};
+	_LOGD ("link: change %d: enslave: master %d", slave, master);
 
-static int
-gre_info_data_parser (struct nlattr *info_data, gpointer parser_data)
-{
-	NMPlatformGreProperties *props = parser_data;
-	struct nlattr *tb[IFLA_GRE_MAX + 1];
-	int err;
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data,
-	                        (struct nla_policy *) gre_info_policy);
-	if (err < 0)
-		return err;
+	NLA_PUT_U32 (nlmsg, IFLA_MASTER, master);
 
-	props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0;
-	props->input_flags = nla_get_u16 (tb[IFLA_GRE_IFLAGS]);
-	props->output_flags = nla_get_u16 (tb[IFLA_GRE_OFLAGS]);
-	props->input_key = (props->input_flags & GRE_KEY) ? nla_get_u32 (tb[IFLA_GRE_IKEY]) : 0;
-	props->output_key = (props->output_flags & GRE_KEY) ? nla_get_u32 (tb[IFLA_GRE_OKEY]) : 0;
-	props->local = nla_get_u32 (tb[IFLA_GRE_LOCAL]);
-	props->remote = nla_get_u32 (tb[IFLA_GRE_REMOTE]);
-	props->tos = nla_get_u8 (tb[IFLA_GRE_TOS]);
-	props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]);
-	props->path_mtu_discovery = !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]);
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
 
-	return 0;
+static gboolean
+link_release (NMPlatform *platform, int master, int slave)
+{
+	return link_enslave (platform, 0, slave);
 }
 
+/******************************************************************/
+
 static gboolean
-gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *props)
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int err;
+	const NMPObject *obj_parent;
+	const NMPObject *obj;
+	gs_free char *path = NULL;
+	gs_free char *id = NULL;
+	gs_free char *ifname = NULL;
 
-	err = _nl_link_parse_info_data (priv->nlh, ifindex,
-	                                gre_info_data_parser, props);
-	if (err != 0) {
-		warning ("(%s) could not read gre properties: %s",
-		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
-	}
-	return (err == 0);
+	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
+	if (!obj_parent || !obj_parent->link.name[0])
+		g_return_val_if_reached (FALSE);
+
+	ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
+
+	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
+	id = g_strdup_printf ("0x%04x", p_key);
+	if (!nm_platform_sysctl_set (platform, path, id))
+		return FALSE;
+
+	do_request_link (platform, 0, ifname);
+
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL);
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
+	return !!obj;
 }
 
+/******************************************************************/
+
 static WifiData *
 wifi_get_wifi_data (NMPlatform *platform, int ifindex)
 {
@@ -3846,16 +4943,6 @@ wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 	return wifi_utils_get_bssid (wifi_data, bssid);
 }
 
-static GByteArray *
-wifi_get_ssid (NMPlatform *platform, int ifindex)
-{
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return NULL;
-	return wifi_utils_get_ssid (wifi_data);
-}
-
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
@@ -3906,6 +4993,15 @@ wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode)
 		wifi_utils_set_mode (wifi_data, mode);
 }
 
+static void
+wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave)
+{
+	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
+
+	if (wifi_data)
+		wifi_utils_set_powersave (wifi_data, powersave);
+}
+
 static guint32
 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 {
@@ -3926,6 +5022,7 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru
 		wifi_utils_indicate_addressing_running (wifi_data, running);
 }
 
+/******************************************************************/
 
 static guint32
 mesh_get_channel (NMPlatform *platform, int ifindex)
@@ -3960,6 +5057,8 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 	return wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
 }
 
+/******************************************************************/
+
 static gboolean
 link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 {
@@ -4019,208 +5118,124 @@ ip6_address_get_all (NMPlatform *platform, int ifindex)
 	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
 }
 
-#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
-#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
-
-static gboolean
-ip4_is_link_local (const struct in_addr *src)
-{
-	return (src->s_addr & IPV4LL_NETMASK) == IPV4LL_NETWORK;
-}
-
-static struct nl_object *
-build_rtnl_addr (NMPlatform *platform,
-                 int family,
-                 int ifindex,
-                 gconstpointer addr,
-                 gconstpointer peer_addr,
-                 int plen,
-                 guint32 lifetime,
-                 guint32 preferred,
-                 guint flags,
-                 const char *label)
-{
-	auto_nl_object struct rtnl_addr *rtnladdr = _nl_rtnl_addr_alloc (ifindex);
-	struct rtnl_addr *rtnladdr_copy;
-	int addrlen = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
-	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (family, addr, addrlen);
-	int nle;
-
-	/* IP address */
-	nle = rtnl_addr_set_local (rtnladdr, nladdr);
-	if (nle) {
-		error ("build_rtnl_addr(): rtnl_addr_set_local failed with %s (%d)", nl_geterror (nle), nle);
-		return NULL;
-	}
-
-	/* Tighten scope (IPv4 only) */
-	if (family == AF_INET && ip4_is_link_local (addr))
-		rtnl_addr_set_scope (rtnladdr, RT_SCOPE_LINK);
-
-	/* IPv4 Broadcast address */
-	if (family == AF_INET) {
-		in_addr_t bcast;
-		auto_nl_addr struct nl_addr *bcaddr = NULL;
-
-		bcast = *((in_addr_t *) addr) | ~nm_utils_ip4_prefix_to_netmask (plen);
-		bcaddr = _nl_addr_build (family, &bcast, addrlen);
-		g_assert (bcaddr);
-		rtnl_addr_set_broadcast (rtnladdr, bcaddr);
-	}
-
-	/* Peer/point-to-point address */
-	if (peer_addr) {
-		auto_nl_addr struct nl_addr *nlpeer = _nl_addr_build (family, peer_addr, addrlen);
-
-		nle = rtnl_addr_set_peer (rtnladdr, nlpeer);
-		if (nle && nle != -NLE_AF_NOSUPPORT) {
-			/* IPv6 doesn't support peer addresses yet */
-			error ("build_rtnl_addr(): rtnl_addr_set_peer failed with %s (%d)", nl_geterror (nle), nle);
-			return NULL;
-		}
-	}
-
-	_nl_rtnl_addr_set_prefixlen (rtnladdr, plen);
-	if (   (lifetime  != 0 && lifetime  != NM_PLATFORM_LIFETIME_PERMANENT)
-	    || (preferred != 0 && preferred != NM_PLATFORM_LIFETIME_PERMANENT)) {
-		/* note that here we set the relative timestamps (ticking from *now*). */
-		rtnl_addr_set_valid_lifetime (rtnladdr, lifetime);
-		rtnl_addr_set_preferred_lifetime (rtnladdr, preferred);
-	}
-	if (flags) {
-		if ((flags & ~0xFF) && !_support_kernel_extended_ifa_flags_get ()) {
-			/* Older kernels don't accept unknown netlink attributes.
-			 *
-			 * With commit libnl commit 5206c050504f8676a24854519b9c351470fb7cc6, libnl will only set
-			 * the extended address flags attribute IFA_FLAGS when necessary (> 8 bit). But it's up to
-			 * us not to shove those extended flags on to older kernels.
-			 *
-			 * Just silently clear them. The kernel should ignore those unknown flags anyway. */
-			flags &= 0xFF;
-		}
-		rtnl_addr_set_flags (rtnladdr, flags);
-	}
-	if (label && *label)
-		rtnl_addr_set_label (rtnladdr, label);
-
-	rtnladdr_copy = rtnladdr;
-	rtnladdr = NULL;
-	return (struct nl_object *) rtnladdr_copy;
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj;
-	guint32 lifetime, preferred;
-
-	nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred,
-	                        0, 0, &lifetime, &preferred);
-
-	return build_rtnl_addr (platform,
-	                        AF_INET,
-	                        obj->ifindex,
-	                        &obj->address,
-	                        obj->peer_address ? &obj->peer_address : NULL,
-	                        obj->plen,
-	                        lifetime,
-	                        preferred,
-	                        0,
-	                        obj->label[0] ? obj->label : NULL);
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj;
-	guint32 lifetime, preferred;
-
-	nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred,
-	                        0, 0, &lifetime, &preferred);
-
-	return build_rtnl_addr (platform,
-	                        AF_INET6,
-	                        obj->ifindex,
-	                        &obj->address,
-	                        !IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL,
-	                        obj->plen,
-	                        lifetime,
-	                        preferred,
-	                        0,
-	                        NULL);
-}
-
 static gboolean
 ip4_address_add (NMPlatform *platform,
                  int ifindex,
                  in_addr_t addr,
-                 in_addr_t peer_addr,
                  int plen,
+                 in_addr_t peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
                  const char *label)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	nlo = build_rtnl_addr (platform, AF_INET, ifindex, &addr,
-	                       peer_addr ? &peer_addr : NULL,
-	                       plen, lifetime, preferred, 0,
-	                       label);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen),
-	                         nlo);
+	nlmsg = _nl_msg_new_address (RTM_NEWADDR,
+	                             NLM_F_CREATE | NLM_F_REPLACE,
+	                             AF_INET,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             &peer_addr,
+	                             0,
+	                             ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
+	                             lifetime,
+	                             preferred,
+	                             label);
+
+	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip6_address_add (NMPlatform *platform,
                  int ifindex,
                  struct in6_addr addr,
-                 struct in6_addr peer_addr,
                  int plen,
+                 struct in6_addr peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
                  guint flags)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+
+	nlmsg = _nl_msg_new_address (RTM_NEWADDR,
+	                             NLM_F_CREATE | NLM_F_REPLACE,
+	                             AF_INET6,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             &peer_addr,
+	                             flags,
+	                             RT_SCOPE_UNIVERSE,
+	                             lifetime,
+	                             preferred,
+	                             NULL);
 
-	nlo = build_rtnl_addr (platform, AF_INET6, ifindex, &addr,
-	                       IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
-	                       plen, lifetime, preferred, flags,
-	                       NULL);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen),
-	                         nlo);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
+
+	nlmsg = _nl_msg_new_address (RTM_DELADDR,
+	                             0,
+	                             AF_INET,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             &peer_address,
+	                             0,
+	                             RT_SCOPE_NOWHERE,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NULL);
+	if (!nlmsg)
+		g_return_val_if_reached (FALSE);
 
-	nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen);
-	obj_needle.ip4_address.peer_address = peer_address;
-	return do_delete_object (platform, &obj_needle, NULL);
+	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_address);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
+
+	nlmsg = _nl_msg_new_address (RTM_DELADDR,
+	                             0,
+	                             AF_INET6,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             NULL,
+	                             0,
+	                             RT_SCOPE_NOWHERE,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NULL);
+	if (!nlmsg)
+		g_return_val_if_reached (FALSE);
 
-	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
-	return do_delete_object (platform, &obj_needle, NULL);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
-	nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_address);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
 	if (nmp_object_is_visible (obj))
 		return &obj->ip4_address;
 	return NULL;
@@ -4229,11 +5244,11 @@ ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 static const NMPlatformIP6Address *
 ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
-	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
 	if (nmp_object_is_visible (obj))
 		return &obj->ip6_address;
 	return NULL;
@@ -4292,113 +5307,29 @@ ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags fl
 	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags);
 }
 
-static void
-clear_host_address (int family, const void *network, int plen, void *dst)
-{
-	g_return_if_fail (plen == (guint8)plen);
-	g_return_if_fail (network);
-
-	switch (family) {
-	case AF_INET:
-		*((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen);
-		break;
-	case AF_INET6:
-		nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-}
-
-static struct nl_object *
-build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
-                  gconstpointer network, int plen, gconstpointer gateway,
-                  gconstpointer pref_src,
-                  guint32 metric, guint32 mss)
-{
-	guint32 network_clean[4];
-	struct rtnl_route *rtnlroute;
-	struct rtnl_nexthop *nexthop;
-	int addrlen = (family == AF_INET) ? sizeof (in_addr_t) : sizeof (struct in6_addr);
-	/* Workaround a libnl bug by using zero destination address length for default routes */
-	auto_nl_addr struct nl_addr *dst = NULL;
-	auto_nl_addr struct nl_addr *gw = gateway ? _nl_addr_build (family, gateway, addrlen) : NULL;
-	auto_nl_addr struct nl_addr *pref_src_nl = pref_src ? _nl_addr_build (family, pref_src, addrlen) : NULL;
-
-	/* There seem to be problems adding a route with non-zero host identifier.
-	 * Adding IPv6 routes is simply ignored, without error message.
-	 * In the IPv4 case, we got an error. Thus, we have to make sure, that
-	 * the address is sane. */
-	clear_host_address (family, network, plen, network_clean);
-	dst = _nl_addr_build (family, network_clean, plen ? addrlen : 0);
-	nl_addr_set_prefixlen (dst, plen);
-
-	rtnlroute = _nl_rtnl_route_alloc ();
-	rtnl_route_set_table (rtnlroute, RT_TABLE_MAIN);
-	rtnl_route_set_tos (rtnlroute, 0);
-	rtnl_route_set_dst (rtnlroute, dst);
-	rtnl_route_set_priority (rtnlroute, metric);
-	rtnl_route_set_family (rtnlroute, family);
-	rtnl_route_set_protocol (rtnlroute, _nm_ip_config_source_to_rtprot (source));
-
-	nexthop = _nl_rtnl_route_nh_alloc ();
-	rtnl_route_nh_set_ifindex (nexthop, ifindex);
-	if (gw && !nl_addr_iszero (gw))
-		rtnl_route_nh_set_gateway (nexthop, gw);
-	if (pref_src_nl)
-		rtnl_route_set_pref_src (rtnlroute, pref_src_nl);
-	rtnl_route_add_nexthop (rtnlroute, nexthop);
-
-	if (mss > 0)
-		rtnl_route_set_metric (rtnlroute, RTAX_ADVMSS, mss);
-
-	return (struct nl_object *) rtnlroute;
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj;
-
-	return build_rtnl_route (AF_INET,
-	                         obj->ifindex,
-	                         obj->source,
-	                         &obj->network,
-	                         obj->plen,
-	                         &obj->gateway,
-	                         obj->pref_src ? &obj->pref_src : NULL,
-	                         obj->metric,
-	                         obj->mss);
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj;
-
-	return build_rtnl_route (AF_INET6,
-	                         obj->ifindex,
-	                         obj->source,
-	                         &obj->network,
-	                         obj->plen,
-	                         &obj->gateway,
-	                         NULL,
-	                         obj->metric,
-	                         obj->mss);
-}
-
 static gboolean
 ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
                in_addr_t network, int plen, in_addr_t gateway,
-               guint32 pref_src, guint32 metric, guint32 mss)
+               in_addr_t pref_src, guint32 metric, guint32 mss)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	nlo = build_rtnl_route (AF_INET, ifindex, source, &network, plen, &gateway, pref_src ? &pref_src : NULL, metric, mss);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric),
-	                         nlo);
+	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
+	                           NLM_F_CREATE | NLM_F_REPLACE,
+	                           AF_INET,
+	                           ifindex,
+	                           source,
+	                           gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK,
+	                           &network,
+	                           plen,
+	                           &gateway,
+	                           metric,
+	                           mss,
+	                           pref_src ? &pref_src : NULL);
+
+	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
@@ -4406,30 +5337,34 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
                struct in6_addr network, int plen, struct in6_addr gateway,
                guint32 metric, guint32 mss)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	metric = nm_utils_ip6_route_metric_normalize (metric);
+	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
+	                           NLM_F_CREATE | NLM_F_REPLACE,
+	                           AF_INET6,
+	                           ifindex,
+	                           source,
+	                           !IN6_IS_ADDR_UNSPECIFIED (&gateway) ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK,
+	                           &network,
+	                           plen,
+	                           &gateway,
+	                           metric,
+	                           mss,
+	                           NULL);
 
-	nlo = build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric),
-	                         nlo);
+	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	in_addr_t gateway = 0;
-	auto_nl_object struct nl_object *nlo = build_rtnl_route (AF_INET, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN, &network, plen, &gateway, NULL, metric, 0);
-	uint8_t scope = RT_SCOPE_NOWHERE;
-	const NMPObject *obj;
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
 
-	g_return_val_if_fail (nlo, FALSE);
-
-	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
+	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
 
 	if (metric == 0) {
 		/* Deleting an IPv4 route with metric 0 does not only delete an exectly matching route.
@@ -4439,94 +5374,81 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 		 * Instead, make sure that we have the most recent state and process all
 		 * delayed actions (including re-reading data from netlink). */
 		delayed_action_handle_all (platform, TRUE);
-	}
-
-	obj = nmp_cache_lookup_obj (priv->cache, &obj_needle);
-
-	if (metric == 0 && !obj) {
-		/* hmm... we are about to delete an IP4 route with metric 0. We must only
-		 * send the delete request if such a route really exists. Above we refreshed
-		 * the platform cache, still no such route exists.
-		 *
-		 * Be extra careful and reload the routes. We must be sure that such a
-		 * route doesn't exists, because when we add an IPv4 address, we immediately
-		 * afterwards try to delete the kernel-added device route with metric 0.
-		 * It might be, that we didn't yet get the notification about that route.
-		 *
-		 * FIXME: once our ip4_address_add() is sure that upon return we have
-		 * the latest state from in the platform cache, we might save this
-		 * additional expensive cache-resync. */
-		do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE, TRUE);
-
-		obj = nmp_cache_lookup_obj (priv->cache, &obj_needle);
-		if (!obj)
-			return TRUE;
-	}
-
-	if (!_nl_has_capability (1 /* NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE */)) {
-		/* When searching for a matching IPv4 route to delete, the kernel
-		 * searches for a matching scope, unless the RTM_DELROUTE message
-		 * specifies RT_SCOPE_NOWHERE (see fib_table_delete()).
-		 *
-		 * However, if we set the scope of @rtnlroute to RT_SCOPE_NOWHERE (or
-		 * leave it unset), rtnl_route_build_msg() will reset the scope to
-		 * rtnl_route_guess_scope() -- which probably guesses wrong.
-		 *
-		 * As a workaround, we look at the cached route and use that scope.
-		 *
-		 * Newer versions of libnl, no longer reset the scope if explicitly set to RT_SCOPE_NOWHERE.
-		 * So, this workaround is only needed unless we have NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE.
-		 **/
 
-		if (obj)
-			scope = nm_platform_route_scope_inv (obj->ip4_route.scope_inv);
+		if (!nmp_cache_lookup_obj (priv->cache, &obj_id)) {
+			/* hmm... we are about to delete an IP4 route with metric 0. We must only
+			 * send the delete request if such a route really exists. Above we refreshed
+			 * the platform cache, still no such route exists.
+			 *
+			 * Be extra careful and reload the routes. We must be sure that such a
+			 * route doesn't exists, because when we add an IPv4 address, we immediately
+			 * afterwards try to delete the kernel-added device route with metric 0.
+			 * It might be, that we didn't yet get the notification about that route.
+			 *
+			 * FIXME: once our ip4_address_add() is sure that upon return we have
+			 * the latest state from in the platform cache, we might save this
+			 * additional expensive cache-resync. */
+			do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE);
 
-		if (scope == RT_SCOPE_NOWHERE) {
-			/* If we would set the scope to RT_SCOPE_NOWHERE, libnl would guess the scope.
-			 * But probably it will guess 'link' because we set the next hop of the route
-			 * to zero (0.0.0.0). A better guess is 'global'. */
-			scope = RT_SCOPE_UNIVERSE;
+			if (!nmp_cache_lookup_obj (priv->cache, &obj_id))
+				return TRUE;
 		}
 	}
-	rtnl_route_set_scope ((struct rtnl_route *) nlo, scope);
-
-	/* we only support routes with TOS zero. As such, delete_route() is also only able to delete
-	 * routes with tos==0. build_rtnl_route() already initializes tos properly. */
 
-	/* The following fields are also relevant when comparing the route, but the default values
-	 * are already as we want them:
-	 *
-	 * type: RTN_UNICAST (setting to zero would ignore the type, but we only want to delete RTN_UNICAST)
-	 * pref_src: NULL
-	 */
+	nlmsg = _nl_msg_new_route (RTM_DELROUTE,
+	                           0,
+	                           AF_INET,
+	                           ifindex,
+	                           NM_IP_CONFIG_SOURCE_UNKNOWN,
+	                           RT_SCOPE_NOWHERE,
+	                           &network,
+	                           plen,
+	                           NULL,
+	                           metric,
+	                           0,
+	                           NULL);
+	if (!nlmsg)
+		return FALSE;
 
-	return do_delete_object (platform, &obj_needle, nlo);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	struct in6_addr gateway = IN6ADDR_ANY_INIT;
-	auto_nl_object struct nl_object *nlo = NULL;
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
 
 	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	nlo = build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0);
+	nlmsg = _nl_msg_new_route (RTM_DELROUTE,
+	                           0,
+	                           AF_INET6,
+	                           ifindex,
+	                           NM_IP_CONFIG_SOURCE_UNKNOWN,
+	                           RT_SCOPE_NOWHERE,
+	                           &network,
+	                           plen,
+	                           NULL,
+	                           metric,
+	                           0,
+	                           NULL);
+	if (!nlmsg)
+		return FALSE;
 
-	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
+	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
 
-	return do_delete_object (platform, &obj_needle, nlo);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static const NMPlatformIP4Route *
 ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
-	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
 	if (nmp_object_is_visible (obj))
 		return &obj->ip4_route;
 	return NULL;
@@ -4535,13 +5457,13 @@ ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, g
 static const NMPlatformIP6Route *
 ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
 	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
 	if (nmp_object_is_visible (obj))
 		return &obj->ip6_route;
 	return NULL;
@@ -4553,22 +5475,6 @@ ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int p
 #define ERROR_CONDITIONS      ((GIOCondition) (G_IO_ERR | G_IO_NVAL))
 #define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP))
 
-static int
-verify_source (struct nl_msg *msg, gpointer user_data)
-{
-	struct ucred *creds = nlmsg_get_creds (msg);
-
-	if (!creds || creds->pid) {
-		if (creds)
-			warning ("netlink: received non-kernel message (pid %d)", creds->pid);
-		else
-			warning ("netlink: received message without credentials");
-		return NL_STOP;
-	}
-
-	return NL_OK;
-}
-
 static gboolean
 event_handler (GIOChannel *channel,
                GIOCondition io_condition,
@@ -4578,148 +5484,288 @@ event_handler (GIOChannel *channel,
 	return TRUE;
 }
 
-static gboolean
-event_handler_read_netlink_one (NMPlatform *platform)
+/*****************************************************************************/
+
+/* copied from libnl3's recvmsgs() */
+static int
+event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int nle;
+	struct nl_sock *sk = priv->nlh;
+	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	unsigned char *buf = NULL;
+	struct nlmsghdr *hdr;
+	WaitForNlResponseResult seq_result;
+
+	/*
+	nla is passed on to not only to nl_recv() but may also be passed
+	to a function pointer provided by the caller which may or may not
+	initialize the variable. Thomas Graf.
+	*/
+	struct sockaddr_nl nla = {0};
+	struct nl_msg *msg = NULL;
+	struct ucred *creds = NULL;
 
+continue_reading:
 	errno = 0;
-	nle = nl_recvmsgs_default (priv->nlh_event);
+	n = nl_recv (sk, &nla, &buf, &creds);
 
 	/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-	if (nle == 0 && errno == EAGAIN) {
+	if (n == 0 && errno == EAGAIN) {
 		/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
 		 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
 		G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
-		nle = -NLE_AGAIN;
+		n = -NLE_AGAIN;
 	}
 
-	if (nle < 0)
-		switch (nle) {
-		case -NLE_AGAIN:
-			return FALSE;
-		case -NLE_DUMP_INTR:
-			debug ("Uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
-			break;
-		case -NLE_NOMEM:
-			info ("Too many netlink events. Need to resynchronize platform cache");
-			/* Drain the event queue, we've lost events and are out of sync anyway and we'd
-			 * like to free up some space. We'll read in the status synchronously. */
-			_nl_sock_flush_data (priv->nlh_event);
-			priv->nlh_seq_expect = 0;
-			delayed_action_schedule (platform,
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
-			                         NULL);
-			break;
-		default:
-			error ("Failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
-			break;
+	if (n <= 0)
+		return n;
+
+	if (!handle_events) {
+		/* we read until failure or there is nothing to read (EAGAIN). */
+		goto continue_reading;
 	}
-	return TRUE;
+
+	hdr = (struct nlmsghdr *) buf;
+	while (nlmsg_ok (hdr, n)) {
+		gboolean abort_parsing = FALSE;
+
+		nlmsg_free (msg);
+		msg = nlmsg_convert (hdr);
+		if (!msg) {
+			err = -NLE_NOMEM;
+			goto out;
+		}
+
+		nlmsg_set_proto (msg, NETLINK_ROUTE);
+		nlmsg_set_src (msg, &nla);
+		nrecv++;
+
+		if (!creds || creds->pid) {
+			if (creds)
+				_LOGD ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid);
+			else
+				_LOGD ("netlink: recvmsg: received message without credentials");
+			goto stop;
+		}
+
+		_LOGt ("netlink: recvmsg: new message type %d, seq %u",
+		       hdr->nlmsg_type, hdr->nlmsg_seq);
+
+		if (creds)
+			nlmsg_set_creds (msg, creds);
+
+		if (hdr->nlmsg_flags & NLM_F_MULTI)
+			multipart = 1;
+
+		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+			/*
+			 * We have to continue reading to clear
+			 * all messages until a NLMSG_DONE is
+			 * received and report the inconsistency.
+			 */
+			interrupted = 1;
+		}
+
+		/* Other side wishes to see an ack for this message */
+		if (hdr->nlmsg_flags & NLM_F_ACK) {
+			/* FIXME: implement */
+		}
+
+		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
+
+		if (hdr->nlmsg_type == NLMSG_DONE) {
+			/* messages terminates a multipart message, this is
+			 * usually the end of a message and therefore we slip
+			 * out of the loop by default. the user may overrule
+			 * this action by skipping this packet. */
+			multipart = 0;
+			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+		} else if (hdr->nlmsg_type == NLMSG_NOOP) {
+			/* Message to be ignored, the default action is to
+			 * skip this message if no callback is specified. The
+			 * user may overrule this action by returning
+			 * NL_PROCEED. */
+		} else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+			/* 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;
+			abort_parsing = TRUE;
+		} else if (hdr->nlmsg_type == NLMSG_ERROR) {
+			/* Message carries a nlmsgerr */
+			struct nlmsgerr *e = nlmsg_data (hdr);
+
+			if (hdr->nlmsg_len < nlmsg_size (sizeof (*e))) {
+				/* Truncated error message, the default action
+				 * is to stop parsing. The user may overrule
+				 * this action by returning NL_SKIP or
+				 * NL_PROCEED (dangerous) */
+				err = -NLE_MSG_TRUNC;
+				abort_parsing = TRUE;
+			} else if (e->error) {
+				int errsv = e->error > 0 ? e->error : -e->error;
+
+				/* Error message reported back from kernel. */
+				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d) for request %d",
+				       strerror (errsv),
+				       errsv,
+				       nlmsg_hdr (msg)->nlmsg_seq);
+				seq_result = -errsv;
+			} else
+				seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+		} else {
+			/* Valid message (not checking for MULTIPART bit to
+			 * get along with broken kernels. NL_SKIP has no
+			 * effect on this.  */
+			event_valid_msg (platform, msg);
+			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+		}
+
+		event_seq_check (platform, msg, seq_result);
+		err = 0;
+		hdr = nlmsg_next (hdr, &n);
+
+		if (abort_parsing)
+			goto out;
+	}
+
+	nlmsg_free (msg);
+	free (buf);
+	free (creds);
+	buf = NULL;
+	msg = NULL;
+	creds = NULL;
+
+	if (multipart) {
+		/* Multipart message not yet complete, continue reading */
+		goto continue_reading;
+	}
+stop:
+	err = 0;
+out:
+	nlmsg_free (msg);
+	free (buf);
+	free (creds);
+
+	if (interrupted)
+		err = -NLE_DUMP_INTR;
+
+	if (!err)
+		err = nrecv;
+
+	return err;
 }
 
+/*****************************************************************************/
+
 static gboolean
-event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks)
+event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int r;
+	int r, nle;
 	struct pollfd pfd;
 	gboolean any = FALSE;
-	gint64 timestamp = 0, now;
-	const int TIMEOUT = 250;
-	int timeout = 0;
-	guint32 wait_for_seq = 0;
+	gint64 now_ns;
+	int timeout_ms;
+	guint i;
+	struct {
+		guint32 seq_number;
+		gint64 timeout_abs_ns;
+	} data_next;
 
 	while (TRUE) {
-		while (event_handler_read_netlink_one (platform))
+
+		while (TRUE) {
+
+			nle = event_handler_recvmsgs (platform, TRUE);
+
+			if (nle < 0)
+				switch (nle) {
+				case -NLE_AGAIN:
+					goto after_read;
+				case -NLE_DUMP_INTR:
+					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+					break;
+				case -NLE_NOMEM:
+					_LOGI ("netlink: read: too many netlink events. Need to resynchronize platform cache");
+					/* Drain the event queue, we've lost events and are out of sync anyway and we'd
+					 * like to free up some space. We'll read in the status synchronously. */
+					delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
+					event_handler_recvmsgs (platform, FALSE);
+					delayed_action_schedule (platform,
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+					                         NULL);
+					break;
+				default:
+					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+					break;
+			}
 			any = TRUE;
+		}
 
-		if (!wait_for_acks || priv->nlh_seq_expect == 0) {
-			if (wait_for_seq)
-				_LOGt ("read-netlink-all: ACK for sequence number %u received", priv->nlh_seq_expect);
+after_read:
+
+		if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
 			return any;
-		}
 
-		now = nm_utils_get_monotonic_timestamp_ms ();
-		if (wait_for_seq != priv->nlh_seq_expect) {
-			/* We are waiting for a new sequence number (or we will wait for the first time).
-			 * Reset/start counting the overall wait time. */
-			_LOGt ("read-netlink-all: wait for ACK for sequence number %u...", priv->nlh_seq_expect);
-			wait_for_seq = priv->nlh_seq_expect;
-			timestamp = now;
-			timeout = TIMEOUT;
-		} else {
-			if ((now - timestamp) >= TIMEOUT) {
-				/* timeout. Don't wait for this sequence number anymore. */
-				break;
-			}
+		now_ns = 0;
+		data_next.seq_number = 0;
+		data_next.timeout_abs_ns = 0;
+
+		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
+			DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+			if (data->seq_result)
+				delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
+			else if ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) > data->timeout_abs_ns)
+				delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
+			else {
+				i++;
 
-			/* readjust the wait-time. */
-			timeout = TIMEOUT - (now - timestamp);
+				if (   data_next.seq_number == 0
+				    || data_next.timeout_abs_ns > data->timeout_abs_ns)
+					data_next.seq_number = data->seq_number;
+					data_next.timeout_abs_ns = data->timeout_abs_ns;
+			}
 		}
 
+		if (   !wait_for_acks
+		    || !NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
+			return any;
+
+		nm_assert (data_next.seq_number);
+		nm_assert (data_next.timeout_abs_ns > 0);
+		nm_assert (now_ns > 0);
+
+		_LOGT ("netlink: read: wait for ACK for sequence number %u...", data_next.seq_number);
+
+		timeout_ms = (data_next.timeout_abs_ns - now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
+
 		memset (&pfd, 0, sizeof (pfd));
-		pfd.fd = nl_socket_get_fd (priv->nlh_event);
+		pfd.fd = nl_socket_get_fd (priv->nlh);
 		pfd.events = POLLIN;
-		r = poll (&pfd, 1, timeout);
+		r = poll (&pfd, 1, MAX (1, timeout_ms));
 
 		if (r == 0) {
-			/* timeout. */
-			break;
+			/* timeout and there is nothing to read. */
+			goto after_read;
 		}
 		if (r < 0) {
 			int errsv = errno;
 
 			if (errsv != EINTR) {
-				_LOGE ("read-netlink-all: poll failed with %s", strerror (errsv));
+				_LOGE ("netlink: read: poll failed with %s", strerror (errsv));
+				delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL);
 				return any;
 			}
 			/* Continue to read again, even if there might be nothing to read after EINTR. */
 		}
 	}
-
-	_LOGW ("read-netlink-all: timeout waiting for ACK to sequence number %u...", wait_for_seq);
-	priv->nlh_seq_expect = 0;
-	return any;
-}
-
-static struct nl_sock *
-setup_socket (gboolean event, gpointer user_data)
-{
-	struct nl_sock *sock;
-	int nle;
-
-	sock = nl_socket_alloc ();
-	g_return_val_if_fail (sock, NULL);
-
-	/* Only ever accept messages from kernel */
-	nle = nl_socket_modify_cb (sock, NL_CB_MSG_IN, NL_CB_CUSTOM, verify_source, user_data);
-	g_assert (!nle);
-
-	/* Dispatch event messages (event socket only) */
-	if (event) {
-		nl_socket_modify_cb (sock, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data);
-		nl_socket_modify_cb (sock, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, event_seq_check, user_data);
-		nl_socket_modify_err_cb (sock, NL_CB_CUSTOM, event_err, user_data);
-	}
-
-	nle = nl_connect (sock, NETLINK_ROUTE);
-	g_assert (!nle);
-	nle = nl_socket_set_passcred (sock, 1);
-	g_assert (!nle);
-
-	/* No blocking for event socket, so that we can drain it safely. */
-	if (event) {
-		nle = nl_socket_set_nonblocking (sock);
-		g_assert (!nle);
-	}
-
-	return sock;
 }
 
 /******************************************************************/
@@ -4728,12 +5774,12 @@ static void
 cache_update_link_udev (NMPlatform *platform, int ifindex, GUdevDevice *udev_device)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nmp_obj NMPObject *obj_cache = NULL;
+	nm_auto_nmpobj NMPObject *obj_cache = NULL;
 	gboolean was_visible;
 	NMPCacheOpsType cache_op;
 
 	cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udev_device, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible);
 }
 
 static void
@@ -4745,23 +5791,23 @@ udev_device_added (NMPlatform *platform,
 
 	ifname = g_udev_device_get_name (udev_device);
 	if (!ifname) {
-		debug ("udev-add: failed to get device's interface");
+		_LOGD ("udev-add: failed to get device's interface");
 		return;
 	}
 
 	if (g_udev_device_get_property (udev_device, "IFINDEX"))
 		ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
 	else {
-		warning ("(%s): udev-add: failed to get device's ifindex", ifname);
+		_LOGW ("(%s): udev-add: failed to get device's ifindex", ifname);
 		return;
 	}
 	if (ifindex <= 0) {
-		warning ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex);
+		_LOGW ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex);
 		return;
 	}
 
 	if (!g_udev_device_get_sysfs_path (udev_device)) {
-		debug ("(%s): udev-add: couldn't determine device path; ignoring...", ifname);
+		_LOGD ("(%s): udev-add: couldn't determine device path; ignoring...", ifname);
 		return;
 	}
 
@@ -4791,7 +5837,7 @@ udev_device_removed (NMPlatform *platform,
 			ifindex = obj->link.ifindex;
 	}
 
-	debug ("udev-remove: IFINDEX=%d", ifindex);
+	_LOGD ("udev-remove: IFINDEX=%d", ifindex);
 	if (ifindex <= 0)
 		return;
 
@@ -4817,9 +5863,9 @@ handle_udev_event (GUdevClient *client,
 
 	ifindex = g_udev_device_get_property (udev_device, "IFINDEX");
 	seqnum = g_udev_device_get_seqnum (udev_device);
-	debug ("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT,
-	       action, subsys, g_udev_device_get_name (udev_device),
-	       ifindex ? ifindex : "unknown", seqnum);
+	_LOGD ("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT,
+	        action, subsys, g_udev_device_get_name (udev_device),
+	        ifindex ? ifindex : "unknown", seqnum);
 
 	if (!strcmp (action, "add") || !strcmp (action, "move"))
 		udev_device_added (platform, udev_device);
@@ -4836,9 +5882,11 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 
 	self->priv = priv;
 
+	priv->nlh_seq_next = 1;
 	priv->cache = nmp_cache_new ();
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
+	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
 	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
 }
 
@@ -4856,29 +5904,37 @@ constructed (GObject *_object)
 
 	_LOGD ("create");
 
-	/* Initialize netlink socket for requests */
-	priv->nlh = setup_socket (FALSE, platform);
+	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
-	debug ("Netlink socket for requests established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
-	/* Initialize netlink socket for events */
-	priv->nlh_event = setup_socket (TRUE, platform);
-	g_assert (priv->nlh_event);
+	nle = nl_connect (priv->nlh, NETLINK_ROUTE);
+	g_assert (!nle);
+	nle = nl_socket_set_passcred (priv->nlh, 1);
+	g_assert (!nle);
+
+	/* No blocking for event socket, so that we can drain it safely. */
+	nle = nl_socket_set_nonblocking (priv->nlh);
+	g_assert (!nle);
+
 	/* The default buffer size wasn't enough for the testsuites. It might just
 	 * as well happen with NetworkManager itself. For now let's hope 128KB is
 	 * good enough.
+	 *
+	 * FIXME: it's unclear that this is still actually needed. The testsuite
+	 * certainly doesn't fail for me. Maybe it can be removed.
 	 */
-	nle = nl_socket_set_buffer_size (priv->nlh_event, 131072, 0);
+	nle = nl_socket_set_buffer_size (priv->nlh, 131072, 0);
 	g_assert (!nle);
-	nle = nl_socket_add_memberships (priv->nlh_event,
+
+	nle = nl_socket_add_memberships (priv->nlh,
 	                                 RTNLGRP_LINK,
 	                                 RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR,
 	                                 RTNLGRP_IPV4_ROUTE,  RTNLGRP_IPV6_ROUTE,
 	                                 0);
 	g_assert (!nle);
-	debug ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh_event), nl_socket_get_fd (priv->nlh_event));
+	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
-	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh_event));
+	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh));
 	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
 	g_io_channel_set_close_on_unref (priv->event_channel, TRUE);
 
@@ -4931,12 +5987,12 @@ dispose (GObject *object)
 
 	_LOGD ("dispose");
 
+	delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
+
 	priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
 	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
 	g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
 
-	nm_clear_g_source (&priv->delayed_action.idle_id);
-
 	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
 
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
@@ -4951,16 +6007,21 @@ nm_linux_platform_finalize (GObject *object)
 
 	g_ptr_array_unref (priv->delayed_action.list_master_connected);
 	g_ptr_array_unref (priv->delayed_action.list_refresh_link);
+	g_array_unref (priv->delayed_action.list_wait_for_nl_response);
 
 	/* Free netlink resources */
 	g_source_remove (priv->event_id);
 	g_io_channel_unref (priv->event_channel);
 	nl_socket_free (priv->nlh);
-	nl_socket_free (priv->nlh_event);
 
 	g_object_unref (priv->udev_client);
 	g_hash_table_unref (priv->wifi_data);
 
+	if (priv->sysctl_get_prev_values) {
+		sysctl_clear_cache_list = g_slist_remove (sysctl_clear_cache_list, object);
+		g_hash_table_destroy (priv->sysctl_get_prev_values);
+	}
+
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->finalize (object);
 }
 
@@ -4991,6 +6052,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_get_type_name = link_get_type_name;
 	platform_class->link_get_unmanaged = link_get_unmanaged;
 
+	platform_class->link_get_lnk = link_get_lnk;
+
 	platform_class->link_refresh = link_refresh;
 
 	platform_class->link_set_up = link_set_up;
@@ -5017,33 +6080,23 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
-	platform_class->master_set_option = master_set_option;
-	platform_class->master_get_option = master_get_option;
-	platform_class->slave_set_option = slave_set_option;
-	platform_class->slave_get_option = slave_get_option;
 
 	platform_class->vlan_add = vlan_add;
-	platform_class->vlan_get_info = vlan_get_info;
-	platform_class->vlan_set_ingress_map = vlan_set_ingress_map;
-	platform_class->vlan_set_egress_map = vlan_set_egress_map;
+	platform_class->link_vlan_change = link_vlan_change;
+	platform_class->link_vxlan_add = link_vxlan_add;
 
-	platform_class->infiniband_partition_add = infiniband_partition_add;
-	platform_class->infiniband_get_info = infiniband_get_info;
+	platform_class->tun_add = tun_add;
 
-	platform_class->veth_get_properties = veth_get_properties;
-	platform_class->tun_get_properties = tun_get_properties;
-	platform_class->macvlan_get_properties = macvlan_get_properties;
-	platform_class->vxlan_get_properties = vxlan_get_properties;
-	platform_class->gre_get_properties = gre_get_properties;
+	platform_class->infiniband_partition_add = infiniband_partition_add;
 
 	platform_class->wifi_get_capabilities = wifi_get_capabilities;
 	platform_class->wifi_get_bssid = wifi_get_bssid;
-	platform_class->wifi_get_ssid = wifi_get_ssid;
 	platform_class->wifi_get_frequency = wifi_get_frequency;
 	platform_class->wifi_get_quality = wifi_get_quality;
 	platform_class->wifi_get_rate = wifi_get_rate;
 	platform_class->wifi_get_mode = wifi_get_mode;
 	platform_class->wifi_set_mode = wifi_set_mode;
+	platform_class->wifi_set_powersave = wifi_set_powersave;
 	platform_class->wifi_find_frequency = wifi_find_frequency;
 	platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
 
@@ -5051,6 +6104,12 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
+	platform_class->link_gre_add = link_gre_add;
+	platform_class->link_ip6tnl_add = link_ip6tnl_add;
+	platform_class->link_macvlan_add = link_macvlan_add;
+	platform_class->link_ipip_add = link_ipip_add;
+	platform_class->link_sit_add = link_sit_add;
+
 	platform_class->ip4_address_get = ip4_address_get;
 	platform_class->ip6_address_get = ip6_address_get;
 	platform_class->ip4_address_get_all = ip4_address_get_all;
diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h
index a9e2cd82..81f9c738 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/platform/nm-linux-platform.h
@@ -50,4 +50,6 @@ GType nm_linux_platform_get_type (void);
 
 void nm_linux_platform_setup (void);
 
+void _nm_linux_platform_sysctl_clear_cache (void);
+
 #endif /* __NETWORKMANAGER_LINUX_PLATFORM_H__ */
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index e9b8d4be..953ac8c5 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -29,10 +29,9 @@
 #include <linux/mii.h>
 #include <linux/version.h>
 
-#include "gsystem-local-alloc.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "nm-setting-wired.h"
 
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 9948ac8b..3bb44e1a 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -28,20 +28,27 @@
 #include <string.h>
 #include <netlink/route/addr.h>
 #include <netlink/route/rtnl.h>
+#include <linux/ip.h>
+#include <linux/if_tun.h>
+#include <linux/if_tunnel.h>
 
-#include "gsystem-local-alloc.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-platform.h"
 #include "nm-platform-utils.h"
+#include "nmp-object.h"
 #include "NetworkManagerUtils.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "nm-enum-types.h"
 #include "nm-core-internal.h"
 
 #define ADDRESS_LIFETIME_PADDING 5
 
 G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWADDR_LEN_MAX);
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
 
 #define _NMLOG_DOMAIN           LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME      "platform"
@@ -64,21 +71,15 @@ G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWA
         } \
     } G_STMT_END
 
+#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
+
+/*****************************************************************************/
+
 #define NM_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_PLATFORM, NMPlatformPrivate))
 
 G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT)
 
-/* NMPlatform signals */
-enum {
-	SIGNAL_LINK_CHANGED,
-	SIGNAL_IP4_ADDRESS_CHANGED,
-	SIGNAL_IP6_ADDRESS_CHANGED,
-	SIGNAL_IP4_ROUTE_CHANGED,
-	SIGNAL_IP6_ROUTE_CHANGED,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
+static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
 
 enum {
 	PROP_0,
@@ -92,10 +93,22 @@ typedef struct {
 
 /******************************************************************/
 
+guint
+_nm_platform_signal_id_get (NMPlatformSignalIdType signal_type)
+{
+	nm_assert (   signal_type > 0
+	           && signal_type != NM_PLATFORM_SIGNAL_ID_NONE
+	           && signal_type < _NM_PLATFORM_SIGNAL_ID_LAST);
+
+	return signals[signal_type];
+}
+
+/******************************************************************/
+
 /* Singleton NMPlatform subclass instance and cached class object */
 NM_DEFINE_SINGLETON_INSTANCE (NMPlatform);
 
-NM_DEFINE_SINGLETON_WEAK_REF (NMPlatform);
+NM_DEFINE_SINGLETON_REGISTER (NMPlatform);
 
 /* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS()
  * is only a plain read on the self instance, which the compiler
@@ -137,9 +150,9 @@ nm_platform_setup (NMPlatform *instance)
 
 	singleton_instance = instance;
 
-	nm_singleton_instance_weak_ref_register ();
+	nm_singleton_instance_register ();
 
-	nm_log_dbg (LOGD_CORE, "setup NMPlatform singleton (%p, %s)", instance,  G_OBJECT_TYPE_NAME (instance));
+	nm_log_dbg (LOGD_CORE, "setup %s singleton (%p, %s)", "NMPlatform", singleton_instance, G_OBJECT_TYPE_NAME (instance));
 }
 
 /**
@@ -204,22 +217,6 @@ nm_platform_error_to_string (NMPlatformError error)
 
 /******************************************************************/
 
-#define IFA_F_MANAGETEMPADDR_STR "mngtmpaddr"
-#define IFA_F_NOPREFIXROUTE_STR "noprefixroute"
-gboolean
-nm_platform_check_support_libnl_extended_ifa_flags ()
-{
-	static int supported = -1;
-
-	/* support for extended ifa-flags was added together
-	 * with the IFA_F_MANAGETEMPADDR flag. So, check if libnl
-	 * is able to parse this flag. */
-	if (supported == -1)
-		supported = rtnl_addr_str2flags (IFA_F_MANAGETEMPADDR_STR) == IFA_F_MANAGETEMPADDR;
-
-	return supported;
-}
-
 gboolean
 nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self)
 {
@@ -284,7 +281,6 @@ nm_platform_sysctl_set (NMPlatform *self, const char *path, const char *value)
 
 	g_return_val_if_fail (path, FALSE);
 	g_return_val_if_fail (value, FALSE);
-	g_return_val_if_fail (klass->sysctl_set, FALSE);
 
 	return klass->sysctl_set (self, path, value);
 }
@@ -336,7 +332,6 @@ nm_platform_sysctl_get (NMPlatform *self, const char *path)
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (path, NULL);
-	g_return_val_if_fail (klass->sysctl_get, NULL);
 
 	return klass->sysctl_get (self, path);
 }
@@ -416,8 +411,6 @@ nm_platform_link_get_all (NMPlatform *self)
 
 	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (klass->link_get_all, NULL);
-
 	links = klass->link_get_all (self);
 
 	if (!links || links->len == 0)
@@ -427,7 +420,7 @@ nm_platform_link_get_all (NMPlatform *self)
 	for (i = 0; i < links->len; i++) {
 		item = &g_array_index (links, NMPlatformLink, i);
 
-		_LOGt ("link-get: %3d: %s", i, nm_platform_link_to_string (item));
+		_LOGt ("link-get: %3d: %s", i, nm_platform_link_to_string (item, NULL, 0));
 
 		nm_assert (item->ifindex > 0 && !g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));
 
@@ -450,8 +443,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			if (item->parent != NM_PLATFORM_LINK_OTHER_NETNS) {
 				g_warn_if_fail (item->parent > 0);
 				g_warn_if_fail (item->parent != item->ifindex);
-				g_warn_if_fail (   !nm_platform_check_support_libnl_link_netnsid ()
-				                || g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)));
+				g_warn_if_fail (g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)));
 			}
 		}
 	}
@@ -483,7 +475,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			if (item->parent > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)))
 				continue;
 
-			_LOGt ("link-get: add %3d -> %3d: %s", i, j, nm_platform_link_to_string (item));
+			_LOGt ("link-get: add %3d -> %3d: %s", i, j, nm_platform_link_to_string (item, NULL, 0));
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
@@ -496,7 +488,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			 * This can happen for veth pairs where each peer is parent of the other end. */
 			item = &g_array_index (links, NMPlatformLink, first_idx);
 
-			_LOGt ("link-get: add (loop) %3d -> %3d: %s", first_idx, j, nm_platform_link_to_string (item));
+			_LOGt ("link-get: add (loop) %3d -> %3d: %s", first_idx, j, nm_platform_link_to_string (item, NULL, 0));
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
@@ -575,7 +567,7 @@ nm_platform_link_get_by_address (NMPlatform *self,
 }
 
 static NMPlatformError
-_link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, NMPlatformLink *out_link)
+_link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, const NMPlatformLink **out_link)
 {
 	const NMPlatformLink *pllink;
 
@@ -590,11 +582,13 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, N
 		       wrong_type ? ", expected " : "",
 		       wrong_type ? nm_link_type_to_string (type) : "");
 		if (out_link)
-			*out_link = *pllink;
+			*out_link = pllink;
 		if (wrong_type)
 			return NM_PLATFORM_ERROR_WRONG_TYPE;
 		return NM_PLATFORM_ERROR_EXISTS;
 	}
+	if (out_link)
+		*out_link = NULL;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
@@ -623,14 +617,13 @@ nm_platform_link_add (NMPlatform *self,
                       NMLinkType type,
                       const void *address,
                       size_t address_len,
-                      NMPlatformLink *out_link)
+                      const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (klass->link_add, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail ( (address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, type, out_link);
@@ -644,7 +637,7 @@ nm_platform_link_add (NMPlatform *self,
 }
 
 /**
- * nm_platform_dummy_add:
+ * nm_platform_link_dummy_add:
  * @self: platform instance
  * @name: New interface name
  * @out_link: on success, the link object
@@ -652,7 +645,9 @@ nm_platform_link_add (NMPlatform *self,
  * Create a software ethernet-like interface
  */
 NMPlatformError
-nm_platform_dummy_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
+nm_platform_link_dummy_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, 0, out_link);
 }
@@ -746,8 +741,6 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 {
 	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (klass->link_get_type_name, NULL);
-
 	return klass->link_get_type_name (self, ifindex);
 }
 
@@ -890,22 +883,19 @@ nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_get_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId *iid)
 {
+	const NMPlatformLink *pllink;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (iid, FALSE);
 
-#if HAVE_LIBNL_INET6_TOKEN
-	{
-		const NMPlatformLink *pllink;
 
-		pllink = nm_platform_link_get (self, ifindex);
-		if (pllink && pllink->inet6_token.is_valid) {
-			*iid = pllink->inet6_token.iid;
-			return TRUE;
-		}
+	pllink = nm_platform_link_get (self, ifindex);
+	if (pllink && pllink->inet6_token.is_valid) {
+		*iid = pllink->inet6_token.iid;
+		return TRUE;
 	}
-#endif
 	return FALSE;
 }
 
@@ -947,23 +937,18 @@ nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
 gboolean
 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);
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	{
-		const NMPlatformLink *pllink;
-
-		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) == IN6_ADDR_GEN_MODE_NONE;
-	}
-#endif
+	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;
 	return FALSE;
 }
 
-
 /**
  * nm_platform_link_set_user_ip6vll_enabled:
  * @self: platform instance
@@ -981,7 +966,6 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->check_support_user_ipv6ll, FALSE);
 
 	if (klass->link_set_user_ipv6ll_enabled)
 		return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
@@ -1004,7 +988,6 @@ nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer addre
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (address, FALSE);
 	g_return_val_if_fail (length > 0, FALSE);
-	g_return_val_if_fail (klass->link_set_address, FALSE);
 
 	_LOGD ("link: setting '%s' (%d) hardware address", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_address (self, ifindex, address, length);
@@ -1083,7 +1066,6 @@ nm_platform_link_supports_carrier_detect (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_supports_carrier_detect, FALSE);
 
 	return klass->link_supports_carrier_detect (self, ifindex);
 }
@@ -1094,7 +1076,6 @@ nm_platform_link_supports_vlans (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_supports_vlans, FALSE);
 
 	return klass->link_supports_vlans (self, ifindex);
 }
@@ -1113,7 +1094,6 @@ nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmwar
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (klass->link_set_up, FALSE);
 
 	_LOGD ("link: setting up '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_up (self, ifindex, out_no_firmware);
@@ -1132,7 +1112,6 @@ nm_platform_link_set_down (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (klass->link_set_down, FALSE);
 
 	_LOGD ("link: setting down '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_down (self, ifindex);
@@ -1151,7 +1130,6 @@ nm_platform_link_set_arp (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_set_arp, FALSE);
 
 	_LOGD ("link: setting arp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_arp (self, ifindex);
@@ -1170,7 +1148,6 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_set_noarp, FALSE);
 
 	_LOGD ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_noarp (self, ifindex);
@@ -1191,7 +1168,6 @@ 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);
-	g_return_val_if_fail (klass->link_set_mtu, FALSE);
 
 	_LOGD ("link: setting '%s' (%d) mtu %"G_GUINT32_FORMAT, nm_platform_link_get_name (self, ifindex), ifindex, mtu);
 	return klass->link_set_mtu (self, ifindex, mtu);
@@ -1306,7 +1282,6 @@ nm_platform_link_get_driver_info (NMPlatform *self,
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_get_driver_info, FALSE);
 
 	return klass->link_get_driver_info (self,
 	                                    ifindex,
@@ -1330,7 +1305,6 @@ nm_platform_link_enslave (NMPlatform *self, int master, int slave)
 
 	g_return_val_if_fail (master > 0, FALSE);
 	g_return_val_if_fail (slave> 0, FALSE);
-	g_return_val_if_fail (klass->link_enslave, FALSE);
 
 	_LOGD ("link: enslaving '%s' (%d) to master '%s' (%d)",
 	       nm_platform_link_get_name (self, slave), slave,
@@ -1353,7 +1327,6 @@ nm_platform_link_release (NMPlatform *self, int master, int slave)
 
 	g_return_val_if_fail (master > 0, FALSE);
 	g_return_val_if_fail (slave > 0, FALSE);
-	g_return_val_if_fail (klass->link_release, FALSE);
 
 	if (nm_platform_link_get_master (self, slave) != master)
 		return FALSE;
@@ -1384,8 +1357,109 @@ nm_platform_link_get_master (NMPlatform *self, int slave)
 	return pllink ? pllink->master : 0;
 }
 
+/*****************************************************************************/
+
 /**
- * nm_platform_bridge_add:
+ * nm_platform_link_get_lnk:
+ * @self: the platform instance
+ * @ifindex: the link ifindex to lookup
+ * @link_type: filter by link-type.
+ * @out_link: (allow-none): returns the platform link instance
+ *
+ * If the function returns %NULL, that could mean that no such ifindex
+ * exists, of that the link has no lnk data. You can find that out
+ * by checking @out_link. @out_link will always be set if a link
+ * with @ifindex exists.
+ *
+ * If @link_type is %NM_LINK_TYPE_NONE, the function returns the lnk
+ * object if it is present. If you set link-type, you can be sure
+ * that only a link type of the matching type is returned (or %NULL).
+ *
+ * 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
+ * (but don't modify it).
+ */
+const NMPObject *
+nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	NM_SET_OUT (out_link, NULL);
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	return klass->link_get_lnk (self, ifindex, link_type, out_link);
+}
+
+static gconstpointer
+_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
+{
+	const NMPObject *lnk;
+
+	lnk = nm_platform_link_get_lnk (self, ifindex, link_type, out_link);
+	return lnk ? &lnk->object : NULL;
+}
+
+const NMPlatformLnkGre *
+nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_GRE, out_link);
+}
+
+const NMPlatformLnkInfiniband *
+nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_INFINIBAND, out_link);
+}
+
+const NMPlatformLnkIp6Tnl *
+nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6TNL, out_link);
+}
+
+const NMPlatformLnkIpIp *
+nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IPIP, out_link);
+}
+
+const NMPlatformLnkMacvlan *
+nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVLAN, out_link);
+}
+
+const NMPlatformLnkMacvtap *
+nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVTAP, out_link);
+}
+
+const NMPlatformLnkSit *
+nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_SIT, out_link);
+}
+
+const NMPlatformLnkVlan *
+nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VLAN, out_link);
+}
+
+const NMPlatformLnkVxlan *
+nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VXLAN, out_link);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_platform_link_bridge_add:
  * @self: platform instance
  * @name: New interface name
  * @address: (allow-none): set the mac address of the new bridge
@@ -1395,17 +1469,17 @@ nm_platform_link_get_master (NMPlatform *self, int slave)
  * Create a software bridge.
  */
 NMPlatformError
-nm_platform_bridge_add (NMPlatform *self,
-                        const char *name,
-                        const void *address,
-                        size_t address_len,
-                        NMPlatformLink *out_link)
+nm_platform_link_bridge_add (NMPlatform *self,
+                             const char *name,
+                             const void *address,
+                             size_t address_len,
+                             const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, address, address_len, out_link);
 }
 
 /**
- * nm_platform_bond_add:
+ * nm_platform_link_bond_add:
  * @self: platform instance
  * @name: New interface name
  * @out_link: on success, the link object
@@ -1413,13 +1487,15 @@ nm_platform_bridge_add (NMPlatform *self,
  * Create a software bonding device.
  */
 NMPlatformError
-nm_platform_bond_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
+nm_platform_link_bond_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, 0, out_link);
 }
 
 /**
- * nm_platform_team_add:
+ * nm_platform_link_team_add:
  * @self: platform instance
  * @name: New interface name
  * @out_link: on success, the link object
@@ -1427,13 +1503,15 @@ nm_platform_bond_add (NMPlatform *self, const char *name, NMPlatformLink *out_li
  * Create a software teaming device.
  */
 NMPlatformError
-nm_platform_team_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
+nm_platform_link_team_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, 0, out_link);
 }
 
 /**
- * nm_platform_vlan_add:
+ * nm_platform_link_vlan_add:
  * @self: platform instance
  * @name: New interface name
  * @vlanid: VLAN identifier
@@ -1443,12 +1521,12 @@ nm_platform_team_add (NMPlatform *self, const char *name, NMPlatformLink *out_li
  * Create a software VLAN device.
  */
 NMPlatformError
-nm_platform_vlan_add (NMPlatform *self,
-                      const char *name,
-                      int parent,
-                      int vlanid,
-                      guint32 vlanflags,
-                      NMPlatformLink *out_link)
+nm_platform_link_vlan_add (NMPlatform *self,
+                           const char *name,
+                           int parent,
+                           int vlanid,
+                           guint32 vlanflags,
+                           const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
 
@@ -1457,7 +1535,6 @@ nm_platform_vlan_add (NMPlatform *self,
 	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 (klass->vlan_add, NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
@@ -1470,98 +1547,321 @@ nm_platform_vlan_add (NMPlatform *self,
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
+/**
+ * nm_platform_link_vxlan_add:
+ * @self: platform instance
+ * @name: New interface name
+ * @props: properties of the new link
+ * @out_link: on success, the link object
+ *
+ * Create a VXLAN device.
+ */
+NMPlatformError
+nm_platform_link_vxlan_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLnkVxlan *props,
+                            const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("link: adding vxlan '%s' parent %d id %d",
+	       name, props->parent_ifindex, props->id);
+	if (!klass->link_vxlan_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
+ * nm_platform_link_tun_add:
+ * @self: platform instance
+ * @name: new interface name
+ * @tap: whether the interface is a TAP
+ * @owner: interface owner or -1
+ * @group: interface group or -1
+ * @pi: whether to clear the IFF_NO_PI flag
+ * @vnet_hdr: whether to set the IFF_VNET_HDR flag
+ * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
+ * @out_link: on success, the link object
+ *
+ * Create a TUN or TAP interface.
+ */
+NMPlatformError
+nm_platform_link_tun_add (NMPlatform *self,
+                          const char *name,
+                          gboolean tap,
+                          gint64 owner,
+                          gint64 group,
+                          gboolean pi,
+                          gboolean vnet_hdr,
+                          gboolean multi_queue,
+                          const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("link: adding %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
+	       tap ? "tap" : "tun", name, owner, group);
+	if (!klass->tun_add (self, name, tap, owner, group, pi, vnet_hdr, multi_queue, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/*****************************************************************************/
+
+static char *
+link_option_path (NMPlatform *self, int master, const char *category, const char *option)
+{
+	const char *name = nm_platform_link_get_name (self, master);
+
+	if (!name || !category || !option)
+		return NULL;
+
+	return g_strdup_printf ("/sys/class/net/%s/%s/%s",
+	                        ASSERT_VALID_PATH_COMPONENT (name),
+	                        ASSERT_VALID_PATH_COMPONENT (category),
+	                        ASSERT_VALID_PATH_COMPONENT (option));
+}
+
+static gboolean
+link_set_option (NMPlatform *self, int master, const char *category, const char *option, const char *value)
+{
+	gs_free char *path = link_option_path (self, master, category, option);
+
+	return path && nm_platform_sysctl_set (self, path, value);
+}
+
+static char *
+link_get_option (NMPlatform *self, int master, const char *category, const char *option)
+{
+	gs_free char *path = link_option_path (self, master, category, option);
+
+	return path ? nm_platform_sysctl_get (self, path) : NULL;
+}
+
+static const char *
+master_category (NMPlatform *self, int master)
+{
+	switch (nm_platform_link_get_type (self, master)) {
+	case NM_LINK_TYPE_BRIDGE:
+		return "bridge";
+	case NM_LINK_TYPE_BOND:
+		return "bonding";
+	default:
+		return NULL;
+	}
+}
+
+static const char *
+slave_category (NMPlatform *self, int slave)
+{
+	int master = nm_platform_link_get_master (self, slave);
+
+	if (master <= 0)
+		return NULL;
+
+	switch (nm_platform_link_get_type (self, master)) {
+	case NM_LINK_TYPE_BRIDGE:
+		return "brport";
+	default:
+		return NULL;
+	}
+}
+
 gboolean
-nm_platform_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
+nm_platform_sysctl_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (option, FALSE);
 	g_return_val_if_fail (value, FALSE);
-	g_return_val_if_fail (klass->master_set_option, FALSE);
 
-	return klass->master_set_option (self, ifindex, option, value);
+	return link_set_option (self, ifindex, master_category (self, ifindex), option, value);
 }
 
 char *
-nm_platform_master_get_option (NMPlatform *self, int ifindex, const char *option)
+nm_platform_sysctl_master_get_option (NMPlatform *self, int ifindex, const char *option)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (option, FALSE);
-	g_return_val_if_fail (klass->master_set_option, FALSE);
 
-	return klass->master_get_option (self, ifindex, option);
+	return link_get_option (self, ifindex, master_category (self, ifindex), option);
 }
 
 gboolean
-nm_platform_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
+nm_platform_sysctl_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (option, FALSE);
 	g_return_val_if_fail (value, FALSE);
-	g_return_val_if_fail (klass->slave_set_option, FALSE);
 
-	return klass->slave_set_option (self, ifindex, option, value);
+	return link_set_option (self, ifindex, slave_category (self, ifindex), option, value);
 }
 
 char *
-nm_platform_slave_get_option (NMPlatform *self, int ifindex, const char *option)
+nm_platform_sysctl_slave_get_option (NMPlatform *self, int ifindex, const char *option)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (option, FALSE);
-	g_return_val_if_fail (klass->slave_set_option, FALSE);
 
-	return klass->slave_get_option (self, ifindex, option);
+	return link_get_option (self, ifindex, slave_category (self, ifindex), option);
 }
 
+/******************************************************************************/
+
 gboolean
-nm_platform_vlan_get_info (NMPlatform *self, int ifindex, int *parent, int *vlanid)
+nm_platform_link_vlan_change (NMPlatform *self,
+                              int ifindex,
+                              NMVlanFlags flags_mask,
+                              NMVlanFlags flags_set,
+                              gboolean ingress_reset_all,
+                              const NMVlanQosMapping *ingress_map,
+                              gsize n_ingress_map,
+                              gboolean egress_reset_all,
+                              const NMVlanQosMapping *egress_map,
+                              gsize n_egress_map)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->vlan_get_info, FALSE);
+	nm_assert (klass->link_vlan_change);
 
-	if (parent)
-		*parent = 0;
-	if (vlanid)
-		*vlanid = 0;
+	g_return_val_if_fail (!n_ingress_map || ingress_map, FALSE);
+	g_return_val_if_fail (!n_egress_map || egress_map, FALSE);
 
-	if (nm_platform_link_get_type (self, ifindex) != NM_LINK_TYPE_VLAN)
-		return FALSE;
+	flags_set &= flags_mask;
+
+	if (_LOGD_ENABLED ()) {
+		char buf[512];
+		char *b = buf;
+		gsize len, i;
+
+		b[0] = '\0';
+		len = sizeof (buf);
+
+		if (flags_mask)
+			nm_utils_strbuf_append (&b, &len, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask);
+
+		if (ingress_reset_all || n_ingress_map) {
+			nm_utils_strbuf_append_str (&b, &len, " ingress-qos-map");
+			nm_platform_vlan_qos_mapping_to_string ("", ingress_map, n_ingress_map, b, len);
+			i = strlen (b);
+			b += i;
+			len -= i;
+			if (ingress_reset_all)
+				nm_utils_strbuf_append_str (&b, &len, " (reset-all)");
+		}
+
+		if (egress_reset_all || n_egress_map) {
+			nm_utils_strbuf_append_str (&b, &len, " egress-qos-map");
+			nm_platform_vlan_qos_mapping_to_string ("", egress_map, n_egress_map, b, len);
+			i = strlen (b);
+			b += i;
+			len -= i;
+			if (egress_reset_all)
+				nm_utils_strbuf_append_str (&b, &len, " (reset-all)");
+		}
 
-	return klass->vlan_get_info (self, ifindex, parent, vlanid);
+		_LOGD ("link: change vlan %d:%s", ifindex, buf);
+	}
+	return klass->link_vlan_change (self,
+	                                ifindex,
+	                                flags_mask,
+	                                flags_set,
+	                                ingress_reset_all,
+	                                ingress_map,
+	                                n_ingress_map,
+	                                egress_reset_all,
+	                                egress_map,
+	                                n_egress_map);
 }
 
 gboolean
-nm_platform_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to)
+nm_platform_link_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to)
 {
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (klass->vlan_set_ingress_map, FALSE);
+	NMVlanQosMapping map = {
+		.from = from,
+		.to = to,
+	};
 
-	_LOGD ("link: setting vlan ingress map for %d from %d to %d", ifindex, from, to);
-	return klass->vlan_set_ingress_map (self, ifindex, from, to);
+	return nm_platform_link_vlan_change (self, ifindex, 0, 0, FALSE, &map, 1, FALSE, NULL, 0);
 }
 
 gboolean
-nm_platform_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to)
+nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMVlanQosMapping map = {
+		.from = from,
+		.to = to,
+	};
 
-	g_return_val_if_fail (klass->vlan_set_egress_map, FALSE);
+	return nm_platform_link_vlan_change (self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1);
+}
 
-	_LOGD ("link: setting vlan egress map for %d from %d to %d", ifindex, from, to);
-	return klass->vlan_set_egress_map (self, ifindex, from, to);
+/**
+ * nm_platform_link_gre_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a software GRE device.
+ */
+NMPlatformError
+nm_platform_link_gre_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkGre *props,
+                          const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+	char buffer[INET_ADDRSTRLEN];
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_GRE, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "gre",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	if (!klass->link_gre_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 NMPlatformError
-nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, NMPlatformLink *out_link)
+nm_platform_link_infiniband_add (NMPlatform *self,
+                                 int parent,
+                                 int p_key,
+                                 const NMPlatformLink **out_link)
 {
 	gs_free char *parent_name = NULL;
 	gs_free char *name = NULL;
@@ -1571,7 +1871,6 @@ nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, N
 
 	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (p_key >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (klass->infiniband_partition_add, NM_PLATFORM_ERROR_BUG);
 
 	parent_name = g_strdup (nm_platform_link_get_name (self, parent));
 	if (   !parent_name
@@ -1591,73 +1890,330 @@ nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, N
 }
 
 gboolean
-nm_platform_infiniband_get_info (NMPlatform *self,
-                                 int ifindex,
-                                 int *parent,
-                                 int *p_key,
-                                 const char **mode)
-{
+nm_platform_link_infiniband_get_properties (NMPlatform *self,
+                                            int ifindex,
+                                            int *out_parent,
+                                            int *out_p_key,
+                                            const char **out_mode)
+{
+	const NMPlatformLnkInfiniband *plnk;
+	const NMPlatformLink *plink;
+	const char *iface;
+	char *path, *contents;
+	const char *mode;
+	int p_key = 0;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (klass->infiniband_get_info, FALSE);
 
-	return klass->infiniband_get_info (self, ifindex, parent, p_key, mode);
+	plnk = nm_platform_link_get_lnk_infiniband (self, ifindex, &plink);
+
+	if (   !plink
+	    || plink->type != NM_LINK_TYPE_INFINIBAND)
+		return FALSE;
+
+	if (plnk) {
+		NM_SET_OUT (out_parent, plink->parent);
+		NM_SET_OUT (out_p_key, plnk->p_key);
+		NM_SET_OUT (out_mode, plnk->mode);
+		return TRUE;
+	}
+
+	/* Could not get the link information via netlink. To support older kernels,
+	 * fallback to reading sysfs. */
+
+	iface = ASSERT_VALID_PATH_COMPONENT (plink->name);
+
+	/* Fall back to reading sysfs */
+	path = g_strdup_printf ("/sys/class/net/%s/mode", iface);
+	contents = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (!contents)
+		return FALSE;
+
+	if (strstr (contents, "datagram"))
+		mode = "datagram";
+	else if (strstr (contents, "connected"))
+		mode = "connected";
+	else
+		mode = NULL;
+	g_free (contents);
+
+	path = g_strdup_printf ("/sys/class/net/%s/pkey", iface);
+	contents = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (!contents)
+		return FALSE;
+	p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
+	g_free (contents);
+	if (p_key < 0)
+		return FALSE;
+
+	NM_SET_OUT (out_parent, plink->parent);
+	NM_SET_OUT (out_p_key, p_key);
+	NM_SET_OUT (out_mode, mode);
+	return TRUE;
 }
 
-gboolean
-nm_platform_veth_get_properties (NMPlatform *self, int ifindex, NMPlatformVethProperties *props)
+/**
+ * nm_platform_ip6tnl_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create an IPv6 tunnel.
+ */
+NMPlatformError
+nm_platform_link_ip6tnl_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMPlatformError plerr;
+	char buffer[INET6_ADDRSTRLEN];
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
 
-	return klass->veth_get_properties (self, ifindex, props);
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ip6tnl",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet6_ntop (&props->local, NULL),
+	       nm_utils_inet6_ntop (&props->remote, buffer));
+
+	if (!klass->link_ip6tnl_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
-gboolean
-nm_platform_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
+/**
+ * nm_platform_ipip_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create an IPIP tunnel.
+ */
+NMPlatformError
+nm_platform_link_ipip_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLnkIpIp *props,
+                           const NMPlatformLink **out_link)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMPlatformError plerr;
+	char buffer[INET_ADDRSTRLEN];
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	return klass->tun_get_properties (self, ifindex, props);
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ipip",
+	       name,
+		   props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	if (!klass->link_ipip_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
+ * nm_platform_macvlan_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a MACVLAN or MACVTAP device.
+ */
+NMPlatformError
+nm_platform_link_macvlan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLnkMacvlan *props,
+                              const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+	NMLinkType type;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_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;
+
+	_LOGD ("adding %s '%s' parent %u mode %u",
+	       props->tap ? "macvtap" : "macvlan",
+	       name,
+	       parent,
+	       props->mode);
+
+	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
+ * nm_platform_sit_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a software SIT device.
+ */
+NMPlatformError
+nm_platform_link_sit_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkSit *props,
+                          const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+	char buffer[INET_ADDRSTRLEN];
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "sit",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	if (!klass->link_sit_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 gboolean
-nm_platform_macvlan_get_properties (NMPlatform *self, int ifindex, NMPlatformMacvlanProperties *props)
+nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_peer_ifindex)
 {
+	const NMPlatformLink *plink;
+	int peer_ifindex;
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	plink = nm_platform_link_get (self, ifindex);
+
+	if (!plink)
+		return FALSE;
+	if (plink->type != NM_LINK_TYPE_VETH)
+		return FALSE;
+
+	if (plink->parent != 0) {
+		NM_SET_OUT (out_peer_ifindex, plink->parent);
+		return TRUE;
+	}
+
+	/* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */
+	if (out_peer_ifindex) {
+		peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->name);
+		if (peer_ifindex <= 0)
+			return FALSE;
 
-	return klass->macvlan_get_properties (self, ifindex, props);
+		*out_peer_ifindex = peer_ifindex;
+	}
+	return TRUE;
 }
 
 gboolean
-nm_platform_vxlan_get_properties (NMPlatform *self, int ifindex, NMPlatformVxlanProperties *props)
+nm_platform_link_tun_get_properties_ifname (NMPlatform *self, const char *ifname, NMPlatformTunProperties *props)
 {
+	char *path, *val;
+	gboolean success = TRUE;
+
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	g_return_val_if_fail (props, FALSE);
+
+	memset (props, 0, sizeof (*props));
+	props->owner = -1;
+	props->group = -1;
 
-	return klass->vxlan_get_properties (self, ifindex, props);
+	if (!ifname || !nm_utils_iface_valid_name (ifname))
+		return FALSE;
+	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+
+	path = g_strdup_printf ("/sys/class/net/%s/owner", ifname);
+	val = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (val) {
+		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
+		if (errno)
+			success = FALSE;
+		g_free (val);
+	} else
+		success = FALSE;
+
+	path = g_strdup_printf ("/sys/class/net/%s/group", ifname);
+	val = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (val) {
+		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
+		if (errno)
+			success = FALSE;
+		g_free (val);
+	} else
+		success = FALSE;
+
+	path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname);
+	val = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (val) {
+		gint64 flags;
+
+		flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
+		if (!errno) {
+			props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
+			props->no_pi = !!(flags & IFF_NO_PI);
+			props->vnet_hdr = !!(flags & IFF_VNET_HDR);
+			props->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
+		} else
+			success = FALSE;
+		g_free (val);
+	} else
+		success = FALSE;
+
+	return success;
 }
 
 gboolean
-nm_platform_gre_get_properties (NMPlatform *self, int ifindex, NMPlatformGreProperties *props)
+nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props != NULL, FALSE);
 
-	return klass->gre_get_properties (self, ifindex, props);
+	return nm_platform_link_tun_get_properties_ifname (self, nm_platform_link_get_name (self, ifindex), props);
 }
 
 gboolean
@@ -1680,16 +2236,6 @@ nm_platform_wifi_get_bssid (NMPlatform *self, int ifindex, guint8 *bssid)
 	return klass->wifi_get_bssid (self, ifindex, bssid);
 }
 
-GByteArray *
-nm_platform_wifi_get_ssid (NMPlatform *self, int ifindex)
-{
-	_CHECK_SELF (self, klass, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
-	return klass->wifi_get_ssid (self, ifindex);
-}
-
 guint32
 nm_platform_wifi_get_frequency (NMPlatform *self, int ifindex)
 {
@@ -1740,6 +2286,22 @@ nm_platform_wifi_set_mode (NMPlatform *self, int ifindex, NM80211Mode mode)
 	klass->wifi_set_mode (self, ifindex, mode);
 }
 
+static void
+wifi_set_powersave (NMPlatform *p, int ifindex, guint32 powersave)
+{
+	/* empty */
+}
+
+void
+nm_platform_wifi_set_powersave (NMPlatform *self, int ifindex, guint32 powersave)
+{
+	_CHECK_SELF_VOID (self, klass);
+
+	g_return_if_fail (ifindex > 0);
+
+	klass->wifi_set_powersave (self, ifindex, powersave);
+}
+
 guint32
 nm_platform_wifi_find_frequency (NMPlatform *self, int ifindex, const guint32 *freqs)
 {
@@ -1818,13 +2380,29 @@ _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
 
 /******************************************************************/
 
+void
+nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, int plen)
+{
+	addr->address = address;
+	addr->peer_address = address;
+	addr->plen = plen;
+}
+
+const struct in6_addr *
+nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr)
+{
+	if (   IN6_IS_ADDR_UNSPECIFIED (&addr->peer_address)
+	    || IN6_ARE_ADDR_EQUAL (&addr->peer_address, &addr->address))
+		return &addr->address;
+	return &addr->peer_address;
+}
+
 GArray *
 nm_platform_ip4_address_get_all (NMPlatform *self, int ifindex)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex > 0, NULL);
-	g_return_val_if_fail (klass->ip4_address_get_all, NULL);
 
 	return klass->ip4_address_get_all (self, ifindex);
 }
@@ -1835,17 +2413,16 @@ nm_platform_ip6_address_get_all (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex > 0, NULL);
-	g_return_val_if_fail (klass->ip6_address_get_all, NULL);
 
 	return klass->ip6_address_get_all (self, ifindex);
 }
 
 gboolean
 nm_platform_ip4_address_add (NMPlatform *self,
-	                         int ifindex,
+                             int ifindex,
                              in_addr_t address,
-                             in_addr_t peer_address,
                              int plen,
+                             in_addr_t peer_address,
                              guint32 lifetime,
                              guint32 preferred,
                              const char *label)
@@ -1856,7 +2433,6 @@ nm_platform_ip4_address_add (NMPlatform *self,
 	g_return_val_if_fail (plen > 0, FALSE);
 	g_return_val_if_fail (lifetime > 0, FALSE);
 	g_return_val_if_fail (preferred <= lifetime, FALSE);
-	g_return_val_if_fail (klass->ip4_address_add, FALSE);
 	g_return_val_if_fail (!label || strlen (label) < sizeof (((NMPlatformIP4Address *) NULL)->label), FALSE);
 
 	if (_LOGD_ENABLED ()) {
@@ -1872,17 +2448,17 @@ 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));
+		_LOGD ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip4_address_add (self, ifindex, address, peer_address, plen, lifetime, preferred, label);
+	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, label);
 }
 
 gboolean
 nm_platform_ip6_address_add (NMPlatform *self,
                              int ifindex,
                              struct in6_addr address,
-                             struct in6_addr peer_address,
                              int plen,
+                             struct in6_addr peer_address,
                              guint32 lifetime,
                              guint32 preferred,
                              guint flags)
@@ -1893,7 +2469,6 @@ nm_platform_ip6_address_add (NMPlatform *self,
 	g_return_val_if_fail (plen > 0, FALSE);
 	g_return_val_if_fail (lifetime > 0, FALSE);
 	g_return_val_if_fail (preferred <= lifetime, FALSE);
-	g_return_val_if_fail (klass->ip6_address_add, FALSE);
 
 	if (_LOGD_ENABLED ()) {
 		NMPlatformIP6Address addr = { 0 };
@@ -1907,28 +2482,27 @@ nm_platform_ip6_address_add (NMPlatform *self,
 		addr.preferred = preferred;
 		addr.flags = flags;
 
-		_LOGD ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr));
+		_LOGD ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip6_address_add (self, ifindex, address, peer_address, plen, lifetime, preferred, flags);
+	return klass->ip6_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
 }
 
 gboolean
 nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, int plen, in_addr_t peer_address)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
-	char str_peer[NM_UTILS_INET_ADDRSTRLEN];
+	char str_peer2[NM_UTILS_INET_ADDRSTRLEN];
+	char str_peer[100];
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen > 0, FALSE);
-	g_return_val_if_fail (klass->ip4_address_delete, FALSE);
 
-	_LOGD ("address: deleting IPv4 address %s/%d, %s%s%sifindex %d%s",
+	_LOGD ("address: deleting IPv4 address %s/%d, %sifindex %d%s",
 	       nm_utils_inet4_ntop (address, NULL), plen,
-	       peer_address ? "peer " : "",
-	       peer_address ? nm_utils_inet4_ntop (peer_address, str_peer) : "",
-	       peer_address ? ", " : "",
+	       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)));
 	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
@@ -1943,7 +2517,6 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen > 0, FALSE);
-	g_return_val_if_fail (klass->ip6_address_delete, FALSE);
 
 	_LOGD ("address: deleting IPv6 address %s/%d, ifindex %d%s",
 	       nm_utils_inet6_ntop (&address, NULL), plen, ifindex,
@@ -1952,21 +2525,21 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 }
 
 const NMPlatformIP4Address *
-nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, int plen)
+nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, int plen, guint32 peer_address)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (plen > 0, NULL);
 
-	return klass->ip4_address_get (self, ifindex, address, plen);
+	return klass->ip4_address_get (self, ifindex, address, plen, peer_address);
 }
 
 const NMPlatformIP6Address *
 nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address, int plen)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (plen > 0, NULL);
 
 	return klass->ip6_address_get (self, ifindex, address, plen);
 }
@@ -1980,7 +2553,9 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
 	for (i = 0; i < len; i++) {
 		NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
 
-		if (candidate->address == address->address && candidate->plen == address->plen) {
+		if (   candidate->address == address->address
+		    && candidate->plen == address->plen
+		    && ((candidate->peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (address->plen)) == 0) {
 			guint32 lifetime, preferred;
 
 			if (nmp_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
@@ -2064,7 +2639,7 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		                             now, ADDRESS_LIFETIME_PADDING, &lifetime, &preferred))
 			continue;
 
-		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->peer_address, known_address->plen, lifetime, preferred, known_address->label))
+		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen, known_address->peer_address, lifetime, preferred, known_address->label))
 			return FALSE;
 
 		if (out_added_addresses) {
@@ -2125,7 +2700,7 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 			continue;
 
 		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
-		                                  known_address->peer_address, known_address->plen,
+		                                  known_address->plen, known_address->peer_address,
 		                                  lifetime, preferred, known_address->flags))
 			return FALSE;
 	}
@@ -2164,17 +2739,46 @@ nm_platform_ip6_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRoute
 	return klass->ip6_route_get_all (self, ifindex, flags);
 }
 
+/**
+ * nm_platform_ip4_route_add:
+ * @self:
+ * @ifindex:
+ * @source:
+ * network:
+ * plen:
+ * gateway:
+ * pref_src:
+ * metric:
+ * mss:
+ *
+ * For kernel, a gateway can be either explicitly set or left
+ * at zero (0.0.0.0). In addition, there is the scope of the IPv4
+ * route.
+ * When adding a route with
+ *   $ ip route add default dev $IFNAME
+ * the resulting route will have gateway 0.0.0.0 and scope "link".
+ * Contrary to
+ *   $ ip route add default via 0.0.0.0 dev $IFNAME
+ * which adds the route with scope "global".
+ *
+ * NetworkManager's Platform can currently only add on-link-routes with scope
+ * "link" (and gateway 0.0.0.0) or gateway-routes with scope "global" (and
+ * gateway not 0.0.0.0).
+ *
+ * It does not support adding globally scoped routes via 0.0.0.0.
+ *
+ * Returns: %TRUE in case of success.
+ */
 gboolean
 nm_platform_ip4_route_add (NMPlatform *self,
                            int ifindex, NMIPConfigSource source,
                            in_addr_t network, int plen,
-                           in_addr_t gateway, guint32 pref_src,
+                           in_addr_t gateway, in_addr_t pref_src,
                            guint32 metric, guint32 mss)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (0 <= plen && plen <= 32, FALSE);
-	g_return_val_if_fail (klass->ip4_route_add, FALSE);
 
 	if (_LOGD_ENABLED ()) {
 		NMPlatformIP4Route route = { 0 };
@@ -2188,7 +2792,7 @@ nm_platform_ip4_route_add (NMPlatform *self,
 		route.mss = mss;
 		route.pref_src = pref_src;
 
-		_LOGD ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (&route));
+		_LOGD ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (&route, NULL, 0));
 	}
 	return klass->ip4_route_add (self, ifindex, source, network, plen, gateway, pref_src, metric, mss);
 }
@@ -2202,7 +2806,6 @@ nm_platform_ip6_route_add (NMPlatform *self,
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (0 <= plen && plen <= 128, FALSE);
-	g_return_val_if_fail (klass->ip6_route_add, FALSE);
 
 	if (_LOGD_ENABLED ()) {
 		NMPlatformIP6Route route = { 0 };
@@ -2215,7 +2818,7 @@ nm_platform_ip6_route_add (NMPlatform *self,
 		route.metric = metric;
 		route.mss = mss;
 
-		_LOGD ("route: adding or updating IPv6 route: %s", nm_platform_ip6_route_to_string (&route));
+		_LOGD ("route: adding or updating IPv6 route: %s", nm_platform_ip6_route_to_string (&route, NULL, 0));
 	}
 	return klass->ip6_route_add (self, ifindex, source, network, plen, gateway, metric, mss);
 }
@@ -2227,8 +2830,6 @@ nm_platform_ip4_route_delete (NMPlatform *self, int ifindex, in_addr_t network,
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->ip4_route_delete, FALSE);
-
 	_LOGD ("route: deleting IPv4 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
 	       nm_utils_inet4_ntop (network, NULL), plen, metric, ifindex,
 	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
@@ -2242,8 +2843,6 @@ nm_platform_ip6_route_delete (NMPlatform *self, int ifindex, struct in6_addr net
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->ip6_route_delete, FALSE);
-
 	_LOGD ("route: deleting IPv6 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
 	       nm_utils_inet6_ntop (&network, NULL), plen, metric, ifindex,
 	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
@@ -2268,6 +2867,40 @@ nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr networ
 
 /******************************************************************/
 
+const char *
+nm_platform_vlan_qos_mapping_to_string (const char *name,
+                                        const NMVlanQosMapping *map,
+                                        gsize n_map,
+                                        char *buf,
+                                        gsize len)
+{
+	gsize i;
+	char *b;
+
+	nm_utils_to_string_buffer_init (&buf, &len);
+
+	if (!n_map) {
+		nm_utils_strbuf_append_str (&buf, &len, "");
+		return buf;
+	}
+
+	if (!map)
+		g_return_val_if_reached ("");
+
+	b = buf;
+
+	if (name) {
+		nm_utils_strbuf_append_str (&b, &len, name);
+		nm_utils_strbuf_append_str (&b, &len, " {");
+	} else
+		nm_utils_strbuf_append_c (&b, &len, '{');
+
+	for (i = 0; i < n_map; i++)
+		nm_utils_strbuf_append (&b, &len, " %u:%u", map[i].from, map[i].to);
+	nm_utils_strbuf_append_str (&b, &len, " }");
+	return buf;
+}
+
 static const char *
 source_to_string (NMIPConfigSource source)
 {
@@ -2320,21 +2953,29 @@ _lifetime_summary_to_string (gint32 now, guint32 timestamp, guint32 preferred, g
 	return buf;
 }
 
-char _nm_platform_to_string_buffer[256];
-
+/**
+ * nm_platform_link_to_string:
+ * @route: pointer to NMPlatformLink address structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
+ *
+ * A method for converting an link struct into a string representation.
+ *
+ * Returns: a string representation of the link.
+ */
 const char *
-nm_platform_link_to_string (const NMPlatformLink *link)
+nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 {
 	char master[20];
 	char parent[20];
-	char str_vlan[16];
 	GString *str_flags;
 	char str_addrmode[30];
 	gs_free char *str_addr = NULL;
 	gs_free char *str_inet6_token = NULL;
+	const char *str_link_type;
 
-	if (!link)
-		return "(unknown link)";
+	if (!nm_utils_to_string_buffer_init_null (link, &buf, &len))
+		return buf;
 
 	str_flags = g_string_new (NULL);
 	if (NM_FLAGS_HAS (link->flags, IFF_NOARP))
@@ -2349,7 +2990,7 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	if (link->flags) {
 		char str_flags_buf[64];
 
-		rtnl_link_flags2str (link->flags, str_flags_buf, sizeof (str_flags_buf));
+		nm_platform_link_flags2str (link->flags, str_flags_buf, sizeof (str_flags_buf));
 		g_string_append_printf (str_flags, ";%s", str_flags_buf);
 	}
 
@@ -2365,11 +3006,6 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	else
 		parent[0] = 0;
 
-	if (link->vlan_id)
-		g_snprintf (str_vlan, sizeof (str_vlan), " vlan %u", (guint) link->vlan_id);
-	else
-		str_vlan[0] = '\0';
-
 	if (link->inet6_addr_gen_mode_inv) {
 		switch (_nm_platform_uint8_inv (link->inet6_addr_gen_mode_inv)) {
 			case 0:
@@ -2390,14 +3026,15 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	if (link->inet6_token.is_valid)
 		str_inet6_token = nm_utils_hwaddr_ntoa (&link->inet6_token.iid, sizeof (link->inet6_token.iid));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer),
+	str_link_type = nm_link_type_to_string (link->type);
+
+	g_snprintf (buf, len,
 	            "%d: " /* ifindex */
 	            "%s" /* name */
 	            "%s" /* parent */
 	            " <%s>" /* flags */
 	            " mtu %d"
 	            "%s" /* master */
-	            "%s" /* vlan */
 	            " arp %u" /* arptype */
 	            "%s%s" /* link->type */
 	            "%s%s" /* kind */
@@ -2412,12 +3049,11 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	            parent,
 	            str_flags->str,
 	            link->mtu, master,
-	            str_vlan,
 	            link->arptype,
-	            nm_link_type_to_string (link->type) ? " " : "",
-	            str_if_set (nm_link_type_to_string (link->type), "???"),
-	            link->kind ? (g_strcmp0 (nm_link_type_to_string (link->type), link->kind) ? "/" : "*") : "",
-	            link->kind && g_strcmp0 (nm_link_type_to_string (link->type), link->kind) ? link->kind : "",
+	            str_link_type ? " " : "",
+	            str_if_set (str_link_type, "???"),
+	            link->kind ? (g_strcmp0 (str_link_type, link->kind) ? "/" : "*") : "",
+	            link->kind && g_strcmp0 (str_link_type, link->kind) ? link->kind : "",
 	            link->initialized ? " init" : " not-init",
 	            str_addrmode,
 	            str_addr ? " addr " : "",
@@ -2427,23 +3063,313 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	            link->driver ? " driver " : "",
 	            link->driver ? link->driver : "");
 	g_string_free (str_flags, TRUE);
-	return _nm_platform_to_string_buffer;
+	return buf;
+}
+
+const char *
+nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tos[30];
+	char str_parent_ifindex[30];
+	char str_input_flags[30];
+	char str_output_flags[30];
+	char str_input_key[30];
+	char str_input_key1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_output_key[30];
+	char str_output_key1[NM_UTILS_INET_ADDRSTRLEN];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "gre"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tos */
+	            "%s" /* path_mtu_discovery */
+	            "%s" /* iflags */
+	            "%s" /* oflags */
+	            "%s" /* ikey */
+	            "%s" /* okey */
+	            "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
+	            lnk->path_mtu_discovery ? "" : " nopmtudisc",
+	            lnk->input_flags ? nm_sprintf_buf (str_input_flags, " iflags 0x%x", lnk->input_flags) : "",
+	            lnk->output_flags ? nm_sprintf_buf (str_output_flags, " oflags 0x%x", lnk->output_flags) : "",
+	            NM_FLAGS_HAS (lnk->input_flags, GRE_KEY) || lnk->input_key ? nm_sprintf_buf (str_input_key, " ikey %s", nm_utils_inet4_ntop (lnk->input_key, str_input_key1)) : "",
+	            NM_FLAGS_HAS (lnk->output_flags, GRE_KEY) || lnk->output_key ? nm_sprintf_buf (str_output_key, " okey %s", nm_utils_inet4_ntop (lnk->output_key, str_output_key1)) : "");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_infiniband_to_string (const NMPlatformLnkInfiniband *lnk, char *buf, gsize len)
+{
+	char str_p_key[64];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "infiniband"
+	            "%s" /* p_key */
+	            "%s%s" /* mode */
+	            "",
+	            lnk->p_key ? nm_sprintf_buf (str_p_key, " pkey %d", lnk->p_key) : "",
+	            lnk->mode ? " mode " : "",
+	            lnk->mode ?: "");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tclass[30];
+	char str_flow[30];
+	char str_encap[30];
+	char str_proto[30];
+	char str_parent_ifindex[30];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "ip6tnl"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tclass */
+	            "%s" /* encap limit */
+	            "%s" /* flow label */
+	            "%s" /* proto */
+	            "",
+	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
+	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass),
+	            nm_sprintf_buf (str_encap, " encap-limit %u", lnk->encap_limit),
+	            nm_sprintf_buf (str_flow, " flow-label 0x05%x", lnk->flow_label),
+	            nm_sprintf_buf (str_proto, " proto %u", lnk->proto));
+	return buf;
+}
+
+const char *
+nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tos[30];
+	char str_parent_ifindex[30];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "ipip"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tos */
+	            "%s" /* path_mtu_discovery */
+	            "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
+	            lnk->path_mtu_discovery ? "" : " nopmtudisc");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len)
+{
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "macvlan mode %u %s",
+	            lnk->mode,
+	            lnk->no_promisc ? "not-promisc" : "promisc");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tos[30];
+	char str_flags[30];
+	char str_proto[30];
+	char str_parent_ifindex[30];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "sit"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tos */
+	            "%s" /* path_mtu_discovery */
+	            "%s" /* flags */
+	            "%s" /* proto */
+	            "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
+	            lnk->path_mtu_discovery ? "" : " nopmtudisc",
+	            lnk->flags ? nm_sprintf_buf (str_flags, " flags 0x%x", lnk->flags) : "",
+	            lnk->proto ? nm_sprintf_buf (str_proto, " proto 0x%x", lnk->proto) : "");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len)
+{
+	char *b;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	b = buf;
+
+	nm_utils_strbuf_append (&b, &len, "vlan %u", lnk->id);
+	if (lnk->flags)
+		nm_utils_strbuf_append (&b, &len, " flags 0x%x", lnk->flags);
+	return buf;
+}
+
+const char *
+nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len)
+{
+	char str_group[100];
+	char str_group6[100];
+	char str_local[100];
+	char str_local6[100];
+	char str_dev[25];
+	char str_limit[25];
+	char str_src_port[35];
+	char str_dst_port[25];
+	char str_tos[25];
+	char str_ttl[25];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	if (lnk->group == 0)
+		str_group[0] = '\0';
+	else {
+		g_snprintf (str_group, sizeof (str_group),
+		            " %s %s",
+		            IN_MULTICAST (ntohl (lnk->group)) ? "group" : "remote",
+		            nm_utils_inet4_ntop (lnk->group, NULL));
+	}
+	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->group6))
+		str_group6[0] = '\0';
+	else {
+		g_snprintf (str_group6, sizeof (str_group6),
+		            " %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));
+	}
+
+	if (lnk->local == 0)
+		str_local[0] = '\0';
+	else {
+		g_snprintf (str_local, sizeof (str_local),
+		            " local %s",
+		            nm_utils_inet4_ntop (lnk->local, NULL));
+	}
+	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->local6))
+		str_local6[0] = '\0';
+	else {
+		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));
+	}
+
+	g_snprintf (buf, len,
+	            "vxlan"
+	            " id %u" /* id */
+	            "%s%s" /* group/group6 */
+	            "%s%s" /* local/local6 */
+	            "%s" /* dev */
+	            "%s" /* src_port_min/src_port_max */
+	            "%s" /* dst_port */
+	            "%s" /* learning */
+	            "%s" /* proxy */
+	            "%s" /* rsc */
+	            "%s" /* l2miss */
+	            "%s" /* l3miss */
+	            "%s" /* tos */
+	            "%s" /* ttl */
+	            " ageing %u" /* ageing */
+	            "%s" /* limit */
+	            "",
+	            (guint) lnk->id,
+	            str_group, str_group6,
+	            str_local, str_local6,
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_dev, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->src_port_min || lnk->src_port_max ? nm_sprintf_buf (str_src_port, " srcport %u %u", lnk->src_port_min, lnk->src_port_max) : "",
+	            lnk->dst_port ? nm_sprintf_buf (str_dst_port, " dstport %u", lnk->dst_port) : "",
+	            !lnk->learning ? " nolearning" : "",
+	            lnk->proxy ? " proxy" : "",
+	            lnk->rsc ? " rsc" : "",
+	            lnk->l2miss ? " l2miss" : "",
+	            lnk->l3miss ? " l3miss" : "",
+	            lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos %#x", lnk->tos),
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : "",
+	            lnk->ageing,
+	            lnk->limit ? nm_sprintf_buf (str_limit, " maxaddr %u", lnk->limit) : "");
+	return buf;
 }
 
 /**
  * nm_platform_ip4_address_to_string:
  * @route: pointer to NMPlatformIP4Address address structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting an address struct into a string representation.
  *
  * Example output: ""
  *
- * Returns: a string representation of the address. The returned string
- * is an internal buffer, so do not keep or free the returned string.
- * Also, this function is not thread safe.
+ * Returns: a string representation of the address.
  */
 const char *
-nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
+nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len)
 {
 	char s_address[INET_ADDRSTRLEN];
 	char s_peer[INET_ADDRSTRLEN];
@@ -2454,11 +3380,12 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
 	const char *str_lft_p, *str_pref_p, *str_time_p;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 
-	g_return_val_if_fail (address, "(unknown)");
+	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
+		return buf;
 
 	inet_ntop (AF_INET, &address->address, s_address, sizeof (s_address));
 
-	if (address->peer_address) {
+	if (address->peer_address != address->address) {
 		inet_ntop (AF_INET, &address->peer_address, s_peer, sizeof (s_peer));
 		str_peer = g_strconcat (" ptp ", s_peer, NULL);
 	}
@@ -2480,66 +3407,92 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
 	                                      now, str_pref, sizeof (str_pref)) );
 	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	g_snprintf (buf, len,
+	            "%s/%d lft %s pref %s%s%s%s%s src %s",
 	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
 	            str_peer ? str_peer : "",
 	            str_dev,
 	            str_label,
 	            source_to_string (address->source));
 	g_free (str_peer);
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
-/**
- * nm_platform_addr_flags2str: wrapper for rtnl_addr_flags2str(),
- * which might not yet support some recent address flags.
- **/
-void
-nm_platform_addr_flags2str (int flags, char *buf, size_t size)
-{
-	rtnl_addr_flags2str(flags, buf, size);
-
-	/* There are two recent flags IFA_F_MANAGETEMPADDR and IFA_F_NOPREFIXROUTE.
-	 * If libnl does not yet support them, add them by hand.
-	 * These two flags were introduced together with the extended ifa_flags,
-	 * so, check for that.
-	 */
-	if ((flags & IFA_F_MANAGETEMPADDR) && !nm_platform_check_support_libnl_extended_ifa_flags ()) {
-		strncat (buf, buf[0] ? "," IFA_F_MANAGETEMPADDR_STR : IFA_F_MANAGETEMPADDR_STR,
-		         size - strlen (buf) - 1);
-	}
-	if ((flags & IFA_F_NOPREFIXROUTE) && !nm_platform_check_support_libnl_extended_ifa_flags ()) {
-		strncat (buf, buf[0] ? "," IFA_F_NOPREFIXROUTE_STR : IFA_F_NOPREFIXROUTE_STR,
-		         size - strlen (buf) - 1);
-	}
-}
+NM_UTILS_FLAGS2STR_DEFINE (nm_platform_link_flags2str, unsigned,
+	NM_UTILS_FLAGS2STR (IFF_LOOPBACK, "loopback"),
+	NM_UTILS_FLAGS2STR (IFF_BROADCAST, "broadcast"),
+	NM_UTILS_FLAGS2STR (IFF_POINTOPOINT, "pointopoint"),
+	NM_UTILS_FLAGS2STR (IFF_MULTICAST, "multicast"),
+	NM_UTILS_FLAGS2STR (IFF_NOARP, "noarp"),
+	NM_UTILS_FLAGS2STR (IFF_ALLMULTI, "allmulti"),
+	NM_UTILS_FLAGS2STR (IFF_PROMISC, "promisc"),
+	NM_UTILS_FLAGS2STR (IFF_MASTER, "master"),
+	NM_UTILS_FLAGS2STR (IFF_SLAVE, "slave"),
+	NM_UTILS_FLAGS2STR (IFF_DEBUG, "debug"),
+	NM_UTILS_FLAGS2STR (IFF_DYNAMIC, "dynamic"),
+	NM_UTILS_FLAGS2STR (IFF_AUTOMEDIA, "automedia"),
+	NM_UTILS_FLAGS2STR (IFF_PORTSEL, "portsel"),
+	NM_UTILS_FLAGS2STR (IFF_NOTRAILERS, "notrailers"),
+	NM_UTILS_FLAGS2STR (IFF_UP, "up"),
+	NM_UTILS_FLAGS2STR (IFF_RUNNING, "running"),
+	NM_UTILS_FLAGS2STR (IFF_LOWER_UP, "lowerup"),
+	NM_UTILS_FLAGS2STR (IFF_DORMANT, "dormant"),
+	NM_UTILS_FLAGS2STR (IFF_ECHO, "echo"),
+);
+
+NM_UTILS_ENUM2STR_DEFINE (nm_platform_link_inet6_addrgenmode2str, guint8,
+	NM_UTILS_ENUM2STR (NM_IN6_ADDR_GEN_MODE_NONE, "none"),
+	NM_UTILS_ENUM2STR (NM_IN6_ADDR_GEN_MODE_EUI64, "eui64"),
+	NM_UTILS_ENUM2STR (NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"),
+);
+
+NM_UTILS_FLAGS2STR_DEFINE (nm_platform_addr_flags2str, unsigned,
+	NM_UTILS_FLAGS2STR (IFA_F_SECONDARY, "secondary"),
+	NM_UTILS_FLAGS2STR (IFA_F_NODAD, "nodad"),
+	NM_UTILS_FLAGS2STR (IFA_F_OPTIMISTIC, "optimistic"),
+	NM_UTILS_FLAGS2STR (IFA_F_HOMEADDRESS, "homeaddress"),
+	NM_UTILS_FLAGS2STR (IFA_F_DEPRECATED, "deprecated"),
+	NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"),
+	NM_UTILS_FLAGS2STR (IFA_F_PERMANENT, "permanent"),
+	NM_UTILS_FLAGS2STR (IFA_F_MANAGETEMPADDR, "mngtmpaddr"),
+	NM_UTILS_FLAGS2STR (IFA_F_NOPREFIXROUTE, "noprefixroute"),
+);
+
+NM_UTILS_ENUM2STR_DEFINE (nm_platform_route_scope2str, int,
+	NM_UTILS_ENUM2STR (RT_SCOPE_NOWHERE, "nowhere"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_HOST, "host"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_LINK, "link"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_SITE, "site"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_UNIVERSE, "global"),
+);
 
 /**
  * nm_platform_ip6_address_to_string:
  * @route: pointer to NMPlatformIP6Address address structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting an address struct into a string representation.
  *
  * Example output: "2001:db8:0:f101::1/64 lft 4294967295 pref 4294967295 time 16922666 on dev em1"
  *
- * Returns: a string representation of the address. The returned string
- * is an internal buffer, so do not keep or free the returned string.
- * Also, this function is not thread safe.
+ * Returns: a string representation of the address.
  */
 const char *
-nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
+nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len)
 {
+#define S_FLAGS_PREFIX " flags "
 	char s_flags[256];
 	char s_address[INET6_ADDRSTRLEN];
 	char s_peer[INET6_ADDRSTRLEN];
 	char str_lft[30], str_pref[30], str_time[50];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
-	char *str_flags;
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 
-	g_return_val_if_fail (address, "(unknown)");
+	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
+		return buf;
 
 	inet_ntop (AF_INET6, &address->address, s_address, sizeof (s_address));
 
@@ -2550,9 +3503,11 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
 
 	_to_string_dev (NULL, address->ifindex, str_dev, sizeof (str_dev));
 
-	nm_platform_addr_flags2str (address->flags, s_flags, sizeof (s_flags));
-
-	str_flags = s_flags[0] ? g_strconcat (" flags ", s_flags, NULL) : NULL;
+	nm_platform_addr_flags2str (address->flags, &s_flags[STRLEN (S_FLAGS_PREFIX)], sizeof (s_flags) - STRLEN (S_FLAGS_PREFIX));
+	if (s_flags[STRLEN (S_FLAGS_PREFIX)] == '\0')
+		s_flags[0] = '\0';
+	else
+		memcpy (s_flags, S_FLAGS_PREFIX, STRLEN (S_FLAGS_PREFIX));
 
 	str_lft_p = _lifetime_to_string (address->timestamp,
 	                                 address->lifetime ? address->lifetime : NM_PLATFORM_LIFETIME_PERMANENT,
@@ -2564,45 +3519,46 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
 	                                      now, str_pref, sizeof (str_pref)) );
 	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	g_snprintf (buf, len,
+	            "%s/%d lft %s pref %s%s%s%s%s src %s",
 	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
 	            str_peer ? str_peer : "",
 	            str_dev,
-	            str_flags ? str_flags : "",
+	            s_flags,
 	            source_to_string (address->source));
-	g_free (str_flags);
 	g_free (str_peer);
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
 /**
  * nm_platform_ip4_route_to_string:
  * @route: pointer to NMPlatformIP4Route route structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting a route struct into a string representation.
  *
  * Example output: "192.168.1.0/24 via 0.0.0.0 dev em1 metric 0 mss 0"
  *
- * Returns: a string representation of the route. The returned string
- * is an internal buffer, so do not keep or free the returned string.
- * Also, this function is not thread safe.
+ * Returns: a string representation of the route.
  */
 const char *
-nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route)
+nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len)
 {
 	char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
 	char s_pref_src[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 	char str_scope[30];
 
-	g_return_val_if_fail (route, "(unknown)");
+	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
+		return buf;
 
 	inet_ntop (AF_INET, &route->network, s_network, sizeof(s_network));
 	inet_ntop (AF_INET, &route->gateway, s_gateway, sizeof(s_gateway));
 
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer),
+	g_snprintf (buf, len,
 	            "%s/%d"
 	            " via %s"
 	            "%s"
@@ -2619,38 +3575,39 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route)
 	            route->mss,
 	            source_to_string (route->source),
 	            route->scope_inv ? " scope " : "",
-	            route->scope_inv ? (rtnl_scope2str (nm_platform_route_scope_inv (route->scope_inv), str_scope, sizeof (str_scope))) : "",
+	            route->scope_inv ? (nm_platform_route_scope2str (nm_platform_route_scope_inv (route->scope_inv), str_scope, sizeof (str_scope))) : "",
 	            route->pref_src ? " pref-src " : "",
 	            route->pref_src ? inet_ntop (AF_INET, &route->pref_src, s_pref_src, sizeof(s_pref_src)) : "");
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
 /**
  * nm_platform_ip6_route_to_string:
  * @route: pointer to NMPlatformIP6Route route structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting a route struct into a string representation.
  *
  * Example output: "ff02::fb/128 via :: dev em1 metric 0"
  *
- * Returns: a string representation of the route. The returned string
- * is an internal buffer, so do not keep or free the returned string.
- * Also, this function is not thread safe.
+ * Returns: a string representation of the route.
  */
 const char *
-nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
+nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len)
 {
 	char s_network[INET6_ADDRSTRLEN], s_gateway[INET6_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 
-	g_return_val_if_fail (route, "(unknown)");
+	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
+		return buf;
 
 	inet_ntop (AF_INET6, &route->network, s_network, sizeof(s_network));
 	inet_ntop (AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway));
 
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer),
+	g_snprintf (buf, len,
 	            "%s/%d"
 	            " via %s"
 	            "%s"
@@ -2664,10 +3621,10 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
 	            route->metric,
 	            route->mss,
 	            source_to_string (route->source));
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
-#define _CMP_POINTER(a, b)                                  \
+#define _CMP_SELF(a, b)                                     \
     G_STMT_START {                                          \
         if ((a) == (b))                                     \
             return 0;                                       \
@@ -2677,6 +3634,19 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
             return 1;                                       \
     } G_STMT_END
 
+#define _CMP_DIRECT(a, b)                                   \
+    G_STMT_START {                                          \
+        if ((a) != (b))                                     \
+            return ((a) < (b)) ? -1 : 1;                    \
+    } G_STMT_END
+
+#define _CMP_DIRECT_MEMCMP(a, b, size)                      \
+    G_STMT_START {                                          \
+        int c = memcmp ((a), (b), (size));                  \
+        if (c != 0)                                         \
+            return c < 0 ? -1 : 1;                          \
+    } G_STMT_END
+
 #define _CMP_FIELD(a, b, field)                             \
     G_STMT_START {                                          \
         if (((a)->field) != ((b)->field))                   \
@@ -2732,13 +3702,12 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
 int
 nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, type);
 	_CMP_FIELD_STR (a, b, name);
 	_CMP_FIELD (a, b, master);
 	_CMP_FIELD (a, b, parent);
-	_CMP_FIELD (a, b, vlan_id);
 	_CMP_FIELD (a, b, flags);
 	_CMP_FIELD (a, b, connected);
 	_CMP_FIELD (a, b, mtu);
@@ -2757,14 +3726,126 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 }
 
 int
+nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, input_flags);
+	_CMP_FIELD (a, b, output_flags);
+	_CMP_FIELD (a, b, input_key);
+	_CMP_FIELD (a, b, output_key);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	return 0;
+}
+
+int
+nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, p_key);
+	_CMP_FIELD_STR_INTERNED (a, b, mode);
+	return 0;
+}
+
+int
+nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD_MEMCMP (a, b, local);
+	_CMP_FIELD_MEMCMP (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tclass);
+	_CMP_FIELD (a, b, encap_limit);
+	_CMP_FIELD (a, b, flow_label);
+	_CMP_FIELD (a, b, proto);
+	return 0;
+}
+
+int
+nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	return 0;
+}
+
+int
+nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, mode);
+	_CMP_FIELD_BOOL (a, b, no_promisc);
+	return 0;
+}
+
+int
+nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	_CMP_FIELD (a, b, flags);
+	_CMP_FIELD (a, b, proto);
+	return 0;
+}
+
+int
+nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, id);
+	_CMP_FIELD (a, b, flags);
+	return 0;
+}
+
+int
+nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, id);
+	_CMP_FIELD (a, b, group);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD_MEMCMP (a, b, group6);
+	_CMP_FIELD_MEMCMP (a, b, local6);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD_BOOL (a, b, learning);
+	_CMP_FIELD (a, b, ageing);
+	_CMP_FIELD (a, b, limit);
+	_CMP_FIELD (a, b, dst_port);
+	_CMP_FIELD (a, b, src_port_min);
+	_CMP_FIELD (a, b, src_port_max);
+	_CMP_FIELD_BOOL (a, b, proxy);
+	_CMP_FIELD_BOOL (a, b, rsc);
+	_CMP_FIELD_BOOL (a, b, l2miss);
+	_CMP_FIELD_BOOL (a, b, l3miss);
+	return 0;
+}
+
+int
 nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, source);
 	_CMP_FIELD (a, b, address);
-	_CMP_FIELD (a, b, peer_address);
 	_CMP_FIELD (a, b, plen);
+	_CMP_FIELD (a, b, peer_address);
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
 	_CMP_FIELD (a, b, preferred);
@@ -2775,11 +3856,17 @@ nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4A
 int
 nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
 {
-	_CMP_POINTER (a, b);
+	const struct in6_addr *p_a, *p_b;
+
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, source);
 	_CMP_FIELD_MEMCMP (a, b, address);
-	_CMP_FIELD_MEMCMP (a, b, peer_address);
+
+	p_a = nm_platform_ip6_address_get_peer (a);
+	p_b = nm_platform_ip6_address_get_peer (b);
+	_CMP_DIRECT_MEMCMP (p_a, p_b, sizeof (*p_a));
+
 	_CMP_FIELD (a, b, plen);
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
@@ -2791,7 +3878,7 @@ nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6A
 int
 nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, source);
 	_CMP_FIELD (a, b, network);
@@ -2807,7 +3894,7 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 int
 nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, source);
 	_CMP_FIELD_MEMCMP (a, b, network);
@@ -2818,9 +3905,6 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 	return 0;
 }
 
-#undef _CMP_FIELD
-#undef _CMP_FIELD_MEMCMP
-
 /**
  * nm_platform_ip_address_cmp_expiry:
  * @a: a NMPlatformIPAddress to compare
@@ -2839,7 +3923,7 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 {
 	gint64 ta = 0, tb = 0;
 
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 
 	if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0)
 		ta = G_MAXINT64;
@@ -2872,8 +3956,6 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 	return ta < tb ? -1 : 1;
 }
 
-#undef _CMP_POINTER
-
 const char *
 nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type)
 {
@@ -2893,31 +3975,31 @@ 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));
+	_LOGD ("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));
+	_LOGD ("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));
+	_LOGD ("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));
+	_LOGD ("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));
+	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
 }
 
 /******************************************************************/
@@ -2994,7 +4076,7 @@ const NMPlatformVTableRoute nm_platform_vtable_route_v4 = {
 	.addr_family                    = AF_INET,
 	.sizeof_route                   = sizeof (NMPlatformIP4Route),
 	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b)) nm_platform_ip4_route_cmp,
-	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route)) nm_platform_ip4_route_to_string,
+	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip4_route_to_string,
 	.route_get_all                  = nm_platform_ip4_route_get_all,
 	.route_add                      = _vtr_v4_route_add,
 	.route_delete                   = _vtr_v4_route_delete,
@@ -3007,7 +4089,7 @@ const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
 	.addr_family                    = AF_INET6,
 	.sizeof_route                   = sizeof (NMPlatformIP6Route),
 	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b)) nm_platform_ip6_route_cmp,
-	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route)) nm_platform_ip6_route_to_string,
+	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip6_route_to_string,
 	.route_get_all                  = nm_platform_ip6_route_get_all,
 	.route_add                      = _vtr_v6_route_add,
 	.route_delete                   = _vtr_v6_route_delete,
@@ -3051,14 +4133,6 @@ nm_platform_init (NMPlatform *object)
 {
 }
 
-#define SIGNAL(signal_id, method) signals[signal_id] = \
-	g_signal_new_class_handler (NM_PLATFORM_ ## signal_id, \
-		G_OBJECT_CLASS_TYPE (object_class), \
-		G_SIGNAL_RUN_FIRST, \
-		G_CALLBACK (method), \
-		NULL, NULL, NULL, \
-		G_TYPE_NONE, 5, NM_TYPE_POBJECT_TYPE, G_TYPE_INT, G_TYPE_POINTER, NM_TYPE_PLATFORM_SIGNAL_CHANGE_TYPE, NM_TYPE_PLATFORM_REASON);
-
 static void
 nm_platform_class_init (NMPlatformClass *platform_class)
 {
@@ -3069,6 +4143,8 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	object_class->set_property = set_property;
 	object_class->constructed = constructed;
 
+	platform_class->wifi_set_powersave = wifi_set_powersave;
+
 	g_object_class_install_property
 	 (object_class, PROP_REGISTER_SINGLETON,
 	     g_param_spec_boolean (NM_PLATFORM_REGISTER_SINGLETON, "", "",
@@ -3077,10 +4153,21 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	                           G_PARAM_CONSTRUCT_ONLY |
 	                           G_PARAM_STATIC_STRINGS));
 
+#define SIGNAL(signal, signal_id, method) \
+	G_STMT_START { \
+		signals[signal] = \
+			g_signal_new_class_handler (""signal_id"", \
+			                            G_OBJECT_CLASS_TYPE (object_class), \
+			                            G_SIGNAL_RUN_FIRST, \
+			                            G_CALLBACK (method), \
+			                            NULL, NULL, NULL, \
+			                            G_TYPE_NONE, 4, NM_TYPE_POBJECT_TYPE, G_TYPE_INT, G_TYPE_POINTER, NM_TYPE_PLATFORM_SIGNAL_CHANGE_TYPE); \
+	} G_STMT_END
+
 	/* Signals */
-	SIGNAL (SIGNAL_LINK_CHANGED, log_link)
-	SIGNAL (SIGNAL_IP4_ADDRESS_CHANGED, log_ip4_address)
-	SIGNAL (SIGNAL_IP6_ADDRESS_CHANGED, log_ip6_address)
-	SIGNAL (SIGNAL_IP4_ROUTE_CHANGED, log_ip4_route)
-	SIGNAL (SIGNAL_IP6_ROUTE_CHANGED, log_ip6_route)
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_LINK,        NM_PLATFORM_SIGNAL_LINK_CHANGED,        log_link);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, log_ip4_address);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, log_ip6_address);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,   NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,   log_ip4_route);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,   NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,   log_ip6_route);
 }
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 3219dd5a..8ac129a0 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -21,15 +21,16 @@
 #ifndef __NETWORKMANAGER_PLATFORM_H__
 #define __NETWORKMANAGER_PLATFORM_H__
 
-#include <glib-object.h>
-#include "nm-glib-compat.h"
 #include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/if_addr.h>
+#include <linux/if_link.h>
 
-#include <nm-dbus-interface.h>
-#include "nm-types.h"
+#include "nm-dbus-interface.h"
+#include "nm-default.h"
 #include "NetworkManagerUtils.h"
+#include "nm-setting-vlan.h"
+#include "nm-core-types-internal.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -54,6 +55,18 @@ typedef struct _NMPlatform NMPlatform;
 #define IFA_F_NOPREFIXROUTE 0x200
 #endif
 
+/* Define of the IN6_ADDR_GEN_MODE_* values to workaround old kernel headers
+ * that don't define it. */
+#define NM_IN6_ADDR_GEN_MODE_UNKNOWN           255  /* no corresponding value.  */
+#define NM_IN6_ADDR_GEN_MODE_EUI64             0    /* IN6_ADDR_GEN_MODE_EUI64 */
+#define NM_IN6_ADDR_GEN_MODE_NONE              1    /* IN6_ADDR_GEN_MODE_NONE */
+#define NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY    2    /* IN6_ADDR_GEN_MODE_STABLE_PRIVACY */
+
+#define NM_IFF_MULTI_QUEUE                     0x0100 /* IFF_MULTI_QUEUE */
+
+/* Redefine this in host's endianness */
+#define NM_GRE_KEY      0x2000
+
 typedef enum { /*< skip >*/
 
 	/* dummy value, to enforce that the enum type is signed and has a size
@@ -74,18 +87,18 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_ERROR_NO_FIRMWARE,
 } NMPlatformError;
 
-typedef enum {
-	NM_PLATFORM_REASON_NONE,
-	/* Event was requested by NetworkManager. */
-	NM_PLATFORM_REASON_INTERNAL,
-	/* Event came from the kernel. */
-	NM_PLATFORM_REASON_EXTERNAL,
-	/* Event is a result of cache checking and cleanups. */
-	NM_PLATFORM_REASON_CACHE_CHECK,
-
-	/* Internal reason to suppress announcing change events */
-	_NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL,
-} NMPlatformReason;
+
+typedef struct {
+	union {
+		guint8 addr_ptr[1];
+		in_addr_t addr4;
+		struct in6_addr addr6;
+	};
+} NMIPAddr;
+
+extern const NMIPAddr nm_ip_addr_zero;
+
+#define NMIPAddrInit { .addr6 = IN6ADDR_ANY_INIT }
 
 #define NM_PLATFORM_LINK_OTHER_NETNS    (-1)
 
@@ -133,9 +146,6 @@ struct _NMPlatformLink {
 	 * initialized with memset(0) has and unset value.*/
 	guint8 inet6_addr_gen_mode_inv;
 
-	/* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */
-	guint16 vlan_id;
-
 	/* IFF_* flags as u32. Note that ifi_flags in 'struct ifinfomsg' is declared as 'unsigned',
 	 * but libnl stores the flag internally as u32.  */
 	guint32 flags;
@@ -147,6 +157,18 @@ struct _NMPlatformLink {
 	guint mtu;
 };
 
+typedef enum { /*< skip >*/
+	NM_PLATFORM_SIGNAL_ID_NONE,
+	NM_PLATFORM_SIGNAL_ID_LINK,
+	NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
+	NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
+	NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
+	NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
+	_NM_PLATFORM_SIGNAL_ID_LAST,
+} NMPlatformSignalIdType;
+
+guint _nm_platform_signal_id_get (NMPlatformSignalIdType signal_type);
+
 typedef enum {
 	NM_PLATFORM_SIGNAL_NONE,
 	NM_PLATFORM_SIGNAL_ADDED,
@@ -220,11 +242,23 @@ typedef struct {
  **/
 struct _NMPlatformIP4Address {
 	__NMPlatformIPAddress_COMMON;
+
+	/* The local address IFA_LOCAL. */
 	in_addr_t address;
+
+	/* The IFA_ADDRESS PTP peer address. This field is rather important, because
+	 * it constitutes the identifier for the IPv4 address (e.g. you can add two
+	 * addresses that only differ by their peer's network-part.
+	 *
+	 * Beware that for most cases, NetworkManager doesn't want to set an explicit
+	 * peer-address. Hoever, that corresponds to setting the peer address to @address
+	 * itself. Leaving peer-address unset/zero, means explicitly setting the peer
+	 * address to 0.0.0.0, which you probably don't want.
+	 * */
 	in_addr_t peer_address;  /* PTP peer address */
+
 	char label[IFNAMSIZ];
 };
-G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
 
 /**
  * NMPlatformIP6Address:
@@ -236,7 +270,6 @@ struct _NMPlatformIP6Address {
 	struct in6_addr peer_address;
 	guint flags; /* ifa_flags from <linux/if_addr.h>, field type "unsigned int" is as used in rtnl_addr_get_flags. */
 };
-G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
 
 typedef union {
 	NMPlatformIPAddress  ax;
@@ -291,16 +324,14 @@ struct _NMPlatformIP4Route {
 
 	/* RTA_PREFSRC/rtnl_route_get_pref_src(). A value of zero means that
 	 * no pref-src is set.  */
-	guint32 pref_src;
+	in_addr_t pref_src;
 };
-G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
 
 struct _NMPlatformIP6Route {
 	__NMPlatformIPRoute_COMMON;
 	struct in6_addr network;
 	struct in6_addr gateway;
 };
-G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
 
 typedef union {
 	NMPlatformIPRoute  rx;
@@ -319,7 +350,7 @@ typedef struct {
 	int addr_family;
 	gsize sizeof_route;
 	int (*route_cmp) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b);
-	const char *(*route_to_string) (const NMPlatformIPXRoute *route);
+	const char *(*route_to_string) (const NMPlatformIPXRoute *route, char *buf, gsize len);
 	GArray *(*route_get_all) (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags);
 	gboolean (*route_add) (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric);
 	gboolean (*route_delete) (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route);
@@ -330,26 +361,68 @@ typedef struct {
 extern const NMPlatformVTableRoute nm_platform_vtable_route_v4;
 extern const NMPlatformVTableRoute nm_platform_vtable_route_v6;
 
-extern char _nm_platform_to_string_buffer[256];
-
 typedef struct {
-	int peer;
-} NMPlatformVethProperties;
+	int parent_ifindex;
+	guint16 input_flags;
+	guint16 output_flags;
+	guint32 input_key;
+	guint32 output_key;
+	in_addr_t local;
+	in_addr_t remote;
+	guint8 ttl;
+	guint8 tos;
+	gboolean path_mtu_discovery;
+} NMPlatformLnkGre;
 
 typedef struct {
-	gint64 owner;
-	gint64 group;
+	int p_key;
 	const char *mode;
-	gboolean no_pi;
-	gboolean vnet_hdr;
-	gboolean multi_queue;
-} NMPlatformTunProperties;
+} NMPlatformLnkInfiniband;
 
 typedef struct {
 	int parent_ifindex;
-	const char *mode;
+	struct in6_addr local;
+	struct in6_addr remote;
+	guint8 ttl;
+	guint8 tclass;
+	guint8 encap_limit;
+	guint flow_label;
+	guint8 proto;
+} NMPlatformLnkIp6Tnl;
+
+typedef struct {
+	int parent_ifindex;
+	in_addr_t local;
+	in_addr_t remote;
+	guint8 ttl;
+	guint8 tos;
+	gboolean path_mtu_discovery;
+} NMPlatformLnkIpIp;
+
+typedef struct {
+	guint mode;
 	gboolean no_promisc;
-} NMPlatformMacvlanProperties;
+	gboolean tap;
+} NMPlatformLnkMacvlan;
+
+typedef NMPlatformLnkMacvlan NMPlatformLnkMacvtap;
+
+typedef struct {
+	int parent_ifindex;
+	in_addr_t local;
+	in_addr_t remote;
+	guint8 ttl;
+	guint8 tos;
+	gboolean path_mtu_discovery;
+	guint16 flags;
+	guint8 proto;
+} NMPlatformLnkSit;
+
+typedef struct {
+	/* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */
+	guint16 id;
+	NMVlanFlags flags;
+} NMPlatformLnkVlan;
 
 typedef struct {
 	int parent_ifindex;
@@ -370,51 +443,19 @@ typedef struct {
 	gboolean rsc;
 	gboolean l2miss;
 	gboolean l3miss;
-} NMPlatformVxlanProperties;
+} NMPlatformLnkVxlan;
 
 typedef struct {
-	int parent_ifindex;
-	guint16 input_flags;
-	guint16 output_flags;
-	guint32 input_key;
-	guint32 output_key;
-	in_addr_t local;
-	in_addr_t remote;
-	guint8 ttl;
-	guint8 tos;
-	gboolean path_mtu_discovery;
-} NMPlatformGreProperties;
+	gint64 owner;
+	gint64 group;
+	const char *mode;
+	gboolean no_pi;
+	gboolean vnet_hdr;
+	gboolean multi_queue;
+} NMPlatformTunProperties;
 
 /******************************************************************/
 
-/* NMPlatform abstract class and its implementations provide a layer between
- * networkmanager's device management classes and the operating system kernel.
- *
- * How it works, is best seen in tests/nm-platform-test.c source file.
- *
- * NMPlatform provides interface to configure kernel interfaces and receive
- * notifications about both internal and external configuration changes. It
- * respects the following rules:
- *
- * 1) Every change made through NMPlatform is readily available and the respective
- * signals are called synchronously.
- *
- * 2) State of an object retrieved from NMPlatform (through functions or events)
- * is at least as recent than the state retrieved before.
- *
- * Any failure of the above rules should be fixed in NMPlatform implementations
- * and tested in nm-platform-test. Synchronization hacks should never be put
- * to any other code. That's why NMPlatform was created and that's why the
- * testing code was written for it.
- *
- * In future, parts of linux platform implementation may be moved to the libnl
- * library.
- *
- * If you have any problems related to NMPlatform on your system, you should
- * always first run tests/nm-linux-platform-test as root and with all
- * network configuration daemons stopped. Look at the code first.
- */
-
 struct _NMPlatform {
 	GObject parent;
 };
@@ -429,13 +470,15 @@ typedef struct {
 	const NMPlatformLink *(*link_get_by_ifname) (NMPlatform *platform, const char *ifname);
 	const NMPlatformLink *(*link_get_by_address) (NMPlatform *platform, gconstpointer address, size_t length);
 
+	const NMPObject *(*link_get_lnk) (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link);
+
 	GArray *(*link_get_all) (NMPlatform *);
 	gboolean (*link_add) (NMPlatform *,
 	                      const char *name,
 	                      NMLinkType type,
 	                      const void *address,
 	                      size_t address_len,
-	                      NMPlatformLink *out_link);
+	                      const NMPlatformLink **out_link);
 	gboolean (*link_delete) (NMPlatform *, int ifindex);
 	const char *(*link_get_type_name) (NMPlatform *, int ifindex);
 	gboolean (*link_get_unmanaged) (NMPlatform *, int ifindex, gboolean *unmanaged);
@@ -474,28 +517,48 @@ typedef struct {
 
 	gboolean (*link_enslave) (NMPlatform *, int master, int slave);
 	gboolean (*link_release) (NMPlatform *, int master, int slave);
-	gboolean (*master_set_option) (NMPlatform *, int ifindex, const char *option, const char *value);
-	char * (*master_get_option) (NMPlatform *, int ifindex, const char *option);
-	gboolean (*slave_set_option) (NMPlatform *, int ifindex, const char *option, const char *value);
-	char * (*slave_get_option) (NMPlatform *, int ifindex, const char *option);
-
-	gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, NMPlatformLink *out_link);
-	gboolean (*vlan_get_info) (NMPlatform *, int ifindex, int *parent, int *vlan_id);
-	gboolean (*vlan_set_ingress_map) (NMPlatform *, int ifindex, int from, int to);
-	gboolean (*vlan_set_egress_map) (NMPlatform *, int ifindex, int from, int to);
-
-	gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, NMPlatformLink *out_link);
-	gboolean (*infiniband_get_info)      (NMPlatform *,
-	                                      int ifindex,
-	                                      int *parent,
-	                                      int *p_key,
-	                                      const char **mode);
-
-	gboolean (*veth_get_properties) (NMPlatform *, int ifindex, NMPlatformVethProperties *properties);
-	gboolean (*tun_get_properties) (NMPlatform *, int ifindex, NMPlatformTunProperties *properties);
-	gboolean (*macvlan_get_properties) (NMPlatform *, int ifindex, NMPlatformMacvlanProperties *props);
-	gboolean (*vxlan_get_properties) (NMPlatform *, int ifindex, NMPlatformVxlanProperties *props);
-	gboolean (*gre_get_properties) (NMPlatform *, int ifindex, NMPlatformGreProperties *props);
+
+	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,
+	                              NMVlanFlags flags_mask,
+	                              NMVlanFlags flags_set,
+	                              gboolean ingress_reset_all,
+	                              const NMVlanQosMapping *ingress_map,
+	                              gsize n_ingress_map,
+	                              gboolean egress_reset_all,
+	                              const NMVlanQosMapping *egress_map,
+	                              gsize n_egress_map);
+	gboolean (*link_vxlan_add) (NMPlatform *,
+	                            const char *name,
+	                            const NMPlatformLnkVxlan *props,
+	                            const NMPlatformLink **out_link);
+	gboolean (*link_gre_add) (NMPlatform *,
+	                          const char *name,
+	                          const NMPlatformLnkGre *props,
+	                          const NMPlatformLink **out_link);
+	gboolean (*link_ip6tnl_add) (NMPlatform *,
+	                             const char *name,
+	                             const NMPlatformLnkIp6Tnl *props,
+	                             const NMPlatformLink **out_link);
+	gboolean (*link_ipip_add) (NMPlatform *,
+	                           const char *name,
+	                           const NMPlatformLnkIpIp *props,
+	                           const NMPlatformLink **out_link);
+	gboolean (*link_macvlan_add) (NMPlatform *,
+	                              const char *name,
+	                              int parent,
+	                              const NMPlatformLnkMacvlan *props,
+	                              const NMPlatformLink **out_link);
+	gboolean (*link_sit_add) (NMPlatform *,
+	                          const char *name,
+	                          const NMPlatformLnkSit *props,
+	                          const NMPlatformLink **out_link);
+
+	gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, const NMPlatformLink **out_link);
+
+	gboolean (*tun_add) (NMPlatform *platform, const char *name, gboolean tap, gint64 owner, gint64 group, gboolean pi,
+	                     gboolean vnet_hdr, gboolean multi_queue, const NMPlatformLink **out_link);
 
 	gboolean    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
 	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
@@ -505,6 +568,7 @@ typedef struct {
 	guint32     (*wifi_get_rate)         (NMPlatform *, int ifindex);
 	NM80211Mode (*wifi_get_mode)         (NMPlatform *, int ifindex);
 	void        (*wifi_set_mode)         (NMPlatform *, int ifindex, NM80211Mode mode);
+	void        (*wifi_set_powersave)    (NMPlatform *, int ifindex, guint32 powersave);
 	guint32     (*wifi_find_frequency)   (NMPlatform *, int ifindex, const guint32 *freqs);
 	void        (*wifi_indicate_addressing_running) (NMPlatform *, int ifindex, gboolean running);
 
@@ -514,23 +578,32 @@ typedef struct {
 
 	GArray * (*ip4_address_get_all) (NMPlatform *, int ifindex);
 	GArray * (*ip6_address_get_all) (NMPlatform *, int ifindex);
-	gboolean (*ip4_address_add) (NMPlatform *, int ifindex,
-	                             in_addr_t address, in_addr_t peer_address, int plen,
-	                             guint32 lifetime, guint32 preferred_lft,
+	gboolean (*ip4_address_add) (NMPlatform *,
+	                             int ifindex,
+	                             in_addr_t address,
+	                             int plen,
+	                             in_addr_t peer_address,
+	                             guint32 lifetime,
+	                             guint32 preferred_lft,
 	                             const char *label);
-	gboolean (*ip6_address_add) (NMPlatform *, int ifindex,
-	                             struct in6_addr address, struct in6_addr peer_address, int plen,
-	                             guint32 lifetime, guint32 preferred_lft, guint flags);
+	gboolean (*ip6_address_add) (NMPlatform *,
+	                             int ifindex,
+	                             struct in6_addr address,
+	                             int plen,
+	                             struct in6_addr peer_address,
+	                             guint32 lifetime,
+	                             guint32 preferred_lft,
+	                             guint flags);
 	gboolean (*ip4_address_delete) (NMPlatform *, int ifindex, in_addr_t address, int plen, in_addr_t peer_address);
 	gboolean (*ip6_address_delete) (NMPlatform *, int ifindex, struct in6_addr address, int plen);
-	const NMPlatformIP4Address *(*ip4_address_get) (NMPlatform *, int ifindex, in_addr_t address, int plen);
+	const NMPlatformIP4Address *(*ip4_address_get) (NMPlatform *, int ifindex, in_addr_t address, int plen, in_addr_t peer_address);
 	const NMPlatformIP6Address *(*ip6_address_get) (NMPlatform *, int ifindex, struct in6_addr address, int plen);
 
 	GArray * (*ip4_route_get_all) (NMPlatform *, int ifindex, NMPlatformGetRouteFlags flags);
 	GArray * (*ip6_route_get_all) (NMPlatform *, int ifindex, NMPlatformGetRouteFlags flags);
 	gboolean (*ip4_route_add) (NMPlatform *, int ifindex, NMIPConfigSource source,
 	                           in_addr_t network, int plen, in_addr_t gateway,
-	                           guint32 pref_src, guint32 metric, guint32 mss);
+	                           in_addr_t pref_src, guint32 metric, guint32 mss);
 	gboolean (*ip6_route_add) (NMPlatform *, int ifindex, NMIPConfigSource source,
 	                           struct in6_addr network, int plen, struct in6_addr gateway,
 	                           guint32 metric, guint32 mss);
@@ -607,10 +680,10 @@ const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const ch
 const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, gconstpointer address, size_t length);
 
 GArray *nm_platform_link_get_all (NMPlatform *self);
-NMPlatformError nm_platform_dummy_add (NMPlatform *self, const char *name, NMPlatformLink *out_link);
-NMPlatformError nm_platform_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, NMPlatformLink *out_link);
-NMPlatformError nm_platform_bond_add (NMPlatform *self, const char *name, NMPlatformLink *out_link);
-NMPlatformError nm_platform_team_add (NMPlatform *self, const char *name, NMPlatformLink *out_link);
+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);
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
 /* convienience methods to lookup the link and access fields of NMPlatformLink. */
@@ -663,33 +736,77 @@ gboolean nm_platform_link_supports_vlans (NMPlatform *self, int ifindex);
 
 gboolean nm_platform_link_enslave (NMPlatform *self, int master, int slave);
 gboolean nm_platform_link_release (NMPlatform *self, int master, int slave);
-gboolean nm_platform_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value);
-char *nm_platform_master_get_option (NMPlatform *self, int ifindex, const char *option);
-gboolean nm_platform_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value);
-char *nm_platform_slave_get_option (NMPlatform *self, int ifindex, const char *option);
-
-NMPlatformError nm_platform_vlan_add (NMPlatform *self, const char *name, int parent, int vlanid, guint32 vlanflags, NMPlatformLink *out_link);
-gboolean nm_platform_vlan_get_info (NMPlatform *self, int ifindex, int *parent, int *vlanid);
-gboolean nm_platform_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to);
-gboolean nm_platform_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to);
 
-NMPlatformError nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, NMPlatformLink *out_link);
-gboolean nm_platform_infiniband_get_info (NMPlatform *self, int ifindex, int *parent, int *p_key, const char **mode);
-
-gboolean nm_platform_veth_get_properties        (NMPlatform *self, int ifindex, NMPlatformVethProperties *properties);
-gboolean nm_platform_tun_get_properties         (NMPlatform *self, int ifindex, NMPlatformTunProperties *properties);
-gboolean nm_platform_macvlan_get_properties     (NMPlatform *self, int ifindex, NMPlatformMacvlanProperties *props);
-gboolean nm_platform_vxlan_get_properties       (NMPlatform *self, int ifindex, NMPlatformVxlanProperties *props);
-gboolean nm_platform_gre_get_properties         (NMPlatform *self, int ifindex, NMPlatformGreProperties *props);
+gboolean nm_platform_sysctl_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value);
+char *nm_platform_sysctl_master_get_option (NMPlatform *self, int ifindex, const char *option);
+gboolean nm_platform_sysctl_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value);
+char *nm_platform_sysctl_slave_get_option (NMPlatform *self, int ifindex, const char *option);
+
+const NMPObject *nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link);
+const NMPlatformLnkGre *nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkInfiniband *nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkMacvtap *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+
+NMPlatformError 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,
+                                       int ifindex,
+                                       NMVlanFlags flags_mask,
+                                       NMVlanFlags flags_set,
+                                       gboolean ingress_reset_all,
+                                       const NMVlanQosMapping *ingress_map,
+                                       gsize n_ingress_map,
+                                       gboolean egress_reset_all,
+                                       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_tun_add (NMPlatform *self,
+                                          const char *name,
+                                          gboolean tap,
+                                          gint64 owner,
+                                          gint64 group,
+                                          gboolean pi,
+                                          gboolean vnet_hdr,
+                                          gboolean multi_queue,
+                                          const NMPlatformLink **out_link);
+
+NMPlatformError nm_platform_link_infiniband_add (NMPlatform *self,
+                                                 int parent,
+                                                 int p_key,
+                                                 const NMPlatformLink **out_link);
+gboolean nm_platform_link_infiniband_get_properties (NMPlatform *self, int ifindex, int *parent, int *p_key, const char **mode);
+
+gboolean nm_platform_link_veth_get_properties   (NMPlatform *self, int ifindex, int *out_peer_ifindex);
+gboolean nm_platform_link_tun_get_properties    (NMPlatform *self, int ifindex, NMPlatformTunProperties *properties);
+
+gboolean nm_platform_link_tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props);
 
 gboolean    nm_platform_wifi_get_capabilities (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
 gboolean    nm_platform_wifi_get_bssid        (NMPlatform *self, int ifindex, guint8 *bssid);
-GByteArray *nm_platform_wifi_get_ssid         (NMPlatform *self, int ifindex);
 guint32     nm_platform_wifi_get_frequency    (NMPlatform *self, int ifindex);
 int         nm_platform_wifi_get_quality      (NMPlatform *self, int ifindex);
 guint32     nm_platform_wifi_get_rate         (NMPlatform *self, int ifindex);
 NM80211Mode nm_platform_wifi_get_mode         (NMPlatform *self, int ifindex);
 void        nm_platform_wifi_set_mode         (NMPlatform *self, int ifindex, NM80211Mode mode);
+void        nm_platform_wifi_set_powersave    (NMPlatform *self, int ifindex, guint32 powersave);
 guint32     nm_platform_wifi_find_frequency   (NMPlatform *self, int ifindex, const guint32 *freqs);
 void        nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gboolean running);
 
@@ -697,17 +814,52 @@ guint32     nm_platform_mesh_get_channel      (NMPlatform *self, int ifindex);
 gboolean    nm_platform_mesh_set_channel      (NMPlatform *self, int ifindex, guint32 channel);
 gboolean    nm_platform_mesh_set_ssid         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
 
-const NMPlatformIP4Address *nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, int plen);
+void                   nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, int 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, int 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_ipip_add (NMPlatform *self,
+                                           const char *name,
+                                           const NMPlatformLnkIpIp *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);
+
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address, int plen);
 GArray *nm_platform_ip4_address_get_all (NMPlatform *self, int ifindex);
 GArray *nm_platform_ip6_address_get_all (NMPlatform *self, int ifindex);
-gboolean nm_platform_ip4_address_add (NMPlatform *self, int ifindex,
-                                      in_addr_t address, in_addr_t peer_address, int plen,
-                                      guint32 lifetime, guint32 preferred_lft,
+gboolean nm_platform_ip4_address_add (NMPlatform *self,
+                                      int ifindex,
+                                      in_addr_t address,
+                                      int plen,
+                                      in_addr_t peer_address,
+                                      guint32 lifetime,
+                                      guint32 preferred_lft,
                                       const char *label);
-gboolean nm_platform_ip6_address_add (NMPlatform *self, int ifindex,
-                                      struct in6_addr address, struct in6_addr peer_address, int plen,
-                                      guint32 lifetime, guint32 preferred_lft, guint flags);
+gboolean nm_platform_ip6_address_add (NMPlatform *self,
+                                      int ifindex,
+                                      struct in6_addr address,
+                                      int plen,
+                                      struct in6_addr peer_address,
+                                      guint32 lifetime,
+                                      guint32 preferred_lft,
+                                      guint flags);
 gboolean nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, int plen, in_addr_t peer_address);
 gboolean nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, int plen);
 gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, GPtrArray **out_added_addresses);
@@ -720,31 +872,54 @@ GArray *nm_platform_ip4_route_get_all (NMPlatform *self, int ifindex, NMPlatform
 GArray *nm_platform_ip6_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags);
 gboolean nm_platform_ip4_route_add (NMPlatform *self, int ifindex, NMIPConfigSource source,
                                     in_addr_t network, int plen, in_addr_t gateway,
-                                    guint32 pref_src, guint32 metric, guint32 mss);
+                                    in_addr_t pref_src, guint32 metric, guint32 mss);
 gboolean nm_platform_ip6_route_add (NMPlatform *self, int ifindex, NMIPConfigSource source,
                                     struct in6_addr network, int plen, struct in6_addr gateway,
                                     guint32 metric, guint32 mss);
 gboolean nm_platform_ip4_route_delete (NMPlatform *self, int ifindex, in_addr_t network, int plen, guint32 metric);
 gboolean nm_platform_ip6_route_delete (NMPlatform *self, int ifindex, struct in6_addr network, int plen, guint32 metric);
 
-const char *nm_platform_link_to_string (const NMPlatformLink *link);
-const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address);
-const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address);
-const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route);
-const char *nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route);
+const char *nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len);
+const char *nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_infiniband_to_string (const NMPlatformLnkInfiniband *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
+const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
+const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len);
+const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len);
+const char *nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len);
+
+const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
+                                                    const NMVlanQosMapping *map,
+                                                    gsize n_map,
+                                                    char *buf,
+                                                    gsize len);
 
 int nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b);
+int nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b);
+int nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b);
+int nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b);
+int nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b);
+int nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b);
+int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b);
+int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
+int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
 int nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b);
 int nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b);
 int nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b);
 
-gboolean nm_platform_check_support_libnl_extended_ifa_flags (void);
-gboolean nm_platform_check_support_libnl_link_netnsid (void);
 gboolean nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self);
 gboolean nm_platform_check_support_user_ipv6ll (NMPlatform *self);
 
-void nm_platform_addr_flags2str (int flags, char *buf, size_t size);
+const char *nm_platform_link_flags2str (unsigned flags, char *buf, gsize len);
+const char *nm_platform_link_inet6_addrgenmode2str (guint8 mode, char *buf, gsize len);
+const char *nm_platform_addr_flags2str (unsigned flags, char *buf, gsize len);
+const char *nm_platform_route_scope2str (int scope, char *buf, gsize len);
 
 int nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatformIPAddress *b);
 
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index eb6bfc3a..892fd9dd 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -22,11 +22,11 @@
 
 #include <unistd.h>
 
+#include "nm-default.h"
 #include "nmp-object.h"
 #include "nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
-#include "nm-logging.h"
 
 /*********************************************************************************************/
 
@@ -82,6 +82,42 @@ _id_hash_ip6_addr (const struct in6_addr *addr)
 	return hash;
 }
 
+static int
+_vlan_xgress_qos_mappings_cmp (guint n_map,
+                               const NMVlanQosMapping *map1,
+                               const NMVlanQosMapping *map2)
+{
+	guint i;
+
+	for (i = 0; i < n_map; i++) {
+		if (map1[i].from != map2[i].from)
+			return map1[i].from < map2[i].from ? -1 : 1;
+		if (map1[i].to != map2[i].to)
+			return map1[i].to < map2[i].to ? -1 : 1;
+	}
+	return 0;
+}
+
+static void
+_vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
+                               const NMVlanQosMapping **dst_map,
+                               guint src_n_map,
+                               const NMVlanQosMapping *src_map)
+{
+	if (src_n_map == 0) {
+		g_clear_pointer (dst_map, g_free);
+		*dst_n_map = 0;
+	} else if (   src_n_map != *dst_n_map
+	           || _vlan_xgress_qos_mappings_cmp (src_n_map, *dst_map, src_map) != 0) {
+		g_clear_pointer (dst_map, g_free);
+		*dst_n_map = src_n_map;
+		if (src_n_map > 0)
+			*dst_map = g_memdup (src_map, sizeof (*src_map) * src_n_map);
+	}
+}
+
+/******************************************************************/
+
 static const char *
 _link_get_driver (GUdevDevice *udev_device, const char *kind, const char *ifname)
 {
@@ -207,6 +243,14 @@ static void
 _vt_cmd_obj_dispose_link (NMPObject *obj)
 {
 	g_clear_object (&obj->_link.udev.device);
+	nmp_object_unref (obj->_link.netlink.lnk);
+}
+
+static void
+_vt_cmd_obj_dispose_lnk_vlan (NMPObject *obj)
+{
+	g_free ((gpointer) obj->_lnk_vlan.ingress_qos_map);
+	g_free ((gpointer) obj->_lnk_vlan.egress_qos_map);
 }
 
 static NMPObject *
@@ -274,10 +318,16 @@ nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformOb
 const NMPObject *
 nmp_object_stackinit_id  (NMPObject *obj, const NMPObject *src)
 {
+	const NMPClass *klass;
+
 	nm_assert (NMP_OBJECT_IS_VALID (src));
 	nm_assert (obj);
 
-	NMP_OBJECT_GET_CLASS (src)->cmd_obj_stackinit_id (obj, src);
+	klass = NMP_OBJECT_GET_CLASS (src);
+	if (!klass->cmd_obj_stackinit_id)
+		nmp_object_stackinit (obj, klass->obj_type, NULL);
+	else
+		klass->cmd_obj_stackinit_id (obj, src);
 	return obj;
 }
 
@@ -296,19 +346,20 @@ _vt_cmd_obj_stackinit_id_link (NMPObject *obj, const NMPObject *src)
 }
 
 const NMPObject *
-nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen)
+nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen, guint32 peer_address)
 {
 	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP4_ADDRESS, NULL);
 	obj->ip4_address.ifindex = ifindex;
 	obj->ip4_address.address = address;
 	obj->ip4_address.plen = plen;
+	obj->ip4_address.peer_address = peer_address;
 	return obj;
 }
 
 static void
 _vt_cmd_obj_stackinit_id_ip4_address (NMPObject *obj, const NMPObject *src)
 {
-	nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen);
+	nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen, src->ip4_address.peer_address);
 }
 
 const NMPObject *
@@ -369,50 +420,131 @@ const char *
 nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
 {
 	const NMPClass *klass;
-	char buf2[sizeof (_nm_platform_to_string_buffer)];
-	char buf3[sizeof (_nm_platform_to_string_buffer)];
-	const char *str;
-
-	if (!buf) {
-		buf = _nm_platform_to_string_buffer;
-		buf_size = sizeof (_nm_platform_to_string_buffer);
-	}
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
 
-	if (!obj) {
-		g_strlcpy (buf, "NULL", buf_size);
+	if (!nm_utils_to_string_buffer_init_null (obj, &buf, &buf_size))
 		return buf;
-	}
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
 
 	klass = NMP_OBJECT_GET_CLASS (obj);
 
+	if (klass->cmd_obj_to_string)
+		return klass->cmd_obj_to_string (obj, to_string_mode, buf, buf_size);
+
 	switch (to_string_mode) {
 	case NMP_OBJECT_TO_STRING_ID:
+		if (!klass->cmd_plobj_to_string_id) {
+			g_snprintf (buf, buf_size, "%p", obj);
+			return buf;
+		}
 		return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
 	case NMP_OBJECT_TO_STRING_ALL:
-		g_strlcpy (buf2, NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object), sizeof (buf2));
+		g_snprintf (buf, buf_size,
+		            "[%s,%p,%d,%ccache,%calive,%cvisible; %s]",
+		            klass->obj_type_name, obj, obj->_ref_count,
+		            obj->is_cached ? '+' : '-',
+		            nmp_object_is_alive (obj) ? '+' : '-',
+		            nmp_object_is_visible (obj) ? '+' : '-',
+		            NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2)));
+		return buf;
+	case NMP_OBJECT_TO_STRING_PUBLIC:
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
 
-		if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
-			g_snprintf (buf3, sizeof (buf3),
-			            ",%cin-nl,%p",
-			            obj->_link.netlink.is_in_netlink ? '+' : '-',
-			            obj->_link.udev.device);
+static const char *
+_vt_cmd_obj_to_string_link (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
+	char buf3[sizeof (_nm_utils_to_string_buffer)];
+
+	switch (to_string_mode) {
+	case NMP_OBJECT_TO_STRING_ID:
+		return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
+	case NMP_OBJECT_TO_STRING_ALL:
+		g_snprintf (buf, buf_size,
+		            "[%s,%p,%d,%ccache,%calive,%cvisible,%cin-nl,%p; %s]",
+		            klass->obj_type_name, obj, obj->_ref_count,
+		            obj->is_cached ? '+' : '-',
+		            nmp_object_is_alive (obj) ? '+' : '-',
+		            nmp_object_is_visible (obj) ? '+' : '-',
+		            obj->_link.netlink.is_in_netlink ? '+' : '-',
+		            obj->_link.udev.device,
+		            nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)));
+		return buf;
+	case NMP_OBJECT_TO_STRING_PUBLIC:
+		if (obj->_link.netlink.lnk) {
+			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2));
+			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, buf3, sizeof (buf3));
+			g_snprintf (buf, buf_size,
+			            "%s; %s",
+			            buf2, buf3);
 		} else
-			buf3[0] = '\0';
+			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
+
+static const char *
+_vt_cmd_obj_to_string_lnk_vlan (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
+	char *b;
+	gsize l;
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	switch (to_string_mode) {
+	case NMP_OBJECT_TO_STRING_ID:
+		g_snprintf (buf, buf_size, "%p", obj);
+		return buf;
+	case NMP_OBJECT_TO_STRING_ALL:
 
 		g_snprintf (buf, buf_size,
-		            "[%s,%p,%d,%ccache,%calive,%cvisible%s; %s]",
+		            "[%s,%p,%d,%ccache,%calive,%cvisible; %s]",
 		            klass->obj_type_name, obj, obj->_ref_count,
 		            obj->is_cached ? '+' : '-',
 		            nmp_object_is_alive (obj) ? '+' : '-',
 		            nmp_object_is_visible (obj) ? '+' : '-',
-		            buf3, buf2);
+		            nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)));
 		return buf;
 	case NMP_OBJECT_TO_STRING_PUBLIC:
-		str = NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object);
-		if (str != buf)
-			g_strlcpy (buf, str, buf_size);
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+
+		b = buf;
+		l = strlen (b);
+		b += l;
+		buf_size -= l;
+
+		if (obj->_lnk_vlan.n_ingress_qos_map) {
+			nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map",
+			                                        obj->_lnk_vlan.ingress_qos_map,
+			                                        obj->_lnk_vlan.n_ingress_qos_map,
+			                                        b,
+			                                        buf_size);
+			l = strlen (b);
+			b += l;
+			buf_size -= l;
+		}
+		if (obj->_lnk_vlan.n_egress_qos_map) {
+			nm_platform_vlan_qos_mapping_to_string (" egress-qos-map",
+			                                        obj->_lnk_vlan.egress_qos_map,
+			                                        obj->_lnk_vlan.n_egress_qos_map,
+			                                        b,
+			                                        buf_size);
+			l = strlen (b);
+			b += l;
+			buf_size -= l;
+		}
+
 		return buf;
 	default:
 		g_return_val_if_reached ("ERROR");
@@ -425,21 +557,27 @@ _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsiz
 { \
 	plat_type *const obj = (plat_type *) _obj; \
 	char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
+	char buf2[NM_UTILS_INET_ADDRSTRLEN]; \
 	\
 	(void) buf1; \
+	(void) buf2; \
 	g_snprintf (buf, buf_len, \
 	            __VA_ARGS__); \
 	return buf; \
 }
-_vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",           obj->ifindex);
-_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d",    obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen);
-_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s/%d",    obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1), obj->plen);
-_vt_cmd_plobj_to_string_id (ip4_route,   NMPlatformIP4Route,   "%d: %s/%d %d", obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric);
-_vt_cmd_plobj_to_string_id (ip6_route,   NMPlatformIP6Route,   "%d: %s/%d %d", obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric);
+_vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",            obj->ifindex);
+_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
+                                                               obj->peer_address != obj->address ? "," : "",
+                                                               obj->peer_address != obj->address ? nm_utils_inet4_ntop (obj->peer_address & nm_utils_ip4_prefix_to_netmask (obj->plen), buf2) : "");
+_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1));
+_vt_cmd_plobj_to_string_id (ip4_route,   NMPlatformIP4Route,   "%d: %s/%d %d",  obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric);
+_vt_cmd_plobj_to_string_id (ip6_route,   NMPlatformIP6Route,   "%d: %s/%d %d",  obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric);
 
 int
 nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 {
+	const NMPClass *klass1, *klass2;
+
 	if (obj1 == obj2)
 		return 0;
 	if (!obj1)
@@ -450,49 +588,72 @@ nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), -1);
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), 1);
 
-	if (NMP_OBJECT_GET_CLASS (obj1) != NMP_OBJECT_GET_CLASS (obj2))
-		return NMP_OBJECT_GET_CLASS (obj1) < NMP_OBJECT_GET_CLASS (obj2) ? -1 : 1;
+	klass1 = NMP_OBJECT_GET_CLASS (obj1);
+	klass2 = NMP_OBJECT_GET_CLASS (obj2);
+
+	if (klass1 != klass2)
+		return klass1->obj_type < klass2->obj_type ? -1 : 1;
 
-	return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object);
+	if (klass1->cmd_obj_cmp)
+		return klass1->cmd_obj_cmp (obj1, obj2);
+	return klass1->cmd_plobj_cmp (&obj1->object, &obj2->object);
 }
 
-gboolean
-nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
+static int
+_vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2)
 {
-	const NMPClass *klass;
+	int i;
 
-	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
-	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
+	i = nm_platform_link_cmp (&obj1->link, &obj2->link);
+	if (i)
+		return i;
+	if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
+		return obj1->_link.netlink.is_in_netlink ? -1 : 1;
+	i = nmp_object_cmp (obj1->_link.netlink.lnk, obj2->_link.netlink.lnk);
+	if (i)
+		return i;
+	if (obj1->_link.udev.device != obj2->_link.udev.device) {
+		if (!obj1->_link.udev.device)
+			return -1;
+		if (!obj2->_link.udev.device)
+			return 1;
+
+		/* Only compare based on pointer values. That is ugly because it's not a
+		 * stable sort order, but probably udev gives us always the same GUdevDevice
+		 * instance.
+		 *
+		 * Have this check as very last. */
+		return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1;
+	}
+	return 0;
+}
 
-	if (obj1 == obj2)
-		return TRUE;
+static int
+_vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
+{
+	int c;
 
-	klass = NMP_OBJECT_GET_CLASS (obj1);
+	c = nm_platform_lnk_vlan_cmp (&obj1->lnk_vlan, &obj2->lnk_vlan);
+	if (c)
+		return c;
 
-	if (klass != NMP_OBJECT_GET_CLASS (obj2))
-		return FALSE;
+	if (obj1->_lnk_vlan.n_ingress_qos_map != obj2->_lnk_vlan.n_ingress_qos_map)
+		return obj1->_lnk_vlan.n_ingress_qos_map < obj2->_lnk_vlan.n_ingress_qos_map ? -1 : 1;
+	if (obj1->_lnk_vlan.n_egress_qos_map != obj2->_lnk_vlan.n_egress_qos_map)
+		return obj1->_lnk_vlan.n_egress_qos_map < obj2->_lnk_vlan.n_egress_qos_map ? -1 : 1;
 
-	return klass->cmd_obj_equal (obj1, obj2);
-}
+	c = _vlan_xgress_qos_mappings_cmp (obj1->_lnk_vlan.n_ingress_qos_map, obj1->_lnk_vlan.ingress_qos_map, obj2->_lnk_vlan.ingress_qos_map);
+	if (c)
+		return c;
+	c = _vlan_xgress_qos_mappings_cmp (obj1->_lnk_vlan.n_egress_qos_map, obj1->_lnk_vlan.egress_qos_map, obj2->_lnk_vlan.egress_qos_map);
 
-static gboolean
-_vt_cmd_obj_equal_plain (const NMPObject *obj1, const NMPObject *obj2)
-{
-	return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object) == 0;
+	return c;
 }
 
-static gboolean
-_vt_cmd_obj_equal_link (const NMPObject *obj1, const NMPObject *obj2)
+gboolean
+nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
 {
-	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj1);
-
-	if (klass->cmd_plobj_cmp (&obj1->object, &obj2->object) != 0)
-		return FALSE;
-	if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
-		return FALSE;
-	if (obj1->_link.udev.device != obj2->_link.udev.device)
-		return FALSE;
-	return TRUE;
+	return nmp_object_cmp (obj1, obj2) == 0;
 }
 
 /* @src is a const object, which is not entirely correct for link types, where
@@ -511,11 +672,46 @@ nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only)
 
 		g_return_if_fail (klass == NMP_OBJECT_GET_CLASS (src));
 
-		if (id_only)
-			klass->cmd_plobj_id_copy (&dst->object, &src->object);
-		else
+		if (id_only) {
+			if (klass->cmd_plobj_id_copy)
+				klass->cmd_plobj_id_copy (&dst->object, &src->object);
+		} else if (klass->cmd_obj_copy)
 			klass->cmd_obj_copy (dst, src);
+		else
+			memcpy (&dst->object, &src->object, klass->sizeof_data);
+	}
+}
+
+static void
+_vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
+{
+	if (dst->_link.udev.device != src->_link.udev.device) {
+		if (src->_link.udev.device)
+			g_object_ref (src->_link.udev.device);
+		if (dst->_link.udev.device)
+			g_object_unref (dst->_link.udev.device);
 	}
+	if (dst->_link.netlink.lnk != src->_link.netlink.lnk) {
+		if (src->_link.netlink.lnk)
+			nmp_object_ref (src->_link.netlink.lnk);
+		if (dst->_link.netlink.lnk)
+			nmp_object_unref (dst->_link.netlink.lnk);
+	}
+	dst->_link = src->_link;
+}
+
+static void
+_vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
+{
+	dst->lnk_vlan = src->lnk_vlan;
+	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_ingress_qos_map,
+	                               &dst->_lnk_vlan.ingress_qos_map,
+	                               src->_lnk_vlan.n_ingress_qos_map,
+	                               src->_lnk_vlan.ingress_qos_map);
+	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_egress_qos_map,
+	                               &dst->_lnk_vlan.egress_qos_map,
+	                               src->_lnk_vlan.n_egress_qos_map,
+	                               src->_lnk_vlan.egress_qos_map);
 }
 
 #define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
@@ -533,10 +729,10 @@ _vt_cmd_plobj_id_copy (ip4_address, NMPlatformIP4Address, {
 	dst->ifindex = src->ifindex;
 	dst->plen = src->plen;
 	dst->address = src->address;
+	dst->peer_address = src->peer_address;
 });
 _vt_cmd_plobj_id_copy (ip6_address, NMPlatformIP6Address, {
 	dst->ifindex = src->ifindex;
-	dst->plen = src->plen;
 	dst->address = src->address;
 });
 _vt_cmd_plobj_id_copy (ip4_route, NMPlatformIP4Route, {
@@ -552,24 +748,6 @@ _vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, {
 	dst->network = src->network;
 });
 
-static void
-_vt_cmd_obj_copy_plain (NMPObject *dst, const NMPObject *src)
-{
-	memcpy (&dst->object, &src->object, NMP_OBJECT_GET_CLASS (dst)->sizeof_data);
-}
-
-static void
-_vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
-{
-	if (dst->_link.udev.device != src->_link.udev.device) {
-		if (dst->_link.udev.device)
-			g_object_unref (dst->_link.udev.device);
-		if (src->_link.udev.device)
-			g_object_ref (src->_link.udev.device);
-	}
-	dst->_link = src->_link;
-}
-
 /* Uses internally nmp_object_copy(), hence it also violates the const
  * promise for @obj.
  * */
@@ -603,6 +781,7 @@ nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2)
 
 	klass = NMP_OBJECT_GET_CLASS (obj1);
 	return    klass == NMP_OBJECT_GET_CLASS (obj2)
+	       && klass->cmd_plobj_id_equal
 	       && klass->cmd_plobj_id_equal (&obj1->object, &obj2->object);
 }
 
@@ -619,10 +798,13 @@ _vt_cmd_plobj_id_equal (link, NMPlatformLink,
 _vt_cmd_plobj_id_equal (ip4_address, NMPlatformIP4Address,
                            obj1->ifindex == obj2->ifindex
                         && obj1->plen == obj2->plen
-                        && obj1->address == obj2->address);
+                        && obj1->address == obj2->address
+                        /* for IPv4 addresses, you can add the same local address with differing peer-adddress
+                         * (IFA_ADDRESS), provided that their net-part differs. */
+                        && ((obj1->peer_address ^ obj2->peer_address) & nm_utils_ip4_prefix_to_netmask (obj1->plen)) == 0);
 _vt_cmd_plobj_id_equal (ip6_address, NMPlatformIP6Address,
                            obj1->ifindex == obj2->ifindex
-                        && obj1->plen == obj2->plen
+                        /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
                         && IN6_ARE_ADDR_EQUAL (&obj1->address, &obj2->address));
 _vt_cmd_plobj_id_equal (ip4_route, NMPlatformIP4Route,
                            obj1->ifindex == obj2->ifindex
@@ -638,11 +820,20 @@ _vt_cmd_plobj_id_equal (ip6_route, NMPlatformIP6Route,
 guint
 nmp_object_id_hash (const NMPObject *obj)
 {
+	const NMPClass *klass;
+
 	if (!obj)
 		return 0;
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), 0);
-	return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_id_hash (&obj->object);
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	if (klass->cmd_plobj_id_hash)
+		return klass->cmd_plobj_id_hash (&obj->object);
+
+	/* unhashable objects implement pointer equality. */
+	return g_direct_hash (obj);
 }
 
 #define _vt_cmd_plobj_id_hash(type, plat_type, cmd) \
@@ -655,34 +846,25 @@ _vt_cmd_plobj_id_hash_##type (const NMPlatformObject *_obj) \
 	return hash; \
 }
 _vt_cmd_plobj_id_hash (link, NMPlatformLink, {
-	/* libnl considers:
-	 *   .oo_id_attrs = LINK_ATTR_IFINDEX | LINK_ATTR_FAMILY,
-	 */
 	hash = (guint) 3982791431u;
 	hash = hash      + ((guint) obj->ifindex);
 })
 _vt_cmd_plobj_id_hash (ip4_address, NMPlatformIP4Address, {
-	/* libnl considers:
-	 *   .oo_id_attrs = (ADDR_ATTR_FAMILY | ADDR_ATTR_IFINDEX |
-	 *                   ADDR_ATTR_LOCAL | ADDR_ATTR_PREFIXLEN),
-	 */
 	hash = (guint) 3591309853u;
 	hash = hash      + ((guint) obj->ifindex);
 	hash = hash * 33 + ((guint) obj->plen);
 	hash = hash * 33 + ((guint) obj->address);
+
+	/* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */
+	hash = hash * 33 + ((guint) (obj->peer_address & nm_utils_ip4_prefix_to_netmask (obj->plen)));
 })
 _vt_cmd_plobj_id_hash (ip6_address, NMPlatformIP6Address, {
 	hash = (guint) 2907861637u;
 	hash = hash      + ((guint) obj->ifindex);
-	hash = hash * 33 + ((guint) obj->plen);
+	/* for IPv6 addresses, the prefix length is not part of the primary identifier. */
 	hash = hash * 33 + _id_hash_ip6_addr (&obj->address);
 })
 _vt_cmd_plobj_id_hash (ip4_route, NMPlatformIP4Route, {
-	/* libnl considers:
-	 *   .oo_id_attrs = (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
-	 *                   ROUTE_ATTR_TABLE | ROUTE_ATTR_DST |
-	 *                   ROUTE_ATTR_PRIO),
-	 */
 	hash = (guint) 2569857221u;
 	hash = hash      + ((guint) obj->ifindex);
 	hash = hash * 33 + ((guint) obj->plen);
@@ -700,11 +882,15 @@ _vt_cmd_plobj_id_hash (ip6_route, NMPlatformIP6Route, {
 gboolean
 nmp_object_is_alive (const NMPObject *obj)
 {
+	const NMPClass *klass;
+
 	/* for convenience, allow NULL. */
 	if (!obj)
 		return FALSE;
 
-	return  NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_alive (obj);
+	klass = NMP_OBJECT_GET_CLASS (obj);
+	return    !klass->cmd_obj_is_alive
+	       || klass->cmd_obj_is_alive (obj);
 }
 
 static gboolean
@@ -742,75 +928,29 @@ _vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
 
 gboolean
 nmp_object_is_visible (const NMPObject *obj)
-
 {
+	const NMPClass *klass;
+
 	/* for convenience, allow NULL. */
 	if (!obj)
 		return FALSE;
 
-	return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_visible (obj);
-}
+	klass = NMP_OBJECT_GET_CLASS (obj);
 
-static gboolean
-_vt_cmd_obj_is_visible_link (const NMPObject *obj)
-{
-	return    obj->object.ifindex > 0
-	       && obj->_link.netlink.is_in_netlink
-	       && obj->link.name[0];
-}
+	/* a dead object is never visible. */
+	if (   klass->cmd_obj_is_alive
+	    && !klass->cmd_obj_is_alive (obj))
+		return FALSE;
 
-static gboolean
-_vt_cmd_obj_is_visible_ipx_address (const NMPObject *obj)
-{
-	return obj->object.ifindex > 0;
+	return    !klass->cmd_obj_is_visible
+	       || klass->cmd_obj_is_visible (obj);
 }
 
 static gboolean
-_vt_cmd_obj_is_visible_ipx_route (const NMPObject *obj)
-{
-	NMIPConfigSource source = obj->ip_route.source;
-
-	return obj->object.ifindex > 0 && source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
-}
-
-/******************************************************************/
-
-/**
- * nmp_object_from_nl:
- * @platform: platform instance (needed to lookup sysctl)
- * @nlo:
- * @id_only: if %TRUE, only fill the id fields of the object and leave the
- *   other fields unset. This is useful to create a needle to lookup a matching
- *   item in the cache.
- * @complete_from_cache: sometimes the netlink object doesn't contain all information.
- *   If true, look them up in the cache and preserve the original value.
- *
- * Convert a libnl object to a platform object.
- * Returns: a NMPObject containing @nlo. If @id_only is %TRUE, only the id fields
- *   are defined.
- **/
-NMPObject *
-nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache)
-{
-	NMPObjectType obj_type = _nlo_get_object_type (nlo);
-	NMPObject *obj;
-
-	if (obj_type == NMP_OBJECT_TYPE_UNKNOWN)
-		return NULL;
-
-	obj = nmp_object_new (obj_type, NULL);
-
-	if (!NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_init_from_nl (platform, &obj->object, nlo, id_only, complete_from_cache)) {
-		nmp_object_unref (obj);
-		return NULL;
-	}
-	return obj;
-}
-
-struct nl_object *
-nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only)
+_vt_cmd_obj_is_visible_link (const NMPObject *obj)
 {
-	return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_nl (platform, &obj->object, id_only);
+	return    obj->_link.netlink.is_in_netlink
+	       && obj->link.name[0];
 }
 
 /******************************************************************/
@@ -922,6 +1062,24 @@ nmp_cache_id_init_routes_visible (NMPCacheId *id,
 	return id;
 }
 
+NMPCacheId *
+nmp_cache_id_init_link_by_ifname (NMPCacheId *id,
+                                  const char *ifname)
+{
+	gsize l;
+
+	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_LINK_BY_IFNAME);
+
+	if (   !ifname
+	    || (l = strlen (ifname)) > sizeof (id->link_by_ifname.ifname_short))
+		g_return_val_if_reached (id);
+
+	/* the trailing NUL is dropped!! */
+	memcpy (id->link_by_ifname.ifname_short, ifname, l);
+
+	return id;
+}
+
 /******************************************************************/
 
 static gboolean
@@ -940,14 +1098,26 @@ _nmp_object_init_cache_id (const NMPObject *obj, NMPCacheIdType id_type, NMPCach
 			*out_id = NULL;
 		return TRUE;
 	default:
-		return klass->cmd_obj_init_cache_id (obj, id_type, id, out_id);
+		return    klass->cmd_obj_init_cache_id
+		       && klass->cmd_obj_init_cache_id (obj, id_type, id, out_id);
 	}
 }
 
 static gboolean
 _vt_cmd_obj_init_cache_id_link (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
 {
-	return FALSE;
+	switch (id_type) {
+	case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME:
+		if (obj->link.name[0]) {
+			*out_id = nmp_cache_id_init_link_by_ifname (id, obj->link.name);
+			return TRUE;
+		}
+		break;
+	default:
+		return FALSE;
+	}
+	*out_id = NULL;
+	return TRUE;
 }
 
 static gboolean
@@ -955,7 +1125,7 @@ _vt_cmd_obj_init_cache_id_ipx_address (const NMPObject *obj, NMPCacheIdType id_t
 {
 	switch (id_type) {
 	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
-		if (_vt_cmd_obj_is_visible_ipx_address (obj)) {
+		if (nmp_object_is_visible (obj)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_addrroute_visible_by_ifindex (id, NMP_OBJECT_GET_TYPE (obj), obj->object.ifindex);
 			return TRUE;
@@ -973,14 +1143,14 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 {
 	switch (id_type) {
 	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
-		if (_vt_cmd_obj_is_visible_ipx_route (obj)) {
+		if (nmp_object_is_visible (obj)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_addrroute_visible_by_ifindex (id, NMP_OBJECT_GET_TYPE (obj), obj->object.ifindex);
 			return TRUE;
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible  (obj)
 		    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), FALSE, TRUE, 0);
@@ -988,7 +1158,7 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible  (obj)
 		    && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), TRUE, FALSE, 0);
@@ -996,7 +1166,7 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible (obj)
 		    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), FALSE, TRUE, obj->object.ifindex);
@@ -1004,7 +1174,7 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible (obj)
 		    && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), TRUE, FALSE, obj->object.ifindex);
@@ -1211,7 +1381,7 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	const NMPObject *obj;
 	const NMPlatformObject *const *list;
 	guint i, len;
-	NMPCacheId cache_id;
+	NMPCacheId cache_id, *p_cache_id;
 
 	if (ifindex > 0) {
 		obj = nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
@@ -1226,7 +1396,14 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	} else if (!ifname && !match_fn)
 		return NULL;
 	else {
-		list = nmp_cache_lookup_multi (cache, nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, visible_only), &len);
+		if (   ifname
+		    && strlen (ifname) <= sizeof (cache_id.link_by_ifname.ifname_short)) {
+			p_cache_id = nmp_cache_id_init_link_by_ifname (&cache_id, ifname);
+			ifname = NULL;
+		} else
+			p_cache_id = nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, visible_only);
+
+		list = nmp_cache_lookup_multi (cache, p_cache_id, &len);
 		for (i = 0; i < len; i++) {
 			obj = NMP_OBJECT_UP_CAST (list[i]);
 
@@ -1303,12 +1480,8 @@ _nmp_cache_update_add (NMPCache *cache, NMPObject *obj)
 	nm_assert (!obj->is_cached);
 	nmp_object_ref (obj);
 	nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
-#if GLIB_CHECK_VERSION(2, 40, 0)
-	if (!g_hash_table_add (cache->idx_main, obj))
+	if (!nm_g_hash_table_add (cache->idx_main, obj))
 		g_assert_not_reached ();
-#else
-	g_hash_table_add (cache->idx_main, obj);
-#endif
 	obj->is_cached = TRUE;
 	_nmp_cache_update_cache (cache, obj, FALSE);
 }
@@ -1385,7 +1558,7 @@ nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, NMPObjec
 {
 	if (NMP_OBJECT_GET_TYPE (obj_needle) == NMP_OBJECT_TYPE_LINK) {
 		NMPObject *old;
-		auto_nmp_obj NMPObject *obj = NULL;
+		nm_auto_nmpobj NMPObject *obj = NULL;
 
 		/* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be
 		 * removed from netlink. Link objects are alive without being in netlink when they
@@ -1510,8 +1683,8 @@ nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj,
 		 * We could add efficient reverse lookup by adding a reverse index to NMMultiIndex. But that
 		 * also adds some cost to support an (uncommon?) usage pattern.
 		 *
-		 * Instead we just don't support it. Actually, we expect the user to
-		 * create a new instance with nmp_object_from_nl(). That is what nmp_cache_update_netlink().
+		 * Instead we just don't support it, instead we expect the user to
+		 * create a new instance from netlink.
 		 *
 		 * TL;DR: a cached object must never be modified.
 		 */
@@ -1580,7 +1753,7 @@ NMPCacheOpsType
 nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
 {
 	NMPObject *old;
-	auto_nmp_obj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
 
 	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
 
@@ -1647,7 +1820,7 @@ NMPCacheOpsType
 nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
 {
 	NMPObject *old;
-	auto_nmp_obj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
 
 	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
 
@@ -1727,7 +1900,7 @@ nmp_cache_free (NMPCache *cache)
 void
 ASSERT_nmp_cache_is_consistent (const NMPCache *cache)
 {
-#ifdef NM_MORE_ASSERTS
+#if NM_MORE_ASSERTS
 	NMMultiIndexIter iter_multi;
 	GHashTableIter iter_hash;
 	guint i, len;
@@ -1788,24 +1961,23 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectLink),
 		.sizeof_public                      = sizeof (NMPlatformLink),
 		.obj_type_name                      = "link",
-		.nl_type                            = "route/link",
 		.addr_family                        = AF_UNSPEC,
 		.rtm_gettype                        = RTM_GETLINK,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_LINK,
 		.signal_type                        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_link,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_link,
+		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_link,
 		.cmd_obj_copy                       = _vt_cmd_obj_copy_link,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_link,
 		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_link,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_link,
 		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_link,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_link,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_link,
+		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_link,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_link,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_link,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_link,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_link,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_link_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_link_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = {
@@ -1813,23 +1985,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP4Address),
 		.sizeof_public                      = sizeof (NMPlatformIP4Address),
 		.obj_type_name                      = "ip4-address",
-		.nl_type                            = "route/addr",
 		.addr_family                        = AF_INET,
 		.rtm_gettype                        = RTM_GETADDR,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_address,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip4_address,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_address,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_address,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip4_address,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip4_address,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip4_address,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip4_address,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip4_address,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip4_address,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_address_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_address_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP6_ADDRESS - 1] = {
@@ -1837,23 +2004,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP6Address),
 		.sizeof_public                      = sizeof (NMPlatformIP6Address),
 		.obj_type_name                      = "ip6-address",
-		.nl_type                            = "route/addr",
 		.addr_family                        = AF_INET6,
 		.rtm_gettype                        = RTM_GETADDR,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_address,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip6_address,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_address,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_address,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip6_address,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip6_address,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip6_address,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip6_address,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip6_address,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip6_address,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_address_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_address_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp
 	},
 	[NMP_OBJECT_TYPE_IP4_ROUTE - 1] = {
@@ -1861,23 +2023,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP4Route),
 		.sizeof_public                      = sizeof (NMPlatformIP4Route),
 		.obj_type_name                      = "ip4-route",
-		.nl_type                            = "route/route",
 		.addr_family                        = AF_INET,
 		.rtm_gettype                        = RTM_GETROUTE,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_route,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip4_route,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_route,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_route,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip4_route,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip4_route,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip4_route,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip4_route,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip4_route,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip4_route,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_route_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_route_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP6_ROUTE - 1] = {
@@ -1885,24 +2042,104 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP6Route),
 		.sizeof_public                      = sizeof (NMPlatformIP6Route),
 		.obj_type_name                      = "ip6-route",
-		.nl_type                            = "route/route",
 		.addr_family                        = AF_INET6,
 		.rtm_gettype                        = RTM_GETROUTE,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_route,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip6_route,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_route,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_route,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip6_route,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip6_route,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip6_route,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip6_route,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip6_route,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip6_route,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_route_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_route_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_GRE - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_GRE,
+		.sizeof_data                        = sizeof (NMPObjectLnkGre),
+		.sizeof_public                      = sizeof (NMPlatformLnkGre),
+		.obj_type_name                      = "gre",
+		.lnk_link_type                      = NM_LINK_TYPE_GRE,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_gre_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_INFINIBAND,
+		.sizeof_data                        = sizeof (NMPObjectLnkInfiniband),
+		.sizeof_public                      = sizeof (NMPlatformLnkInfiniband),
+		.obj_type_name                      = "infiniband",
+		.lnk_link_type                      = NM_LINK_TYPE_INFINIBAND,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_infiniband_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_infiniband_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_IP6TNL - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6TNL,
+		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
+		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
+		.obj_type_name                      = "ip6tnl",
+		.lnk_link_type                      = NM_LINK_TYPE_IP6TNL,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_IPIP - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IPIP,
+		.sizeof_data                        = sizeof (NMPObjectLnkIpIp),
+		.sizeof_public                      = sizeof (NMPlatformLnkIpIp),
+		.obj_type_name                      = "ipip",
+		.lnk_link_type                      = NM_LINK_TYPE_IPIP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ipip_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ipip_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_MACVLAN - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVLAN,
+		.sizeof_data                        = sizeof (NMPObjectLnkMacvlan),
+		.sizeof_public                      = sizeof (NMPlatformLnkMacvlan),
+		.obj_type_name                      = "macvlan",
+		.lnk_link_type                      = NM_LINK_TYPE_MACVLAN,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_MACVTAP - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVTAP,
+		.sizeof_data                        = sizeof (NMPObjectLnkMacvtap),
+		.sizeof_public                      = sizeof (NMPlatformLnkMacvtap),
+		.obj_type_name                      = "macvtap",
+		.lnk_link_type                      = NM_LINK_TYPE_MACVTAP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_SIT - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_SIT,
+		.sizeof_data                        = sizeof (NMPObjectLnkSit),
+		.sizeof_public                      = sizeof (NMPlatformLnkSit),
+		.obj_type_name                      = "sit",
+		.lnk_link_type                      = NM_LINK_TYPE_SIT,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_sit_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_VLAN - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_VLAN,
+		.sizeof_data                        = sizeof (NMPObjectLnkVlan),
+		.sizeof_public                      = sizeof (NMPlatformLnkVlan),
+		.obj_type_name                      = "vlan",
+		.lnk_link_type                      = NM_LINK_TYPE_VLAN,
+		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_lnk_vlan,
+		.cmd_obj_copy                       = _vt_cmd_obj_copy_lnk_vlan,
+		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_vlan,
+		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_vlan,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_VXLAN - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_VXLAN,
+		.sizeof_data                        = sizeof (NMPObjectLnkVxlan),
+		.sizeof_public                      = sizeof (NMPlatformLnkVxlan),
+		.obj_type_name                      = "vxlan",
+		.lnk_link_type                      = NM_LINK_TYPE_VXLAN,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vxlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vxlan_cmp,
+	},
 };
 
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 5b1887b8..ab1cc2fe 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -64,6 +64,9 @@ typedef enum { /*< skip >*/
 	/* all the objects of a certain type */
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 
+	/* index for the link objects by ifname. */
+	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
+
 	/* all the visible objects of a certain type */
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
 
@@ -104,6 +107,11 @@ typedef struct {
 			guint8 obj_type; /* NMPObjectType as guint8 */
 			int ifindex;
 		} object_type_by_ifindex;
+		struct {
+			/* NMP_CACHE_ID_TYPE_LINK_BY_IFNAME */
+			guint8 _id_type;
+			char ifname_short[IFNAMSIZ - 1]; /* don't include the trailing NUL so the struct fits in 4 bytes. */
+		} link_by_ifname;
 	};
 } NMPCacheId;
 
@@ -116,29 +124,31 @@ typedef struct {
 	int rtm_gettype;
 	int sizeof_data;
 	int sizeof_public;
+	NMPlatformSignalIdType signal_type_id;
 	const char *obj_type_name;
-	const char *nl_type;
 	const char *signal_type;
 
+	/* Only for NMPObjectLnk* types. */
+	NMLinkType lnk_link_type;
+
 	/* returns %FALSE, if the obj type would never have an entry for index type @id_type. If @obj has an index,
 	 * initialize @id and set @out_id to it. Otherwise, @out_id is NULL. */
 	gboolean (*cmd_obj_init_cache_id) (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id);
 
-	gboolean (*cmd_obj_equal) (const NMPObject *obj1, const NMPObject *obj2);
+	int (*cmd_obj_cmp) (const NMPObject *obj1, const NMPObject *obj2);
 	void (*cmd_obj_copy) (NMPObject *dst, const NMPObject *src);
 	void (*cmd_obj_stackinit_id) (NMPObject *obj, const NMPObject *src);
 	void (*cmd_obj_dispose) (NMPObject *obj);
 	gboolean (*cmd_obj_is_alive) (const NMPObject *obj);
 	gboolean (*cmd_obj_is_visible) (const NMPObject *obj);
+	const char *(*cmd_obj_to_string) (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size);
 
 	/* functions that operate on NMPlatformObject */
-	gboolean (*cmd_plobj_init_from_nl) (NMPlatform *platform, NMPlatformObject *obj, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache);
-	struct nl_object *(*cmd_plobj_to_nl) (NMPlatform *platform, const NMPlatformObject *obj, gboolean id_only);
 	void (*cmd_plobj_id_copy) (NMPlatformObject *dst, const NMPlatformObject *src);
 	gboolean (*cmd_plobj_id_equal) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
 	guint (*cmd_plobj_id_hash) (const NMPlatformObject *obj);
 	const char *(*cmd_plobj_to_string_id) (const NMPlatformObject *obj, char *buf, gsize buf_size);
-	const char *(*cmd_plobj_to_string) (const NMPlatformObject *obj);
+	const char *(*cmd_plobj_to_string) (const NMPlatformObject *obj, char *buf, gsize len);
 	int (*cmd_plobj_cmp) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
 } NMPClass;
 
@@ -148,7 +158,10 @@ typedef struct {
 	NMPlatformLink _public;
 
 	struct {
-		guint8 is_in_netlink;
+		bool is_in_netlink;
+
+		/* Additional data that depends on the link-type (IFLA_INFO_DATA) */
+		NMPObject *lnk;
 	} netlink;
 
 	struct {
@@ -157,6 +170,45 @@ typedef struct {
 } NMPObjectLink;
 
 typedef struct {
+	NMPlatformLnkGre _public;
+} NMPObjectLnkGre;
+
+typedef struct {
+	NMPlatformLnkInfiniband _public;
+} NMPObjectLnkInfiniband;
+
+typedef struct {
+	NMPlatformLnkIp6Tnl _public;
+} NMPObjectLnkIp6Tnl;
+
+typedef struct {
+	NMPlatformLnkIpIp _public;
+} NMPObjectLnkIpIp;
+
+typedef struct {
+	NMPlatformLnkMacvlan _public;
+} NMPObjectLnkMacvlan;
+
+typedef NMPObjectLnkMacvlan NMPObjectLnkMacvtap;
+
+typedef struct {
+	NMPlatformLnkSit _public;
+} NMPObjectLnkSit;
+
+typedef struct {
+	NMPlatformLnkVlan _public;
+
+	guint n_ingress_qos_map;
+	guint n_egress_qos_map;
+	const NMVlanQosMapping *ingress_qos_map;
+	const NMVlanQosMapping *egress_qos_map;
+} NMPObjectLnkVlan;
+
+typedef struct {
+	NMPlatformLnkVxlan _public;
+} NMPObjectLnkVxlan;
+
+typedef struct {
 	NMPlatformIP4Address _public;
 } NMPObjectIP4Address;
 
@@ -175,13 +227,37 @@ typedef struct {
 struct _NMPObject {
 	const NMPClass *_class;
 	int _ref_count;
-	guint8 is_cached;
+	bool is_cached;
 	union {
 		NMPlatformObject        object;
 
 		NMPlatformLink          link;
 		NMPObjectLink           _link;
 
+		NMPlatformLnkGre        lnk_gre;
+		NMPObjectLnkGre         _lnk_gre;
+
+		NMPlatformLnkInfiniband lnk_infiniband;
+		NMPObjectLnkInfiniband  _lnk_infiniband;
+
+		NMPlatformLnkIpIp       lnk_ipip;
+		NMPObjectLnkIpIp        _lnk_ipip;
+
+		NMPlatformLnkIp6Tnl     lnk_ip6tnl;
+		NMPObjectLnkIp6Tnl      _lnk_ip6tnl;
+
+		NMPlatformLnkMacvlan    lnk_macvlan;
+		NMPObjectLnkMacvlan     _lnk_macvlan;
+
+		NMPlatformLnkSit        lnk_sit;
+		NMPObjectLnkSit         _lnk_sit;
+
+		NMPlatformLnkVlan       lnk_vlan;
+		NMPObjectLnkVlan        _lnk_vlan;
+
+		NMPlatformLnkVxlan      lnk_vxlan;
+		NMPObjectLnkVxlan       _lnk_vxlan;
+
 		NMPlatformIPAddress     ip_address;
 		NMPlatformIPXAddress    ipx_address;
 		NMPlatformIP4Address    ip4_address;
@@ -269,7 +345,7 @@ NMPObject *nmp_object_new_link (int ifindex);
 const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj);
 const NMPObject *nmp_object_stackinit_id  (NMPObject *obj, const NMPObject *src);
 const NMPObject *nmp_object_stackinit_id_link (NMPObject *obj, int ifindex);
-const NMPObject *nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen);
+const NMPObject *nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen, guint32 peer_address);
 const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, int plen);
 const NMPObject *nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, int plen, guint32 metric);
 const NMPObject *nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, int plen, guint32 metric);
@@ -286,9 +362,9 @@ gboolean nmp_object_is_visible (const NMPObject *obj);
 
 void _nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev);
 
-#define auto_nmp_obj __attribute__((cleanup(_nmp_auto_obj_cleanup)))
+#define nm_auto_nmpobj __attribute__((cleanup(_nm_auto_nmpobj_cleanup)))
 static inline void
-_nmp_auto_obj_cleanup (NMPObject **pobj)
+_nm_auto_nmpobj_cleanup (NMPObject **pobj)
 {
 	nmp_object_unref (*pobj);
 }
@@ -306,6 +382,7 @@ void nmp_cache_id_destroy (NMPCacheId *id);
 NMPCacheId *nmp_cache_id_init_object_type (NMPCacheId *id, NMPObjectType obj_type, gboolean visible_only);
 NMPCacheId *nmp_cache_id_init_addrroute_visible_by_ifindex (NMPCacheId *id, NMPObjectType obj_type, int ifindex);
 NMPCacheId *nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPObjectType obj_type, gboolean with_default, gboolean with_non_default, int ifindex);
+NMPCacheId *nmp_cache_id_init_link_by_ifname (NMPCacheId *id, const char *ifname);
 
 const NMPlatformObject *const *nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len);
 GArray *nmp_cache_lookup_multi_to_array (const NMPCache *cache, NMPObjectType obj_type, const NMPCacheId *cache_id);
@@ -341,21 +418,4 @@ NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifi
 NMPCache *nmp_cache_new (void);
 void nmp_cache_free (NMPCache *cache);
 
-NMPObject *nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache);
-struct nl_object *nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only);
-
-/* the following functions are currently implemented inside nm-linux-platform, because
- * they depend on utility functions there. */
-NMPObjectType _nlo_get_object_type (const struct nl_object *nlo);
-gboolean _nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
-gboolean _nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
-gboolean _nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
-gboolean _nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
-gboolean _nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
-struct nl_object *_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
-struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
-struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
-struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
-struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
-
 #endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am
index 085f56ca..b097fcf6 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -1,14 +1,14 @@
 AM_CPPFLAGS = \
 	-I${top_srcdir} \
-	-I${top_srcdir}/include \
-	-I${top_builddir}/include \
+	-I${top_srcdir}/shared \
+	-I${top_builddir}/shared \
 	-I${top_srcdir}/src \
 	-I${top_builddir}/src \
 	-I${top_srcdir}/libnm-core \
 	-I${top_builddir}/libnm-core \
 	-I${srcdir}/.. \
 	-DG_LOG_DOMAIN=\""NetworkManager"\" \
-	-DNETWORKMANAGER_COMPILATION \
+	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
 	$(GLIB_CFLAGS) \
 	$(GUDEV_CFLAGS) \
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index e21a3bb6..c04d6a1b 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -503,7 +503,6 @@ BLUEZ5_LIBS = @BLUEZ5_LIBS@
 CC = @CC@
 CCDEPMODE = @CCDEPMODE@
 CFLAGS = @CFLAGS@
-CKDB_PATH = @CKDB_PATH@
 CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
 CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
 CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
@@ -515,8 +514,6 @@ CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
 DBUS_CFLAGS = @DBUS_CFLAGS@
-DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
-DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
 DBUS_LIBS = @DBUS_LIBS@
 DBUS_SYS_DIR = @DBUS_SYS_DIR@
 DEFS = @DEFS@
@@ -526,6 +523,7 @@ DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
 DNSMASQ_PATH = @DNSMASQ_PATH@
+DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
 DUMPBIN = @DUMPBIN@
 ECHO_C = @ECHO_C@
@@ -580,16 +578,13 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
-IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@
-IWMX_SDK_LIBS = @IWMX_SDK_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
+LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
+LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
 LIBDL = @LIBDL@
-LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
-LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
-LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -626,6 +621,8 @@ NEWT_LIBS = @NEWT_LIBS@
 NM = @NM@
 NMEDIT = @NMEDIT@
 NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT = @NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT@
+NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT = @NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT@
+NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT = @NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT@
 NM_MAJOR_VERSION = @NM_MAJOR_VERSION@
 NM_MICRO_VERSION = @NM_MICRO_VERSION@
 NM_MINOR_VERSION = @NM_MINOR_VERSION@
@@ -654,7 +651,6 @@ POLKIT_LIBS = @POLKIT_LIBS@
 POSUB = @POSUB@
 PPPD_PATH = @PPPD_PATH@
 PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@
-PPPOE_PATH = @PPPOE_PATH@
 QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
@@ -669,6 +665,8 @@ SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@
 SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@
 SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@
 SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
 SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@
 SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@
 SYSTEM_CA_PATH = @SYSTEM_CA_PATH@
@@ -729,6 +727,7 @@ mkdir_p = @mkdir_p@
 nmbinary = @nmbinary@
 nmconfdir = @nmconfdir@
 nmdatadir = @nmdatadir@
+nmlibdir = @nmlibdir@
 nmrundir = @nmrundir@
 nmstatedir = @nmstatedir@
 oldincludedir = @oldincludedir@
@@ -736,6 +735,7 @@ pdfdir = @pdfdir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
@@ -751,12 +751,12 @@ with_dhcpcd = @with_dhcpcd@
 with_netconfig = @with_netconfig@
 with_resolvconf = @with_resolvconf@
 with_valgrind = @with_valgrind@
-AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/include \
-	-I${top_builddir}/include -I${top_srcdir}/src \
+AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/shared \
+	-I${top_builddir}/shared -I${top_srcdir}/src \
 	-I${top_builddir}/src -I${top_srcdir}/libnm-core \
 	-I${top_builddir}/libnm-core -I${srcdir}/.. \
 	-DG_LOG_DOMAIN=\""NetworkManager"\" \
-	-DNETWORKMANAGER_COMPILATION \
+	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE $(GLIB_CFLAGS) \
 	$(GUDEV_CFLAGS) $(LIBNL_CFLAGS) $(am__append_1)
 PLATFORM_SOURCES = \
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index f1058db8..5cc3114c 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -3,34 +3,66 @@
 #include <stdlib.h>
 #include <syslog.h>
 
+#include "nm-default.h"
 #include "nm-linux-platform.h"
-#include "nm-logging.h"
 
 #include "nm-test-utils.h"
 
 NMTST_DEFINE ();
 
+static struct {
+	gboolean persist;
+} global_opt = {
+	.persist = TRUE,
+};
+
+static gboolean
+read_argv (int *argc, char ***argv)
+{
+	GOptionContext *context;
+	GOptionEntry options[] = {
+		{ "no-persist", 'P', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &global_opt.persist, "Exit after processing netlink messages", NULL },
+		{ 0 },
+	};
+	gs_free_error GError *error = NULL;
+
+	context = g_option_context_new (NULL);
+	g_option_context_set_summary (context, "Monitor netlink events in NMPlatform.");
+	g_option_context_add_main_entries (context, options, NULL);
+
+	if (!g_option_context_parse (context, argc, argv, &error)) {
+		g_warning ("Error parsing command line arguments: %s", error->message);
+		g_option_context_free (context);
+		return FALSE;
+	}
+
+	g_option_context_free (context);
+	return TRUE;
+}
+
 int
 main (int argc, char **argv)
 {
 	GMainLoop *loop;
 
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
 	if (!g_getenv ("G_MESSAGES_DEBUG"))
 		g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
 
 	nmtst_init_with_logging (&argc, &argv, "DEBUG", "ALL");
 
+	if (!read_argv (&argc, &argv))
+		return 2;
+
 	nm_log_info (LOGD_PLATFORM, "platform monitor start");
 
 	loop = g_main_loop_new (NULL, FALSE);
 
 	nm_linux_platform_setup ();
 
-	g_main_loop_run (loop);
+	if (global_opt.persist)
+		g_main_loop_run (loop);
+
+	g_main_loop_unref (loop);
 
 	return EXIT_SUCCESS;
 }
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index d6f346b3..566bb479 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -4,12 +4,19 @@
 
 #define DEVICE_NAME "nm-test-device"
 #define IP4_ADDRESS "192.0.2.1"
+#define IP4_ADDRESS_PEER "192.0.2.2"
+#define IP4_ADDRESS_PEER2 "192.0.3.1"
 #define IP4_PLEN 24
 #define IP6_ADDRESS "2001:db8:a:b:1:2:3:4"
 #define IP6_PLEN 64
 
+static int DEVICE_IFINDEX = -1;
+static int EX = -1;
+
+/*****************************************************************************/
+
 static void
-ip4_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip4_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -25,11 +32,11 @@ ip4_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex,
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
-ip6_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip6_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -45,13 +52,15 @@ ip6_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex,
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
+/*****************************************************************************/
+
 static void
-test_ip4_address (void)
+test_ip4_address_general (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_address_callback, ifindex);
 	SignalData *address_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip4_address_callback, ifindex);
 	SignalData *address_removed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_address_callback, ifindex);
@@ -64,13 +73,13 @@ test_ip4_address (void)
 	inet_pton (AF_INET, IP4_ADDRESS, &addr);
 
 	/* Add address */
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime + 100, preferred + 50, NULL);
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
@@ -80,16 +89,17 @@ test_ip4_address (void)
 	address = &g_array_index (addresses, NMPlatformIP4Address, 0);
 	g_assert_cmpint (address->ifindex, ==, ifindex);
 	g_assert_cmphex (address->address, ==, addr);
+	g_assert_cmphex (address->peer_address, ==, addr);
 	g_assert_cmpint (address->plen, ==, IP4_PLEN);
 	g_array_unref (addresses);
 
 	/* Remove address */
-	g_assert (nm_platform_ip4_address_delete (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, 0));
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_removed);
 
 	/* Remove address again */
-	g_assert (nm_platform_ip4_address_delete (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, 0));
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
 
 	free_signal (address_added);
 	free_signal (address_changed);
@@ -97,9 +107,9 @@ test_ip4_address (void)
 }
 
 static void
-test_ip6_address (void)
+test_ip6_address_general (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip6_address_callback, ifindex);
 	SignalData *address_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip6_address_callback, ifindex);
 	SignalData *address_removed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip6_address_callback, ifindex);
@@ -114,12 +124,12 @@ test_ip6_address (void)
 
 	/* Add address */
 	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
 	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
@@ -133,12 +143,15 @@ test_ip6_address (void)
 	g_array_unref (addresses);
 
 	/* Remove address */
-	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	nmtstp_ip6_address_del (EX, ifindex, addr, IP6_PLEN);
 	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 	accept_signal (address_removed);
 
 	/* Remove address again */
-	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	nmtstp_ip6_address_del (EX, ifindex, addr, IP6_PLEN);
+
+	/* ensure not pending signal. */
+	accept_signals (address_changed, 0, 1);
 
 	free_signal (address_added);
 	free_signal (address_changed);
@@ -146,11 +159,11 @@ test_ip6_address (void)
 }
 
 static void
-test_ip4_address_external (void)
+test_ip4_address_general_2 (void)
 {
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_address_callback);
 	SignalData *address_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_address_callback);
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	in_addr_t addr;
 	guint32 lifetime = 2000;
 	guint32 preferred = 1000;
@@ -161,38 +174,31 @@ test_ip4_address_external (void)
 	/* Looks like addresses are not announced by kerenl when the interface
 	 * is down. Link-local IPv6 address is automatically added.
 	 */
-	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, DEVICE_IFINDEX, NULL));
 
 	/* Add/delete notification */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP4_ADDRESS, IP4_PLEN, DEVICE_NAME, lifetime, preferred);
-	wait_signal (address_added);
-	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
-	run_command ("ip address delete %s/%d dev %s", IP4_ADDRESS, IP4_PLEN, DEVICE_NAME);
-	wait_signal (address_removed);
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	accept_signal (address_added);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
+	accept_signal (address_removed);
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 
 	/* Add/delete conflict */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP4_ADDRESS, IP4_PLEN, DEVICE_NAME, lifetime, preferred);
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_added);
-	/*run_command ("ip address delete %s/%d dev %s", IP4_ADDRESS, IP4_PLEN, DEVICE_NAME);
-	g_assert (nm_platform_ip4_address_delete (ifindex, addr, IP4_PLEN, 0));
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
-	accept_signal (address_removed);*/
 
 	free_signal (address_added);
 	free_signal (address_removed);
 }
 
 static void
-test_ip6_address_external (void)
+test_ip6_address_general_2 (void)
 {
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip6_address_callback);
 	SignalData *address_removed = add_signal (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip6_address_callback);
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	struct in6_addr addr;
 	guint32 lifetime = 2000;
 	guint32 preferred = 1000;
@@ -201,51 +207,197 @@ test_ip6_address_external (void)
 	inet_pton (AF_INET6, IP6_ADDRESS, &addr);
 
 	/* Add/delete notification */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP6_ADDRESS, IP6_PLEN, DEVICE_NAME, lifetime, preferred);
-	wait_signal (address_added);
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
+	accept_signal (address_added);
 	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	run_command ("ip address delete %s/%d dev %s", IP6_ADDRESS, IP6_PLEN, DEVICE_NAME);
-	wait_signal (address_removed);
+
+	nmtstp_ip6_address_del (EX, ifindex, addr, IP6_PLEN);
+	accept_signal (address_removed);
 	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 
 	/* Add/delete conflict */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP6_ADDRESS, IP6_PLEN, DEVICE_NAME, lifetime, preferred);
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
+	accept_signal (address_added);
 	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
+	ensure_no_signal (address_added);
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+
+	free_signal (address_added);
+	free_signal (address_removed);
+}
+
+/*****************************************************************************/
+
+static void
+test_ip4_address_peer (void)
+{
+	const int ifindex = DEVICE_IFINDEX;
+	SignalData *address_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_address_callback);
+	SignalData *address_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_address_callback);
+	in_addr_t addr, addr_peer, addr_peer2;
+	guint32 lifetime = 2000;
+	guint32 preferred = 1000;
+	const NMPlatformIP4Address *a;
+
+	inet_pton (AF_INET, IP4_ADDRESS, &addr);
+	inet_pton (AF_INET, IP4_ADDRESS_PEER, &addr_peer);
+	inet_pton (AF_INET, IP4_ADDRESS_PEER2, &addr_peer2);
+	g_assert (ifindex > 0);
+
+	g_assert (addr != addr_peer);
+
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+	accept_signals (address_removed, 0, G_MAXINT);
+	accept_signals (address_added, 0, G_MAXINT);
+
+	/* Add/delete notification */
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer, lifetime, preferred, NULL);
 	accept_signal (address_added);
-	/*run_command ("ip address delete %s/%d dev %s", IP6_ADDRESS, IP6_PLEN, DEVICE_NAME);
-	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	wait_signal (address_removed);*/
+	g_assert ((a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer)));
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2));
+
+	nmtstp_ip_address_assert_lifetime ((NMPlatformIPAddress *) a, -1, lifetime, preferred);
+
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer2, lifetime, preferred, NULL);
+	accept_signal (address_added);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer));
+	g_assert ((a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2)));
+
+	nmtstp_ip_address_assert_lifetime ((NMPlatformIPAddress *) a, -1, lifetime, preferred);
+
+	g_assert (addr != addr_peer);
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr_peer);
+	accept_signal (address_removed);
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer));
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2));
 
 	free_signal (address_added);
 	free_signal (address_removed);
 }
 
+/*****************************************************************************/
+
+static void
+test_ip4_address_peer_zero (void)
+{
+	const int ifindex = DEVICE_IFINDEX;
+	in_addr_t addr, addr_peer;
+	guint32 lifetime = 2000;
+	guint32 preferred = 1000;
+	const int plen = 24;
+	const char *label = NULL;
+	in_addr_t peers[3], r_peers[3];
+	int i;
+	GArray *addrs;
+
+	g_assert (ifindex > 0);
+
+	inet_pton (AF_INET, "192.168.5.2", &addr);
+	inet_pton (AF_INET, "192.168.6.2", &addr_peer);
+	peers[0] = addr;
+	peers[1] = addr_peer;
+	peers[2] = 0;
+
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+
+	nmtst_rand_perm (NULL, r_peers, peers, sizeof (peers[0]), G_N_ELEMENTS (peers));
+	for (i = 0; i < G_N_ELEMENTS (peers); i++) {
+		g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i]));
+
+		nmtstp_ip4_address_add (EX, ifindex, addr, plen, r_peers[i], lifetime, preferred, label);
+
+		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		g_assert (addrs);
+		g_assert_cmpint (addrs->len, ==, i + 1);
+		g_array_unref (addrs);
+	}
+
+	if (nmtst_is_debug () && nmtstp_is_root_test ())
+		nmtstp_run_command_check ("ip address show dev %s", DEVICE_NAME);
+
+	nmtst_rand_perm (NULL, r_peers, peers, sizeof (peers[0]), G_N_ELEMENTS (peers));
+	for (i = 0; i < G_N_ELEMENTS (peers); i++) {
+		g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i]));
+
+		nmtstp_ip4_address_del (EX, ifindex, addr, plen, r_peers[i]);
+
+		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		g_assert (addrs);
+		g_assert_cmpint (addrs->len, ==, G_N_ELEMENTS (peers) - i - 1);
+		g_array_unref (addrs);
+	}
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
 	nmtst_init_with_logging (argc, argv, NULL, "ALL");
 }
 
-void
-setup_tests (void)
+/*****************************************************************************
+ * SETUP TESTS
+ *****************************************************************************/
+
+typedef struct {
+	const char *testpath;
+	GTestFunc test_func;
+} TestSetup;
+
+static void
+_g_test_run (gconstpointer user_data)
 {
-	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
+	const TestSetup *s = user_data;
+	int ifindex;
+
+	_LOGT ("TEST: start %s", s->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 (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
-	accept_signal (link_added);
-	free_signal (link_added);
+	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
 
-	g_test_add_func ("/address/internal/ip4", test_ip4_address);
-	g_test_add_func ("/address/internal/ip6", test_ip6_address);
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (ifindex, >, 0);
+	g_assert_cmpint (DEVICE_IFINDEX, ==, -1);
 
-	if (strcmp (g_type_name (G_TYPE_FROM_INSTANCE (nm_platform_get ())), "NMFakePlatform")) {
-		g_test_add_func ("/address/external/ip4", test_ip4_address_external);
-		g_test_add_func ("/address/external/ip6", test_ip6_address_external);
-	}
+	DEVICE_IFINDEX = ifindex;
+	EX = nmtstp_run_command_check_external_global ();
+
+	s->test_func ();
+
+	g_assert_cmpint (DEVICE_IFINDEX, ==, ifindex);
+	DEVICE_IFINDEX = -1;
+
+	g_assert_cmpint (ifindex, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	_LOGT ("TEST: finished %s", s->testpath);
+}
+
+static void
+_g_test_add_func (const char *testpath,
+                  GTestFunc   test_func)
+{
+	TestSetup *s;
+
+	s = g_new0 (TestSetup, 1);
+	s->testpath = testpath;
+	s->test_func = test_func;
+
+	g_test_add_data_func_full (testpath, s, _g_test_run, g_free);
+}
+
+void
+setup_tests (void)
+{
+	_g_test_add_func ("/address/ipv4/general", test_ip4_address_general);
+	_g_test_add_func ("/address/ipv6/general", test_ip6_address_general);
+
+	_g_test_add_func ("/address/ipv4/general-2", test_ip4_address_general_2);
+	_g_test_add_func ("/address/ipv6/general-2", test_ip6_address_general_2);
+
+	_g_test_add_func ("/address/ipv4/peer", test_ip4_address_peer);
+	_g_test_add_func ("/address/ipv4/peer/zero", test_ip4_address_peer_zero);
 }
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 0b825114..9e624a02 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -35,7 +35,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_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
 	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));
@@ -43,8 +43,8 @@ test_cleanup_internal (void)
 	g_assert (ifindex > 0);
 
 	/* Add routes and addresses */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, 0, plen4, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, in6addr_any, plen6, lifetime, preferred, flags));
+	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, lifetime, preferred, NULL));
+	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, plen6, in6addr_any, lifetime, preferred, flags));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway4, 32, INADDR_ANY, 0, metric, mss));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network4, plen4, gateway4, 0, metric, mss));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway4, 0, metric, mss));
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index b0eae63b..09a57f87 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -2,6 +2,8 @@
 
 #include <sys/mount.h>
 #include <sched.h>
+#include <sys/wait.h>
+#include <fcntl.h>
 
 #include "test-common.h"
 
@@ -10,9 +12,8 @@
 #define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)"
 #define SIGNAL_DATA_ARG(data) (data)->name, nm_platform_signal_change_type_to_string ((data)->change_type), (data)->ifindex, (data)->ifname ? " ifname '" : "", (data)->ifname ? (data)->ifname : "", (data)->ifname ? "'" : "", (data)->received_count
 
-
 gboolean
-nmtst_platform_is_root_test (void)
+nmtstp_is_root_test (void)
 {
 	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
 	return (SETUP == nm_linux_platform_setup);
@@ -20,9 +21,9 @@ nmtst_platform_is_root_test (void)
 }
 
 gboolean
-nmtst_platform_is_sysfs_writable (void)
+nmtstp_is_sysfs_writable (void)
 {
-	return    !nmtst_platform_is_root_test ()
+	return    !nmtstp_is_root_test ()
 	       || (access ("/sys/devices", W_OK) == 0);
 }
 
@@ -34,7 +35,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall
 	data->name = name;
 	data->change_type = change_type;
 	data->received_count = 0;
-	data->handler_id = g_signal_connect (nm_platform_get (), name, callback, data);
+	data->handler_id = g_signal_connect (NM_PLATFORM_GET, name, callback, data);
 	data->ifindex = ifindex;
 	data->ifname = ifname;
 
@@ -46,7 +47,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall
 void
 _accept_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count != 1)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	data->received_count = 0;
@@ -55,7 +56,7 @@ _accept_signal (const char *file, int line, const char *func, SignalData *data)
 void
 _accept_signals (const char *file, int line, const char *func, SignalData *data, int min, int max)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data));
 	if (data->received_count < min || data->received_count > max)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data));
 	data->received_count = 0;
@@ -64,15 +65,28 @@ _accept_signals (const char *file, int line, const char *func, SignalData *data,
 void
 _ensure_no_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count > 0)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 }
 
 void
+_accept_or_wait_signal (const char *file, int line, const char *func, SignalData *data)
+{
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): accept-or-wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	if (data->received_count == 0) {
+		data->loop = g_main_loop_new (NULL, FALSE);
+		g_main_loop_run (data->loop);
+		g_clear_pointer (&data->loop, g_main_loop_unref);
+	}
+
+	_accept_signal (file, line, func, data);
+}
+
+void
 _wait_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to wait for signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 
@@ -86,16 +100,16 @@ _wait_signal (const char *file, int line, const char *func, SignalData *data)
 void
 _free_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): free signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): free signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count != 0)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to free non-accepted signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 
-	g_signal_handler_disconnect (nm_platform_get (), data->handler_id);
+	g_signal_handler_disconnect (NM_PLATFORM_GET, data->handler_id);
 	g_free (data);
 }
 
 void
-link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	GArray *links;
 	NMPlatformLink *cached;
@@ -114,12 +128,12 @@ link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlat
 		return;
 
 	if (data->loop) {
-		debug ("Quitting main loop.");
+		_LOGD ("Quitting main loop.");
 		g_main_loop_quit (data->loop);
 	}
 
 	data->received_count++;
-	debug ("Received signal '%s-%s' ifindex %d ifname '%s' %dth time.", data->name, nm_platform_signal_change_type_to_string (data->change_type), ifindex, received->name, data->received_count);
+	_LOGD ("Received signal '%s-%s' ifindex %d ifname '%s' %dth time.", data->name, nm_platform_signal_change_type_to_string (data->change_type), ifindex, received->name, data->received_count);
 
 	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
 		g_assert (!nm_platform_link_get_name (NM_PLATFORM_GET, ifindex));
@@ -253,21 +267,1176 @@ _assert_ip4_route_exists (const char *file, guint line, const char *func, gboole
 	}
 }
 
-void
-run_command (const char *format, ...)
+int
+nmtstp_run_command (const char *format, ...)
 {
-	char *command;
+	int result;
+	gs_free char *command = NULL;
 	va_list ap;
 
 	va_start (ap, format);
 	command = g_strdup_vprintf (format, ap);
 	va_end (ap);
-	debug ("Running command: %s", command);
-	g_assert (!system (command));
-	debug ("Command finished.");
-	g_free (command);
+
+	_LOGD ("Running command: %s", command);
+	result = system (command);
+	_LOGD ("Command finished: result=%d", result);
+
+	return result;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	GMainLoop *loop;
+	gboolean timeout;
+	guint id;
+} WaitForSignalData;
+
+static void
+_wait_for_signal_cb (NMPlatform *platform,
+                     NMPObjectType obj_type,
+                     int ifindex,
+                     NMPlatformLink *plink,
+                     NMPlatformSignalChangeType change_type,
+                     gpointer user_data)
+{
+	WaitForSignalData *data = user_data;
+
+	g_main_loop_quit (data->loop);
+}
+
+static gboolean
+_wait_for_signal_timeout (gpointer user_data)
+{
+	WaitForSignalData *data = user_data;
+
+	data->timeout = TRUE;
+	data->id = 0;
+	g_main_loop_quit (data->loop);
+	return G_SOURCE_REMOVE;
+}
+
+gboolean
+nmtstp_wait_for_signal (guint timeout_ms)
+{
+	WaitForSignalData data = { 0 };
+
+	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
+
+	data.loop = g_main_loop_new (NULL, FALSE);
+
+	id_link        = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip4_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip6_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip4_route   = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip6_route   = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+
+	if (timeout_ms != 0)
+		data.id = g_timeout_add (timeout_ms, _wait_for_signal_timeout, &data);
+
+	g_main_loop_run (data.loop);
+
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_link));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_address));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_address));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_route));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_route));
+
+	if (nm_clear_g_source (&data.id))
+		g_assert (timeout_ms != 0 && !data.timeout);
+
+	g_clear_pointer (&data.loop, g_main_loop_unref);
+
+	return !data.timeout;
+}
+
+gboolean
+nmtstp_wait_for_signal_until (gint64 until_ms)
+{
+	gint64 now;
+
+	while (TRUE) {
+		now = nm_utils_get_monotonic_timestamp_ms ();
+
+		if (until_ms < now)
+			return FALSE;
+
+		if (nmtstp_wait_for_signal (MAX (1, until_ms - now)))
+			return TRUE;
+	}
+}
+
+const NMPlatformLink *
+nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+{
+	return nmtstp_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+}
+
+const NMPlatformLink *
+nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+{
+	const NMPlatformLink *plink;
+	gint64 now;
+
+	while (TRUE) {
+		now = nm_utils_get_monotonic_timestamp_ms ();
+
+		plink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname);
+		if (   plink
+		    && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type))
+			return plink;
+
+		if (until_ms < now)
+			return NULL;
+
+		nmtstp_wait_for_signal (MAX (1, until_ms - now));
+	}
+}
+
+const NMPlatformLink *
+nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+{
+	return nmtstp_assert_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+}
+
+const NMPlatformLink *
+nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+{
+	const NMPlatformLink *plink;
+
+	plink = nmtstp_wait_for_link_until (ifname, expected_link_type, until_ms);
+	g_assert (plink);
+	return plink;
+}
+
+int
+nmtstp_run_command_check_external_global (void)
+{
+	if (!nmtstp_is_root_test ())
+		return FALSE;
+	switch (nmtst_get_rand_int () % 3) {
+	case 0:
+		return -1;
+	case 1:
+		return FALSE;
+	default:
+		return TRUE;
+	}
+}
+
+gboolean
+nmtstp_run_command_check_external (int external_command)
+{
+	if (external_command != -1) {
+		g_assert (NM_IN_SET (external_command, FALSE, TRUE));
+		g_assert (!external_command || nmtstp_is_root_test ());
+		return !!external_command;
+	}
+	if (!nmtstp_is_root_test ())
+		return FALSE;
+	return (nmtst_get_rand_int () % 2) == 0;
+}
+
+#define CHECK_LIFETIME_MAX_DIFF    2
+
+gboolean
+nmtstp_ip_address_check_lifetime (const NMPlatformIPAddress *addr,
+                                  gint64 now,
+                                  guint32 expected_lifetime,
+                                  guint32 expected_preferred)
+{
+	gint64 offset;
+	int i;
+
+	g_assert (addr);
+
+	if (now == -1)
+		now = nm_utils_get_monotonic_timestamp_s ();
+	g_assert (now > 0);
+
+	g_assert (expected_preferred <= expected_lifetime);
+
+	if (   expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    && expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT) {
+		return    addr->timestamp == 0
+		       && addr->lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+		       && addr->preferred == NM_PLATFORM_LIFETIME_PERMANENT;
+	}
+
+	if (addr->timestamp == 0)
+		return FALSE;
+
+	offset = (gint64) now - addr->timestamp;
+
+	for (i = 0; i < 2; i++) {
+		guint32 lft = i ? expected_lifetime : expected_preferred;
+		guint32 adr = i ? addr->lifetime : addr->preferred;
+
+		if (lft == NM_PLATFORM_LIFETIME_PERMANENT) {
+			if (adr != NM_PLATFORM_LIFETIME_PERMANENT)
+				return FALSE;
+		} else {
+			if (   adr - offset <= lft - CHECK_LIFETIME_MAX_DIFF
+			    || adr - offset >= lft + CHECK_LIFETIME_MAX_DIFF)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void
+nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
+                                   gint64 now,
+                                   guint32 expected_lifetime,
+                                   guint32 expected_preferred)
+{
+	gint64 n = now;
+	gint64 offset;
+	int i;
+
+	g_assert (addr);
+
+	if (now == -1)
+		now = nm_utils_get_monotonic_timestamp_s ();
+	g_assert (now > 0);
+
+	g_assert (expected_preferred <= expected_lifetime);
+
+	if (   expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    && expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT) {
+		g_assert_cmpint (addr->timestamp, ==, 0);
+		g_assert_cmpint (addr->lifetime, ==, NM_PLATFORM_LIFETIME_PERMANENT);
+		g_assert_cmpint (addr->preferred, ==, NM_PLATFORM_LIFETIME_PERMANENT);
+		return;
+	}
+
+	g_assert_cmpint (addr->timestamp, >, 0);
+	g_assert_cmpint (addr->timestamp, <=, now);
+
+	offset = (gint64) now - addr->timestamp;
+	g_assert_cmpint (offset, >=, 0);
+
+	for (i = 0; i < 2; i++) {
+		guint32 lft = i ? expected_lifetime : expected_preferred;
+		guint32 adr = i ? addr->lifetime : addr->preferred;
+
+		if (lft == NM_PLATFORM_LIFETIME_PERMANENT)
+			g_assert_cmpint (adr, ==, NM_PLATFORM_LIFETIME_PERMANENT);
+		else {
+			g_assert_cmpint (adr, <=, lft);
+			g_assert_cmpint (offset, <=, adr);
+			g_assert_cmpint (adr - offset, <=, lft + CHECK_LIFETIME_MAX_DIFF);
+			g_assert_cmpint (adr - offset, >=, lft - CHECK_LIFETIME_MAX_DIFF);
+		}
+	}
+
+	g_assert (nmtstp_ip_address_check_lifetime (addr, n, expected_lifetime, expected_preferred));
+}
+
+static void
+_ip_address_add (gboolean external_command,
+                 gboolean is_v4,
+                 int ifindex,
+                 const NMIPAddr *address,
+                 int plen,
+                 const NMIPAddr *peer_address,
+                 guint32 lifetime,
+                 guint32 preferred,
+                 const char *label,
+                 guint flags)
+{
+	gint64 end_time;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *ifname;
+		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];
+
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		g_assert (ifname);
+
+		if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT)
+			s_valid = g_strdup_printf (" valid_lft %d", lifetime);
+		if (preferred != NM_PLATFORM_LIFETIME_PERMANENT)
+			s_preferred = g_strdup_printf (" preferred_lft %d", preferred);
+		if (label)
+			s_label = g_strdup_printf ("%s:%s", ifname, label);
+
+		if (is_v4) {
+			char s_peer[100];
+
+			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));
+			} else
+				s_peer[0] = '\0';
+
+			nmtstp_run_command_check ("ip address change %s%s/%d dev %s%s%s%s",
+			                          nm_utils_inet4_ntop (address->addr4, b1),
+			                          s_peer,
+			                          plen,
+			                          ifname,
+			                          s_valid ?: "",
+			                          s_preferred ?: "",
+			                          s_label ?: "");
+		} else {
+			g_assert (label == NULL);
+
+			/* flags not implemented (yet) */
+			g_assert (flags == 0);
+			nmtstp_run_command_check ("ip address change %s%s%s/%d dev %s%s%s%s",
+			                          nm_utils_inet6_ntop (&address->addr6, b1),
+			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? " peer " : "",
+			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? nm_utils_inet6_ntop (&peer_address->addr6, b2) : "",
+			                          plen,
+			                          ifname,
+			                          s_valid ?: "",
+			                          s_preferred ?: "",
+			                          s_label ?: "");
+		}
+	} else {
+		gboolean success;
+
+		if (is_v4) {
+			g_assert (flags == 0);
+			success = nm_platform_ip4_address_add (NM_PLATFORM_GET,
+			                                       ifindex,
+			                                       address->addr4,
+			                                       plen,
+			                                       peer_address->addr4,
+			                                       lifetime,
+			                                       preferred,
+			                                       label);
+		} else {
+			g_assert (label == NULL);
+			success = nm_platform_ip6_address_add (NM_PLATFORM_GET,
+			                                       ifindex,
+			                                       address->addr6,
+			                                       plen,
+			                                       peer_address->addr6,
+			                                       lifetime,
+			                                       preferred,
+			                                       flags);
+		}
+		g_assert (success);
+	}
+
+	/* Let's wait until we see the address. */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		/* let's wait until we see the address as we added it. */
+		if (is_v4) {
+			const NMPlatformIP4Address *a;
+
+			g_assert (flags == 0);
+			a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4);
+			if (   a
+			    && a->peer_address == peer_address->addr4
+			    && nmtstp_ip_address_check_lifetime ((NMPlatformIPAddress*) a, -1, lifetime, preferred)
+			    && strcmp (a->label, label ?: "") == 0)
+				break;
+		} else {
+			const NMPlatformIP6Address *a;
+
+			g_assert (label == NULL);
+			g_assert (flags == 0);
+
+			a = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen);
+			if (   a
+			    && !memcmp (nm_platform_ip6_address_get_peer (a),
+			                (IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) || IN6_ARE_ADDR_EQUAL (&address->addr6, &peer_address->addr6))
+			                    ? &address->addr6 : &peer_address->addr6,
+			                sizeof (struct in6_addr))
+			    && nmtstp_ip_address_check_lifetime ((NMPlatformIPAddress*) a, -1, lifetime, preferred))
+				break;
+		}
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
+}
+
+#define _assert_pllink(success, pllink, name, type) \
+	G_STMT_START { \
+		const NMPlatformLink *_pllink = (pllink); \
+		\
+		if ((success)) { \
+			g_assert (_pllink); \
+			g_assert (_pllink == nmtstp_link_get_typed (_pllink->ifindex, (name), (type))); \
+		} else { \
+			g_assert (!_pllink); \
+			g_assert (!nmtstp_link_get (0, (name))); \
+		} \
+	} G_STMT_END
+
+const NMPlatformLink *
+nmtstp_link_dummy_add (gboolean external_command,
+                       const char *name)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		success = !nmtstp_run_command ("ip link add %s type dummy",
+		                                name);
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_DUMMY, 100);
+	} else
+		success = nm_platform_link_dummy_add (NM_PLATFORM_GET, name, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	g_assert (success);
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_DUMMY);
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_gre_add (gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkGre *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		success = !nmtstp_run_command ("ip tunnel add %s mode gre %s local %s remote %s ttl %u tos %02x %s",
+		                                name,
+		                                dev ? dev : "",
+		                                nm_utils_inet4_ntop (lnk->local, NULL),
+		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                lnk->ttl,
+		                                lnk->tos,
+		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_GRE, 100);
+	} else
+		success = nm_platform_link_gre_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_GRE);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_ip6tnl_add (gboolean external_command,
+                        const char *name,
+                        const NMPlatformLnkIp6Tnl *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET6_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+		const char *mode;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		switch (lnk->proto) {
+		case IPPROTO_IPIP:
+			mode = "ipip6";
+			break;
+		case IPPROTO_IPV6:
+			mode = "ip6ip6";
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+
+		success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %02x encaplimit %u flowlabel %x",
+		                                name,
+		                                mode,
+		                                dev,
+		                                nm_utils_inet6_ntop (&lnk->local, NULL),
+		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                lnk->ttl,
+		                                lnk->tclass,
+		                                lnk->encap_limit,
+		                                lnk->flow_label);
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IP6TNL, 100);
+	} else
+		success = nm_platform_link_ip6tnl_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_IP6TNL);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_ipip_add (gboolean external_command,
+                      const char *name,
+                      const NMPlatformLnkIpIp *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		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),
+		                                lnk->ttl,
+		                                lnk->tos,
+		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IPIP, 100);
+	} else
+		success = nm_platform_link_ipip_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_IPIP);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_macvlan_add (gboolean external_command,
+                         const char *name,
+                         int parent,
+                         const NMPlatformLnkMacvlan *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	NMLinkType link_type;
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	link_type = lnk->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
+
+	if (external_command) {
+		const char *dev;
+		char *modes[] = {
+				[MACVLAN_MODE_BRIDGE]   = "bridge",
+				[MACVLAN_MODE_VEPA]     = "vepa",
+				[MACVLAN_MODE_PRIVATE]  = "private",
+				[MACVLAN_MODE_PASSTHRU] = "passthru",
+		};
+
+		dev = nm_platform_link_get_name (NM_PLATFORM_GET, parent);
+		g_assert (dev);
+		g_assert_cmpint (lnk->mode, <, G_N_ELEMENTS (modes));
+
+		success = !nmtstp_run_command ("ip link add name %s link %s type %s mode %s %s",
+		                                name,
+		                                dev,
+		                                lnk->tap ? "macvtap" : "macvlan",
+		                                modes[lnk->mode],
+		                                lnk->no_promisc ? "nopromisc" : "");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, link_type, 100);
+	} else
+		success = nm_platform_link_macvlan_add (NM_PLATFORM_GET, name, parent, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, link_type);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_sit_add (gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkSit *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *dev = "";
+
+		if (lnk->parent_ifindex) {
+			const char *parent_name;
+
+			parent_name = nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex);
+			g_assert (parent_name);
+			dev = nm_sprintf_bufa (100, " dev %s", parent_name);
+		}
+
+		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),
+		                                lnk->ttl,
+		                                lnk->tos,
+		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_SIT, 100);
+	} else
+		success = nm_platform_link_sit_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_SIT);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_vxlan_add (gboolean external_command,
+                       const char *name,
+                       const NMPlatformLnkVxlan *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	NMPlatformError plerr;
+	int err;
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+		gs_free char *local = NULL, *remote = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		if (lnk->local)
+			local = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->local, NULL));
+		else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any)))
+			local = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->local6, NULL));
+
+		if (lnk->group)
+			remote = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->group, NULL));
+		else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any)))
+			remote = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->group6, NULL));
+
+		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 ? dev : "",
+		                          local,
+		                          remote,
+		                          lnk->ttl,
+		                          lnk->tos,
+		                          lnk->dst_port,
+		                          lnk->src_port_min, lnk->src_port_max,
+		                          lnk->ageing);
+		/* Older versions of iproute2 don't support adding vxlan devices.
+		 * On failure, fallback to using platform code. */
+		if (err == 0)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_VXLAN, 100);
+		else
+			_LOGI ("Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task.");
+	}
+	if (!pllink) {
+		plerr = nm_platform_link_vxlan_add (NM_PLATFORM_GET, name, lnk, &pllink);
+		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (pllink);
+	}
+
+	g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_VXLAN);
+	g_assert_cmpstr (pllink->name, ==, name);
+	return pllink;
+}
+
+void
+nmtstp_ip4_address_add (gboolean external_command,
+                        int ifindex,
+                        in_addr_t address,
+                        int plen,
+                        in_addr_t peer_address,
+                        guint32 lifetime,
+                        guint32 preferred,
+                        const char *label)
+{
+	_ip_address_add (external_command,
+	                 TRUE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 (NMIPAddr *) &peer_address,
+	                 lifetime,
+	                 preferred,
+	                 label,
+	                 0);
+}
+
+void
+nmtstp_ip6_address_add (gboolean external_command,
+                        int ifindex,
+                        struct in6_addr address,
+                        int plen,
+                        struct in6_addr peer_address,
+                        guint32 lifetime,
+                        guint32 preferred,
+                        guint flags)
+{
+	_ip_address_add (external_command,
+	                 FALSE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 (NMIPAddr *) &peer_address,
+	                 lifetime,
+	                 preferred,
+	                 NULL,
+	                 flags);
+}
+
+static void
+_ip_address_del (gboolean external_command,
+                 gboolean is_v4,
+                 int ifindex,
+                 const NMIPAddr *address,
+                 int plen,
+                 const NMIPAddr *peer_address)
+{
+	gint64 end_time;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *ifname;
+		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+		int success;
+		gboolean had_address;
+
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		g_assert (ifname);
+
+		/* let's wait until we see the address as we added it. */
+		if (is_v4)
+			had_address = !!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4);
+		else
+			had_address = !!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen);
+
+		if (is_v4) {
+			success = nmtstp_run_command ("ip address delete %s%s%s/%d dev %s",
+			                              nm_utils_inet4_ntop (address->addr4, b1),
+			                              peer_address->addr4 != address->addr4 ? " peer " : "",
+			                              peer_address->addr4 != address->addr4 ? nm_utils_inet4_ntop (peer_address->addr4, b2) : "",
+			                              plen,
+			                              ifname);
+		} else {
+			g_assert (!peer_address);
+			success = nmtstp_run_command ("ip address delete %s/%d dev %s",
+			                              nm_utils_inet6_ntop (&address->addr6, b1),
+			                              plen,
+			                              ifname);
+		}
+		g_assert (success == 0 || !had_address);
+	} else {
+		gboolean success;
+
+		if (is_v4) {
+			success = nm_platform_ip4_address_delete (NM_PLATFORM_GET,
+			                                          ifindex,
+			                                          address->addr4,
+			                                          plen,
+			                                          peer_address->addr4);
+		} else {
+			g_assert (!peer_address);
+			success = nm_platform_ip6_address_delete (NM_PLATFORM_GET,
+			                                          ifindex,
+			                                          address->addr6,
+			                                          plen);
+		}
+		g_assert (success);
+	}
+
+	/* Let's wait until we get the result */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		/* let's wait until we see the address as we added it. */
+		if (is_v4) {
+			const NMPlatformIP4Address *a;
+
+			a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4);
+			if (!a)
+				break;
+		} else {
+			const NMPlatformIP6Address *a;
+
+			a = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen);
+			if (!a)
+				break;
+		}
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
+}
+
+void
+nmtstp_ip4_address_del (gboolean external_command,
+                        int ifindex,
+                        in_addr_t address,
+                        int plen,
+                        in_addr_t peer_address)
+{
+	_ip_address_del (external_command,
+	                 TRUE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 (NMIPAddr *) &peer_address);
+}
+
+void
+nmtstp_ip6_address_del (gboolean external_command,
+                        int ifindex,
+                        struct in6_addr address,
+                        int plen)
+{
+	_ip_address_del (external_command,
+	                 FALSE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 NULL);
+}
+
+const NMPlatformLink *
+nmtstp_link_get_typed (int ifindex,
+                       const char *name,
+                       NMLinkType link_type)
+{
+	const NMPlatformLink *pllink = NULL;
+
+	if (ifindex > 0) {
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+
+		if (pllink) {
+			g_assert_cmpint (pllink->ifindex, ==, ifindex);
+			if (name)
+				g_assert_cmpstr (name, ==, pllink->name);
+		} else {
+			if (name)
+				g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name));
+		}
+	} else {
+		g_assert (name);
+
+		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+
+		if (pllink)
+			g_assert_cmpstr (name, ==, pllink->name);
+	}
+
+	g_assert (!name || nm_utils_iface_valid_name (name));
+
+	if (pllink && link_type != NM_LINK_TYPE_NONE)
+		g_assert_cmpint (pllink->type, ==, link_type);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_get (int ifindex,
+                 const char *name)
+{
+	return nmtstp_link_get_typed (ifindex, name, NM_LINK_TYPE_NONE);
+}
+
+void
+nmtstp_link_del (gboolean external_command,
+                 int ifindex,
+                 const char *name)
+{
+	gint64 end_time;
+	const NMPlatformLink *pllink;
+	gboolean success;
+	gs_free char *name_copy = NULL;
+
+	pllink = nmtstp_link_get (ifindex, name);
+
+	g_assert (pllink);
+
+	name = name_copy = g_strdup (pllink->name);
+	ifindex = pllink->ifindex;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		nmtstp_run_command_check ("ip link delete %s", name);
+	} else {
+		success = nm_platform_link_delete (NM_PLATFORM_GET, ifindex);
+		g_assert (success);
+	}
+
+	/* Let's wait until we get the result */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		if (!nm_platform_link_get (NM_PLATFORM_GET, ifindex)) {
+			g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name));
+			break;
+		}
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
 }
 
+void
+nmtstp_link_set_updown (gboolean external_command,
+                        int ifindex,
+                        gboolean up)
+{
+	const NMPlatformLink *plink;
+	gint64 end_time;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *ifname;
+
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		g_assert (ifname);
+
+		nmtstp_run_command_check ("ip link set %s %s",
+		                          ifname,
+		                          up ? "up" : "down");
+	} else {
+		if (up)
+			g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+		else
+			g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex));
+	}
+
+	/* Let's wait until we get the result */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		/* let's wait until we see the address as we added it. */
+		plink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+		g_assert (plink);
+
+		if (NM_FLAGS_HAS (plink->flags, IFF_UP) == !!up)
+			break;
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
+}
+
+/*****************************************************************************/
+
+struct _NMTstpNamespaceHandle {
+	pid_t pid;
+	int pipe_fd;
+};
+
+NMTstpNamespaceHandle *
+nmtstp_namespace_create (int unshare_flags, GError **error)
+{
+	NMTstpNamespaceHandle *ns_handle;
+	int e;
+	int errsv;
+	pid_t pid, pid2;
+	int pipefd_c2p[2];
+	int pipefd_p2c[2];
+	ssize_t r;
+
+	e = pipe (pipefd_c2p);
+	if (e != 0) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		return FALSE;
+	}
+
+	e = pipe (pipefd_p2c);
+	if (e != 0) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		close (pipefd_c2p[0]);
+		close (pipefd_c2p[1]);
+		return FALSE;
+	}
+
+	pid = fork ();
+	if (pid < 0) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "fork() failed with %d (%s)", errsv, strerror (errsv));
+		close (pipefd_c2p[0]);
+		close (pipefd_c2p[1]);
+		close (pipefd_p2c[0]);
+		close (pipefd_p2c[1]);
+		return FALSE;
+	}
+
+	if (pid == 0) {
+		char read_buf[1];
+
+		close (pipefd_c2p[0]); /* close read-end */
+		close (pipefd_p2c[1]); /* close write-end */
+
+		if (unshare (unshare_flags) != 0) {
+			errsv = errno;
+			if (errsv == 0)
+				errsv = -1;
+		} else
+			errsv = 0;
+
+		/* sync with parent process and send result. */
+		do {
+			r = write (pipefd_c2p[1], &errsv, sizeof (errsv));
+		} while (r < 0 && errno == EINTR);
+		if (r != sizeof (errsv)) {
+			errsv = errno;
+			if (errsv == 0)
+				errsv = -2;
+		}
+		close (pipefd_c2p[1]);
+
+		/* wait until parent process terminates (or kills us). */
+		if (errsv == 0) {
+			do {
+				r = read (pipefd_p2c[0], read_buf, sizeof (read_buf));
+			} while (r < 0 && errno == EINTR);
+		}
+		close (pipefd_p2c[0]);
+		_exit (0);
+	}
+
+	close (pipefd_c2p[1]); /* close write-end */
+	close (pipefd_p2c[0]); /* close read-end */
+
+	/* sync with child process. */
+	do {
+		r = read (pipefd_c2p[0], &errsv, sizeof (errsv));
+	} while (r < 0 && errno == EINTR);
+
+	close (pipefd_c2p[0]);
+
+	if (   r != sizeof (errsv)
+	    || errsv != 0) {
+		int status;
+
+		if (r != sizeof (errsv)) {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "child process failed for unknown reason");
+		} else {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "child process signaled failure %d (%s)", errsv, strerror (errsv));
+		}
+		close (pipefd_p2c[1]);
+		kill (pid, SIGKILL);
+		do {
+			pid2 = waitpid (pid, &status, 0);
+		} while (pid2 == -1 && errno == EINTR);
+		return FALSE;
+	}
+
+	ns_handle = g_new0 (NMTstpNamespaceHandle, 1);
+	ns_handle->pid = pid;
+	ns_handle->pipe_fd = pipefd_p2c[1];
+	return ns_handle;
+}
+
+pid_t
+nmtstp_namespace_handle_get_pid (NMTstpNamespaceHandle *ns_handle)
+{
+	g_return_val_if_fail (ns_handle, 0);
+	g_return_val_if_fail (ns_handle->pid > 0, 0);
+
+	return ns_handle->pid;
+}
+
+void
+nmtstp_namespace_handle_release (NMTstpNamespaceHandle *ns_handle)
+{
+	pid_t pid;
+	int status;
+
+	if (!ns_handle)
+		return;
+
+	g_return_if_fail (ns_handle->pid > 0);
+
+	close (ns_handle->pipe_fd);
+	ns_handle->pipe_fd = 0;
+
+	kill (ns_handle->pid, SIGKILL);
+
+	do {
+		pid = waitpid (ns_handle->pid, &status, 0);
+	} while (pid == -1 && errno == EINTR);
+	ns_handle->pid = 0;
+
+	g_free (ns_handle);
+}
+
+int
+nmtstp_namespace_get_fd_for_process (pid_t pid, const char *ns_name)
+{
+	char p[1000];
+
+	g_return_val_if_fail (pid > 0, 0);
+	g_return_val_if_fail (ns_name && ns_name[0] && strlen (ns_name) < 50, 0);
+
+	nm_sprintf_buf (p, "/proc/%lu/ns/%s", (long unsigned) pid, ns_name);
+
+	return open(p, O_RDONLY);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 static gboolean
@@ -318,7 +1487,7 @@ main (int argc, char **argv)
 
 	init_tests (&argc, &argv);
 
-	if (   nmtst_platform_is_root_test ()
+	if (   nmtstp_is_root_test ()
 	    && (geteuid () != 0 || getegid () != 0)) {
 		if (   g_getenv ("NMTST_FORCE_REAL_ROOT")
 		    || !unshare_user ()) {
@@ -335,7 +1504,7 @@ main (int argc, char **argv)
 		}
 	}
 
-	if (nmtst_platform_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) {
+	if (nmtstp_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) {
 		int errsv;
 
 		if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) {
@@ -345,10 +1514,7 @@ main (int argc, char **argv)
 
 		/* Mount our /sys instance, so that gudev sees only our devices.
 		 * Needs to be read-only, because we don't run udev. */
-		if (mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL) != 0) {
-			errsv = errno;
-			g_error ("mount(\"/\", MS_SLAVE) failed with %s (%d)", strerror (errsv), errsv);
-		}
+		mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL);
 		if (mount ("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) {
 			errsv = errno;
 			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
@@ -383,6 +1549,6 @@ main (int argc, char **argv)
 
 	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
-	g_object_unref (nm_platform_get ());
+	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 3d647543..0281d3c5 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -4,7 +4,7 @@
 #include <string.h>
 #include <arpa/inet.h>
 
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "nm-platform.h"
 #include "nm-fake-platform.h"
 #include "nm-linux-platform.h"
@@ -13,10 +13,33 @@
 
 #define DEVICE_NAME "nm-test-device"
 
-#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
+/*********************************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "platform-test"
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN, __VA_ARGS__)
+
+#define _LOG(level, domain, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            gint64 _ts = nm_utils_get_monotonic_timestamp_ns (); \
+            \
+            _nm_log (__level, __domain, 0, \
+                     "%s[%ld.%09ld]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (long) (_ts / NM_UTILS_NS_PER_SECOND), \
+                     (long) (_ts % NM_UTILS_NS_PER_SECOND) \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*********************************************************************************************/
 
 typedef struct {
-	int handler_id;
+	gulong handler_id;
 	const char *name;
 	NMPlatformSignalChangeType change_type;
 	gint received_count;
@@ -25,8 +48,21 @@ typedef struct {
 	const char *ifname;
 } SignalData;
 
-gboolean nmtst_platform_is_root_test (void);
-gboolean nmtst_platform_is_sysfs_writable (void);
+gboolean nmtstp_is_root_test (void);
+gboolean nmtstp_is_sysfs_writable (void);
+
+/******************************************************************************/
+
+typedef struct _NMTstpNamespaceHandle NMTstpNamespaceHandle;
+
+NMTstpNamespaceHandle *nmtstp_namespace_create (int flags, GError **error);
+
+void nmtstp_namespace_handle_release (NMTstpNamespaceHandle *handle);
+pid_t nmtstp_namespace_handle_get_pid (NMTstpNamespaceHandle *handle);
+
+int nmtstp_namespace_get_fd_for_process (pid_t pid, const char *ns_name);
+
+/******************************************************************************/
 
 SignalData *add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCallback callback, int ifindex, const char *ifname);
 #define add_signal(name, change_type, callback) add_signal_full (name, change_type, (GCallback) callback, 0, NULL)
@@ -35,11 +71,13 @@ SignalData *add_signal_full (const char *name, NMPlatformSignalChangeType change
 void _accept_signal (const char *file, int line, const char *func, SignalData *data);
 void _accept_signals (const char *file, int line, const char *func, SignalData *data, int min, int max);
 void _wait_signal (const char *file, int line, const char *func, SignalData *data);
+void _accept_or_wait_signal (const char *file, int line, const char *func, SignalData *data);
 void _ensure_no_signal (const char *file, int line, const char *func, SignalData *data);
 void _free_signal (const char *file, int line, const char *func, SignalData *data);
 #define accept_signal(data) _accept_signal(__FILE__, __LINE__, G_STRFUNC, data)
 #define accept_signals(data, min, max) _accept_signals(__FILE__, __LINE__, G_STRFUNC, data, min, max)
 #define wait_signal(data) _wait_signal(__FILE__, __LINE__, G_STRFUNC, data)
+#define accept_or_wait_signal(data) _accept_or_wait_signal(__FILE__, __LINE__, G_STRFUNC, data)
 #define ensure_no_signal(data) _ensure_no_signal(__FILE__, __LINE__, G_STRFUNC, data)
 #define free_signal(data) _free_signal(__FILE__, __LINE__, G_STRFUNC, data)
 
@@ -48,9 +86,88 @@ gboolean ip4_route_exists (const char *ifname, guint32 network, int plen, guint3
 void _assert_ip4_route_exists (const char *file, guint line, const char *func, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric);
 #define assert_ip4_route_exists(exists, ifname, network, plen, metric) _assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, exists, ifname, network, plen, metric)
 
-void link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data);
+void link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, SignalData *data);
+
+int nmtstp_run_command (const char *format, ...) __attribute__((__format__ (__printf__, 1, 2)));
+#define nmtstp_run_command_check(...) do { g_assert_cmpint (nmtstp_run_command (__VA_ARGS__), ==, 0); } while (0)
+
+gboolean nmtstp_wait_for_signal (guint timeout_ms);
+gboolean nmtstp_wait_for_signal_until (gint64 until_ms);
+const NMPlatformLink *nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+
+const NMPlatformLink *nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+
+int nmtstp_run_command_check_external_global (void);
+gboolean nmtstp_run_command_check_external (int external_command);
+
+gboolean nmtstp_ip_address_check_lifetime (const NMPlatformIPAddress *addr,
+                                           gint64 now,
+                                           guint32 expected_lifetime,
+                                           guint32 expected_preferred);
+void nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
+                                        gint64 now,
+                                        guint32 expected_lifetime,
+                                        guint32 expected_preferred);
+void nmtstp_ip4_address_add (gboolean external_command,
+                             int ifindex,
+                             in_addr_t address,
+                             int plen,
+                             in_addr_t peer_address,
+                             guint32 lifetime,
+                             guint32 preferred,
+                             const char *label);
+void nmtstp_ip6_address_add (gboolean external_command,
+                             int ifindex,
+                             struct in6_addr address,
+                             int plen,
+                             struct in6_addr peer_address,
+                             guint32 lifetime,
+                             guint32 preferred,
+                             guint flags);
+void nmtstp_ip4_address_del (gboolean external_command,
+                             int ifindex,
+                             in_addr_t address,
+                             int plen,
+                             in_addr_t peer_address);
+void nmtstp_ip6_address_del (gboolean external_command,
+                             int ifindex,
+                             struct in6_addr address,
+                             int plen);
+
+const NMPlatformLink *nmtstp_link_get_typed (int ifindex, const char *name, NMLinkType link_type);
+const NMPlatformLink *nmtstp_link_get (int ifindex, const char *name);
+
+void nmtstp_link_set_updown (gboolean external_command,
+                             int ifindex,
+                             gboolean up);
+
+const NMPlatformLink *nmtstp_link_dummy_add (gboolean external_command,
+                                             const char *name);
+const NMPlatformLink *nmtstp_link_gre_add (gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkGre *lnk);
+const NMPlatformLink *nmtstp_link_ip6tnl_add (gboolean external_command,
+                                              const char *name,
+                                              const NMPlatformLnkIp6Tnl *lnk);
+const NMPlatformLink *nmtstp_link_ipip_add (gboolean external_command,
+                                            const char *name,
+                                            const NMPlatformLnkIpIp *lnk);
+const NMPlatformLink *nmtstp_link_macvlan_add (gboolean external_command,
+                                               const char *name,
+                                               int parent,
+                                               const NMPlatformLnkMacvlan *lnk);
+const NMPlatformLink *nmtstp_link_sit_add (gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkSit *lnk);
+const NMPlatformLink *nmtstp_link_vxlan_add (gboolean external_command,
+                                             const char *name,
+                                             const NMPlatformLnkVxlan *lnk);
 
-void run_command (const char *format, ...);
+void nmtstp_link_del (gboolean external_command,
+                      int ifindex,
+                      const char *name);
 
 void init_tests (int *argc, char ***argv);
 void setup_tests (void);
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index 0a397b37..f342e565 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -23,7 +23,7 @@
 #include <linux/rtnetlink.h>
 
 #include "nm-linux-platform.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 
 #include "nm-test-utils.h"
 
@@ -65,3 +65,4 @@ main (int argc, char **argv)
 
 	return g_test_run ();
 }
+
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 47cbcec1..8b496262 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1,5 +1,9 @@
 #include "config.h"
 
+#include <sched.h>
+
+#include "nmp-object.h"
+
 #include "test-common.h"
 #include "nm-test-utils.h"
 
@@ -28,10 +32,18 @@ test_bogus(void)
 	g_assert (!nm_platform_link_get_type (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_get_type_name (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_up (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_down (NM_PLATFORM_GET, BOGUS_IFINDEX));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_noarp (NM_PLATFORM_GET, BOGUS_IFINDEX));
+
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -39,15 +51,18 @@ test_bogus(void)
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, &addrlen));
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
+
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
 	g_assert (!nm_platform_link_supports_carrier_detect (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_supports_vlans (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_assert (!nm_platform_vlan_get_info (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL, NULL));
-	g_assert (!nm_platform_vlan_set_ingress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
-	g_assert (!nm_platform_vlan_set_egress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
+	g_assert (!nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+	g_assert (!nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
+	g_assert (!nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
 }
 
 static void
@@ -68,15 +83,15 @@ software_add (NMLinkType link_type, const char *name)
 {
 	switch (link_type) {
 	case NM_LINK_TYPE_DUMMY:
-		return nm_platform_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return nm_platform_link_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_BRIDGE:
-		return nm_platform_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return nm_platform_link_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_BOND:
 		{
 			gboolean bond0_exists = !!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0");
 			NMPlatformError plerr;
 
-			plerr = nm_platform_bond_add (NM_PLATFORM_GET, name, NULL);
+			plerr = nm_platform_link_bond_add (NM_PLATFORM_GET, name, NULL);
 
 			/* Check that bond0 is *not* automatically created. */
 			if (!bond0_exists)
@@ -84,14 +99,14 @@ software_add (NMLinkType link_type, const char *name)
 			return plerr == NM_PLATFORM_ERROR_SUCCESS;
 		}
 	case NM_LINK_TYPE_TEAM:
-		return nm_platform_team_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return nm_platform_link_team_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	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_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
+		if (nm_platform_link_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
 			accept_signal (parent_added);
 		free_signal (parent_added);
 
@@ -108,7 +123,7 @@ software_add (NMLinkType link_type, const char *name)
 				accept_signal (parent_changed);
 			free_signal (parent_changed);
 
-			return nm_platform_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+			return nm_platform_link_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 		}
 	}
 	default:
@@ -123,7 +138,6 @@ test_link_changed_signal_cb (NMPlatform *platform,
                              int ifindex,
                              const NMPlatformIP4Route *route,
                              NMPlatformSignalChangeType change_type,
-                             NMPlatformReason reason,
                              gboolean *p_test_link_changed_signal_arg)
 {
 	/* test invocation of platform signals with multiple listeners
@@ -146,9 +160,6 @@ test_link_changed_signal_cb (NMPlatform *platform,
 	g_assert_cmpint ((gint64) change_type, !=, (gint64) 0);
 	g_assert_cmpint (change_type, !=, NM_PLATFORM_SIGNAL_NONE);
 
-	g_assert_cmpint ((gint64) reason, !=, (gint64) 0);
-	g_assert_cmpint (reason, !=, NM_PLATFORM_REASON_NONE);
-
 	*p_test_link_changed_signal_arg = TRUE;
 }
 
@@ -262,9 +273,9 @@ test_slave (int master, int type, SignalData *master_changed)
 	/* Set slave option */
 	switch (type) {
 	case NM_LINK_TYPE_BRIDGE:
-		if (nmtst_platform_is_sysfs_writable ()) {
-			g_assert (nm_platform_slave_set_option (NM_PLATFORM_GET, ifindex, "priority", "789"));
-			value = nm_platform_slave_get_option (NM_PLATFORM_GET, ifindex, "priority");
+		if (nmtstp_is_sysfs_writable ()) {
+			g_assert (nm_platform_sysctl_slave_set_option (NM_PLATFORM_GET, ifindex, "priority", "789"));
+			value = nm_platform_sysctl_slave_get_option (NM_PLATFORM_GET, ifindex, "priority");
 			g_assert_cmpstr (value, ==, "789");
 			g_free (value);
 		}
@@ -296,7 +307,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);
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	nmtstp_link_del (-1, ifindex, NULL);
 	accept_signals (master_changed, 0, 1);
 	accept_signals (link_changed, 0, 1);
 	accept_signal (link_removed);
@@ -311,7 +322,7 @@ test_software (NMLinkType link_type, const char *link_typename)
 {
 	int ifindex;
 	char *value;
-	int vlan_parent, vlan_id;
+	int vlan_parent = -1, vlan_id;
 
 	SignalData *link_added, *link_changed, *link_removed;
 
@@ -327,7 +338,15 @@ test_software (NMLinkType link_type, const char *link_typename)
 	link_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, link_callback, ifindex);
 	link_removed = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, link_callback, ifindex);
 	if (link_type == NM_LINK_TYPE_VLAN) {
-		g_assert (nm_platform_vlan_get_info (NM_PLATFORM_GET, ifindex, &vlan_parent, &vlan_id));
+		const NMPlatformLink *plink;
+		const NMPlatformLnkVlan *plnk;
+
+		plnk = nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, &plink);
+		g_assert (plnk);
+		g_assert (plink);
+
+		vlan_parent = plink->parent;
+		vlan_id = plnk->id;
 		g_assert_cmpint (vlan_parent, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME));
 		g_assert_cmpint (vlan_id, ==, VLAN_ID);
 	}
@@ -347,17 +366,17 @@ test_software (NMLinkType link_type, const char *link_typename)
 	/* Set master option */
 	switch (link_type) {
 	case NM_LINK_TYPE_BRIDGE:
-		if (nmtst_platform_is_sysfs_writable ()) {
-			g_assert (nm_platform_master_set_option (NM_PLATFORM_GET, ifindex, "forward_delay", "789"));
-			value = nm_platform_master_get_option (NM_PLATFORM_GET, ifindex, "forward_delay");
+		if (nmtstp_is_sysfs_writable ()) {
+			g_assert (nm_platform_sysctl_master_set_option (NM_PLATFORM_GET, ifindex, "forward_delay", "789"));
+			value = nm_platform_sysctl_master_get_option (NM_PLATFORM_GET, ifindex, "forward_delay");
 			g_assert_cmpstr (value, ==, "789");
 			g_free (value);
 		}
 		break;
 	case NM_LINK_TYPE_BOND:
-		if (nmtst_platform_is_sysfs_writable ()) {
-			g_assert (nm_platform_master_set_option (NM_PLATFORM_GET, ifindex, "mode", "active-backup"));
-			value = nm_platform_master_get_option (NM_PLATFORM_GET, ifindex, "mode");
+		if (nmtstp_is_sysfs_writable ()) {
+			g_assert (nm_platform_sysctl_master_set_option (NM_PLATFORM_GET, ifindex, "mode", "active-backup"));
+			value = nm_platform_sysctl_master_get_option (NM_PLATFORM_GET, ifindex, "mode");
 			/* When reading back, the output looks slightly different. */
 			g_assert (g_str_has_prefix (value, "active-backup"));
 			g_free (value);
@@ -382,20 +401,18 @@ test_software (NMLinkType link_type, const char *link_typename)
 	free_signal (link_changed);
 
 	/* Delete */
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert_cmpint (nm_platform_link_get_type (NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_NONE);
-	g_assert (!nm_platform_link_get_type (NM_PLATFORM_GET, ifindex));
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
 	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_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);
 
-		g_assert (nm_platform_link_delete (NM_PLATFORM_GET, vlan_parent));
+		nmtstp_link_del (-1, vlan_parent, NULL);
 		accept_signal (link_removed_parent);
 		free_signal (link_removed_parent);
 	}
@@ -414,7 +431,7 @@ test_bridge (void)
 static void
 test_bond (void)
 {
-	if (nmtst_platform_is_root_test () &&
+	if (nmtstp_is_root_test () &&
 	    !g_file_test ("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) &&
 	    system("modprobe --show bonding") != 0) {
 		g_test_skip ("Skipping test for bonding: bonding module not available");
@@ -436,6 +453,53 @@ test_vlan (void)
 	test_software (NM_LINK_TYPE_VLAN, "vlan");
 }
 
+/*****************************************************************************/
+
+static void
+test_bridge_addr (void)
+{
+	char addr[ETH_ALEN];
+	NMPlatformLink link;
+	const NMPlatformLink *plink = NULL;
+
+	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 (plink);
+	link = *plink;
+	g_assert_cmpstr (link.name, ==, DEVICE_NAME);
+
+	g_assert_cmpint (link.addr.len, ==, sizeof (addr));
+	g_assert (!memcmp (link.addr.data, addr, sizeof (addr)));
+
+	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
+	g_assert (plink);
+
+	if (nm_platform_check_support_user_ipv6ll (NM_PLATFORM_GET)) {
+		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));
+		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));
+		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_EUI64);
+	}
+
+	g_assert_cmpint (plink->addr.len, ==, sizeof (addr));
+	g_assert (!memcmp (plink->addr.data, addr, sizeof (addr)));
+
+	nmtstp_link_del (-1, link.ifindex, link.name);
+}
+
+/*****************************************************************************/
+
 static void
 test_internal (void)
 {
@@ -451,11 +515,11 @@ test_internal (void)
 	g_assert (!nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
 	/* Add device */
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
 	accept_signal (link_added);
 
 	/* Try to add again */
-	g_assert (nm_platform_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) == NM_PLATFORM_ERROR_EXISTS);
 
 	/* Check device index, name and type */
 	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
@@ -506,7 +570,7 @@ test_internal (void)
 	accept_signal (link_changed);
 
 	/* Delete device */
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
 	accept_signal (link_removed);
 
 	/* Try to delete again */
@@ -517,15 +581,18 @@ test_internal (void)
 	free_signal (link_removed);
 }
 
+/*****************************************************************************/
+
 static void
 test_external (void)
 {
 	const NMPlatformLink *pllink;
-	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
-	SignalData *link_changed, *link_removed;
+	SignalData *link_added, *link_changed, *link_removed;
 	int ifindex;
 
-	run_command ("ip link add %s type %s", DEVICE_NAME, "dummy");
+	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
+
+	nmtstp_run_command_check ("ip link add %s type %s", DEVICE_NAME, "dummy");
 	wait_signal (link_added);
 
 	g_assert (nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
@@ -549,24 +616,24 @@ test_external (void)
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 
-	run_command ("ip link set %s up", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s up", DEVICE_NAME);
 	wait_signal (link_changed);
 
 	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
-	run_command ("ip link set %s down", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s down", DEVICE_NAME);
 	wait_signal (link_changed);
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 
-	run_command ("ip link set %s arp on", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s arp on", DEVICE_NAME);
 	wait_signal (link_changed);
 	g_assert (nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
-	run_command ("ip link set %s arp off", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s arp off", DEVICE_NAME);
 	wait_signal (link_changed);
 	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 
-	run_command ("ip link del %s", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link del %s", DEVICE_NAME);
 	wait_signal (link_removed);
 	accept_signals (link_changed, 0, 1);
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
@@ -576,6 +643,1183 @@ test_external (void)
 	free_signal (link_removed);
 }
 
+/*****************************************************************************/
+
+typedef struct {
+	NMLinkType link_type;
+	int test_mode;
+	gboolean external_command;
+} TestAddSoftwareDetectData;
+
+static void
+test_software_detect (gconstpointer user_data)
+{
+	const TestAddSoftwareDetectData *test_data = user_data;
+	int ifindex, ifindex_parent;
+	const NMPlatformLink *plink;
+	const NMPObject *lnk;
+	guint i_step;
+	const gboolean ext = test_data->external_command;
+
+	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
+	ifindex_parent = nmtstp_assert_wait_for_link (PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	switch (test_data->link_type) {
+	case NM_LINK_TYPE_GRE: {
+		NMPlatformLnkGre lnk_gre = { };
+		gboolean gracefully_skip = FALSE;
+
+		lnk_gre.local = nmtst_inet4_from_string ("192.168.233.204");
+		lnk_gre.remote = nmtst_inet4_from_string ("172.168.10.25");
+		lnk_gre.parent_ifindex = ifindex_parent;
+		lnk_gre.ttl = 174;
+		lnk_gre.tos = 37;
+		lnk_gre.path_mtu_discovery = TRUE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "gre0")) {
+			/* Seems that the ip_gre module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip_gre", NULL) != 0;
+		}
+
+		if (!nmtstp_link_gre_add (ext, DEVICE_NAME, &lnk_gre)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create gre tunnel because of missing ip_gre module (modprobe ip_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding GRE tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_IPIP: {
+		NMPlatformLnkIpIp lnk_ipip = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "tunl0")) {
+			/* Seems that the ipip module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ipip", NULL) != 0;
+		}
+
+		lnk_ipip.local = nmtst_inet4_from_string ("1.2.3.4");
+		lnk_ipip.remote = nmtst_inet4_from_string ("5.6.7.8");
+		lnk_ipip.parent_ifindex = ifindex_parent;
+		lnk_ipip.tos = 32;
+		lnk_ipip.path_mtu_discovery = FALSE;
+
+		if (!nmtstp_link_ipip_add (ext, DEVICE_NAME, &lnk_ipip)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ipip tunnel because of missing ipip module (modprobe ipip)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IPIP tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_IP6TNL: {
+		NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "ip6tnl0")) {
+			/* Seems that the ip6_tunnel module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_tunnel", NULL) != 0;
+		}
+
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.tclass = 20;
+		lnk_ip6tnl.encap_limit = 6;
+		lnk_ip6tnl.flow_label = 1337;
+		lnk_ip6tnl.proto = IPPROTO_IPV6;
+
+		if (!nmtstp_link_ip6tnl_add (ext, DEVICE_NAME, &lnk_ip6tnl)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ip6tnl tunnel because of missing ip6_tunnel module (modprobe ip6_tunnel)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IP6TNL tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_MACVLAN: {
+		NMPlatformLnkMacvlan lnk_macvlan = { };
+
+		lnk_macvlan.mode = MACVLAN_MODE_BRIDGE;
+		lnk_macvlan.no_promisc = FALSE;
+		lnk_macvlan.tap = FALSE;
+
+		if (!nmtstp_link_macvlan_add (ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan))
+			g_error ("Failed adding MACVLAN interface");
+		break;
+	}
+	case NM_LINK_TYPE_MACVTAP: {
+		NMPlatformLnkMacvtap lnk_macvtap = { };
+
+		lnk_macvtap.mode = MACVLAN_MODE_PRIVATE;
+		lnk_macvtap.no_promisc = FALSE;
+		lnk_macvtap.tap = TRUE;
+
+		if (!nmtstp_link_macvlan_add (ext, DEVICE_NAME, ifindex_parent, &lnk_macvtap))
+			g_error ("Failed adding MACVTAP interface");
+		break;
+	}
+	case NM_LINK_TYPE_SIT: {
+		NMPlatformLnkSit lnk_sit = { };
+		gboolean gracefully_skip = FALSE;
+
+		lnk_sit.local = nmtst_inet4_from_string ("192.168.200.1");
+		lnk_sit.remote = nmtst_inet4_from_string ("172.25.100.14");
+		lnk_sit.parent_ifindex = ifindex_parent;
+		lnk_sit.ttl = 0;
+		lnk_sit.tos = 31;
+		lnk_sit.path_mtu_discovery = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "sit0")) {
+			/* Seems that the sit module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "sit", NULL) != 0;
+		}
+
+		if (!nmtstp_link_sit_add (ext, DEVICE_NAME, &lnk_sit)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create sit tunnel because of missing sit module (modprobe sit)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding SIT tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_VLAN:
+		nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1242", DEVICE_NAME, PARENT_NAME);
+		break;
+	case NM_LINK_TYPE_VXLAN: {
+		NMPlatformLnkVxlan lnk_vxlan = { };
+
+		switch (test_data->test_mode) {
+		case 0:
+			lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME);
+			lnk_vxlan.id = 42;
+			lnk_vxlan.local = nmtst_inet4_from_string ("23.1.2.164");
+			lnk_vxlan.group = nmtst_inet4_from_string ("239.1.2.134");
+			lnk_vxlan.dst_port = 4789;
+			lnk_vxlan.learning = TRUE;
+			lnk_vxlan.ageing = 1245;
+			break;
+		case 1:
+			lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME);
+			lnk_vxlan.id = 11214423;
+			lnk_vxlan.local6 = *nmtst_inet6_from_string ("1:2:3:4:334:23::23");
+			lnk_vxlan.group6 = *nmtst_inet6_from_string ("ff0e::115");
+			lnk_vxlan.ttl = 32;
+			lnk_vxlan.dst_port = 57412;
+			lnk_vxlan.src_port_min = 1000;
+			lnk_vxlan.src_port_max = 1003;
+			lnk_vxlan.learning = TRUE;
+			lnk_vxlan.ageing = 3245;
+			break;
+		}
+
+		g_assert (nmtstp_link_vxlan_add (ext, DEVICE_NAME, &lnk_vxlan));
+		break;
+	}
+	default:
+		g_assert_not_reached ();
+	}
+
+	ifindex = nmtstp_assert_wait_for_link (DEVICE_NAME, test_data->link_type, 100)->ifindex;
+
+	nmtstp_link_set_updown (-1, ifindex_parent, TRUE);
+
+	for (i_step = 0; i_step < 5; i_step++) {
+
+		_LOGD ("test-software-detect: step %u", i_step);
+		if (nmtst_is_debug ())
+			nmtstp_run_command_check ("ip -d link show %s", DEVICE_NAME);
+
+		if (i_step > 0) {
+			gboolean set_up = (i_step % 2) == 1;
+
+			if (   test_data->link_type == NM_LINK_TYPE_VXLAN
+			    && set_up) {
+				/* On RHEL-7, we need to add a tiny sleep here, otherwise,
+				 * upping the vxlan device fails with EADDRINUSE.
+				 * https://bugzilla.redhat.com/show_bug.cgi?id=1277131 */
+				g_usleep (1);
+			}
+			nmtstp_link_set_updown (-1, ifindex, set_up);
+		}
+
+		lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, test_data->link_type, &plink);
+		g_assert (plink);
+		g_assert_cmpint (plink->ifindex, ==, ifindex);
+		g_assert (lnk);
+
+		switch (test_data->link_type) {
+		case NM_LINK_TYPE_GRE: {
+			const NMPlatformLnkGre *plnk = &lnk->lnk_gre;
+
+			g_assert (plnk == nm_platform_link_get_lnk_gre (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			g_assert_cmpint (plnk->input_flags, ==, 0);
+			g_assert_cmpint (plnk->output_flags, ==, 0);
+			g_assert_cmpint (plnk->input_key, ==, 0);
+			g_assert_cmpint (plnk->output_key, ==, 0);
+			nmtst_assert_ip4_address (plnk->local, "192.168.233.204");
+			nmtst_assert_ip4_address (plnk->remote, "172.168.10.25");
+			g_assert_cmpint (plnk->ttl, ==, 174);
+			g_assert_cmpint (plnk->tos, ==, 37);
+			g_assert_cmpint (plnk->path_mtu_discovery, ==, TRUE);
+			break;
+		}
+		case NM_LINK_TYPE_IP6TNL: {
+			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fd01::15");
+			nmtst_assert_ip6_address (&plnk->remote, "fd01::16");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tclass, ==, 20);
+			g_assert_cmpint (plnk->encap_limit, ==, 6);
+			g_assert_cmpint (plnk->flow_label, ==, 1337);
+			g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
+			break;
+		}
+		case NM_LINK_TYPE_IPIP: {
+			const NMPlatformLnkIpIp *plnk = &lnk->lnk_ipip;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ipip (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip4_address (plnk->local, "1.2.3.4");
+			nmtst_assert_ip4_address (plnk->remote, "5.6.7.8");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tos, ==, 32);
+			g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE);
+			break;
+		}
+		case NM_LINK_TYPE_MACVLAN: {
+			const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_macvlan (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->no_promisc, ==, FALSE);
+			g_assert_cmpint (plnk->mode, ==, MACVLAN_MODE_BRIDGE);
+			break;
+		}
+		case NM_LINK_TYPE_MACVTAP: {
+			const NMPlatformLnkMacvtap *plnk = &lnk->lnk_macvlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_macvtap (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->no_promisc, ==, FALSE);
+			g_assert_cmpint (plnk->mode, ==, MACVLAN_MODE_PRIVATE);
+			break;
+		}
+		case NM_LINK_TYPE_SIT: {
+			const NMPlatformLnkSit *plnk = &lnk->lnk_sit;
+
+			g_assert (plnk == nm_platform_link_get_lnk_sit (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip4_address (plnk->local, "192.168.200.1");
+			nmtst_assert_ip4_address (plnk->remote, "172.25.100.14");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tos, ==, 31);
+			g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE);
+			break;
+		}
+		case NM_LINK_TYPE_VLAN: {
+			const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->id, ==, 1242);
+			break;
+		}
+		case NM_LINK_TYPE_VXLAN: {
+			const NMPlatformLnkVxlan *plnk = &lnk->lnk_vxlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_vxlan (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, !=, 0);
+			g_assert_cmpint (plnk->tos, ==, 0);
+			g_assert_cmpint (plnk->learning, ==, TRUE);
+			g_assert_cmpint (plnk->limit, ==, 0);
+			g_assert_cmpint (plnk->proxy, ==, FALSE);
+			g_assert_cmpint (plnk->rsc, ==, FALSE);
+			g_assert_cmpint (plnk->l2miss, ==, FALSE);
+			g_assert_cmpint (plnk->l3miss, ==, FALSE);
+
+			switch (test_data->test_mode) {
+			case 0:
+				g_assert_cmpint (plnk->id, ==, 42);
+				nmtst_assert_ip4_address (plnk->local, "23.1.2.164");
+				nmtst_assert_ip4_address (plnk->group, "239.1.2.134");
+				nmtst_assert_ip6_address (&plnk->group6, "::");
+				nmtst_assert_ip6_address (&plnk->local6, "::");
+				g_assert_cmpint (plnk->ttl, ==, 0);
+				g_assert_cmpint (plnk->ageing, ==, 1245);
+				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
+					 * following default port range. */
+					g_assert_cmpint (plnk->src_port_min, ==, 32768);
+					g_assert_cmpint (plnk->src_port_max, ==, 61000);
+				}
+				break;
+			case 1:
+				g_assert_cmpint (plnk->id, ==, 11214423);
+				nmtst_assert_ip4_address (plnk->local, "0.0.0.0");
+				nmtst_assert_ip4_address (plnk->group, "0.0.0.0");
+				nmtst_assert_ip6_address (&plnk->group6, "ff0e::115");
+				nmtst_assert_ip6_address (&plnk->local6, "1:2:3:4:334:23::23");
+				g_assert_cmpint (plnk->ageing, ==, 3245);
+				g_assert_cmpint (plnk->dst_port, ==, 57412);
+				g_assert_cmpint (plnk->ttl, ==, 32);
+				g_assert_cmpint (plnk->src_port_min, ==, 1000);
+				g_assert_cmpint (plnk->src_port_max, ==, 1003);
+				break;
+			}
+			break;
+		}
+		default:
+			g_assert_not_reached ();
+		}
+	}
+
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
+out_delete_parent:
+	nmtstp_link_del (-1, ifindex_parent, PARENT_NAME);
+}
+
+static void
+test_software_detect_add (const char *testpath,
+                          NMLinkType link_type,
+                          int test_mode)
+{
+	TestAddSoftwareDetectData *test_data;
+	char *path;
+
+	test_data = g_new0 (TestAddSoftwareDetectData, 1);
+	test_data->link_type = link_type;
+	test_data->test_mode = test_mode;
+	test_data->external_command = TRUE;
+
+	path = g_strdup_printf ("%s/external", testpath);
+	g_test_add_data_func_full (path, test_data, test_software_detect, g_free);
+	g_free (path);
+
+	test_data = g_new0 (TestAddSoftwareDetectData, 1);
+	test_data->link_type = link_type;
+	test_data->test_mode = test_mode;
+	test_data->external_command = FALSE;
+
+	path = g_strdup_printf ("%s/platform", testpath);
+	g_test_add_data_func_full (path, test_data, test_software_detect, g_free);
+	g_free (path);
+
+	test_data = g_new0 (TestAddSoftwareDetectData, 1);
+	test_data->link_type = link_type;
+	test_data->test_mode = test_mode;
+	test_data->external_command = -1;
+
+	path = g_strdup_printf ("%s/random", testpath);
+	g_test_add_data_func_full (path, test_data, test_software_detect, g_free);
+	g_free (path);
+}
+
+/*****************************************************************************/
+
+static void
+_assert_xgress_qos_mappings_impl (int ifindex,
+                                  gboolean is_ingress_map ,
+                                  int n_entries,
+                                  int n,
+                                  ...)
+{
+	const NMPlatformLink *plink;
+	const NMPObject *lnk;
+	guint n_map;
+	const NMVlanQosMapping *map;
+	va_list ap;
+	guint i;
+
+	lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, NM_LINK_TYPE_VLAN, &plink);
+
+	g_assert (plink);
+	g_assert_cmpint (plink->ifindex, ==, ifindex);
+	g_assert (lnk);
+	g_assert (&lnk->lnk_vlan == nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, NULL));
+
+	if (nmtst_is_debug ())
+		nmtstp_run_command_check ("ip -d link show %s", plink->name);
+
+	if (is_ingress_map) {
+		map = lnk->_lnk_vlan.ingress_qos_map;
+		n_map = lnk->_lnk_vlan.n_ingress_qos_map;
+	} else {
+		map = lnk->_lnk_vlan.egress_qos_map;
+		n_map = lnk->_lnk_vlan.n_egress_qos_map;
+	}
+
+	if (n_entries != -1)
+		g_assert_cmpint (n_map, ==, n_entries);
+
+	for (i = 0; i < n_map; i++) {
+		if (is_ingress_map) {
+			g_assert_cmpint (map[i].from, >=, 0);
+			g_assert_cmpint (map[i].from, <=, 7);
+		}
+		if (i > 0)
+			g_assert_cmpint (map[i - 1].from, <, map[i].from);
+	}
+
+	va_start (ap, n);
+	for (; n > 0; n--) {
+		gboolean found = FALSE;
+		guint from = va_arg (ap, guint);
+		guint to = va_arg (ap, guint);
+
+		for (i = 0; i < n_map; i++) {
+			if (map[i].from == from) {
+				g_assert (!found);
+				found = TRUE;
+
+				g_assert (map[i].to == to);
+			}
+		}
+		g_assert (found);
+	}
+	va_end (ap);
+}
+#define _assert_xgress_qos_mappings(ifindex, is_ingress_map, n_entries, ...) \
+	_assert_xgress_qos_mappings_impl ((ifindex), (is_ingress_map), (n_entries), \
+	                                  (G_STATIC_ASSERT_EXPR ((NM_NARG (__VA_ARGS__) % 2) == 0), NM_NARG (__VA_ARGS__) / 2), \
+	                                  __VA_ARGS__)
+#define _assert_ingress_qos_mappings(ifindex, n_entries, ...) _assert_xgress_qos_mappings (ifindex, TRUE, n_entries, __VA_ARGS__)
+#define _assert_egress_qos_mappings(ifindex, n_entries, ...)  _assert_xgress_qos_mappings (ifindex, FALSE, n_entries, __VA_ARGS__)
+
+static void
+_assert_vlan_flags (int ifindex, NMVlanFlags flags)
+{
+	const NMPlatformLnkVlan *plnk;
+
+	plnk = nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, NULL);
+	g_assert (plnk);
+	g_assert_cmpint (plnk->flags, ==, flags);
+}
+
+static void
+test_vlan_set_xgress (void)
+{
+	int ifindex, ifindex_parent;
+
+	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
+	ifindex_parent = nmtstp_assert_wait_for_link (PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1245", DEVICE_NAME, PARENT_NAME);
+	ifindex = nmtstp_assert_wait_for_link (DEVICE_NAME, NM_LINK_TYPE_VLAN, 100)->ifindex;
+
+	/* ingress-qos-map */
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 4, 5));
+	_assert_ingress_qos_mappings (ifindex, 1,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 3, 7));
+	_assert_ingress_qos_mappings (ifindex, 2,
+	                              3, 7,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 3, 8));
+	_assert_ingress_qos_mappings (ifindex, 2,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, 4));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 4,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, G_MAXUINT32,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32 - 1));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, G_MAXUINT32 - 1,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, 5));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, 5));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	/* Set invalid values: */
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 8, 3));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 9, 4));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	/* egress-qos-map */
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 7, 3));
+	_assert_egress_qos_mappings (ifindex, 1,
+	                             7, 3);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 8, 4));
+	_assert_egress_qos_mappings (ifindex, 2,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 0, 4));
+	_assert_egress_qos_mappings (ifindex, 3,
+	                             0, 4,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 1, 4));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             0, 4,
+	                             1, 4,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 1, 5));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 9, 5));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             8, 4,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 8, 5));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             8, 5,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 8, 0));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 0, 0));
+	_assert_egress_qos_mappings (ifindex, 3,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 100, 4));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, G_MAXUINT32, 4));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4,
+	                             G_MAXUINT32, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, G_MAXUINT32, 8));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4,
+	                             G_MAXUINT32, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, G_MAXUINT32, 0));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 100, 0));
+	_assert_egress_qos_mappings (ifindex, 3,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 1, 0));
+	_assert_egress_qos_mappings (ifindex, 2,
+	                             7, 3,
+	                             9, 5);
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 1, .to = 5 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 1,
+		                              1, 5);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 3, .to = 5 },
+			{ .from = 7, .to = 1655 },
+			{ .from = 7, .to = 17655 },
+			{ .from = 5, .to = 754 },
+			{ .from = 4, .to = 12 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 4,
+		                              3, 5,
+		                              4, 12,
+		                              7, 17655,
+		                              5, 754);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 3, .to = 18 },
+			{ .from = 6, .to = 121 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 5,
+		                              3, 18,
+		                              4, 12,
+		                              6, 121,
+		                              7, 17655,
+		                              5, 754);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 3, .to = 0 },
+			{ .from = 6, .to = 7 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 1,
+		                              6, 7);
+	}
+
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 1, .to = 5 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 1,
+		                              1, 5);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 5, .to = 1 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 1,
+		                             5, 1);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 5, .to = 3 },
+			{ .from = 1655, .to = 5 },
+			{ .from = 1655, .to = 7 },
+			{ .from = G_MAXUINT32, .to = 6 },
+			{ .from = G_MAXUINT32, .to = 8 },
+			{ .from = 754, .to = 4 },
+			{ .from = 3, .to = 2 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 5,
+		                             3, 2,
+		                             5, 3,
+		                             754, 4,
+		                             1655, 7,
+		                             G_MAXUINT32, 6);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 754, .to = 3 },
+			{ .from = 755, .to = 8 },
+			{ .from = 1655, .to = 0 },
+			{ .from = 6, .to = 1 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        FALSE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 5,
+		                             3, 2,
+		                             5, 3,
+		                             6, 1,
+		                             754, 3,
+		                             G_MAXUINT32, 6);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 6, .to = 0 },
+			{ .from = 3, .to = 4 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 1,
+		                             3, 4);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 1, .to = 5 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 1,
+		                             1, 5);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 6, .to = 145 },
+			{ .from = 4, .to = 1 },
+			{ .from = 6, .to = 12 },
+		};
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 1, .to = 5 },
+			{ .from = 3232, .to = 7 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP,
+		                                        NM_VLAN_FLAG_REORDER_HEADERS,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_ingress_qos_mappings (ifindex, 2,
+		                             4, 1,
+		                             6, 12);
+		_assert_egress_qos_mappings (ifindex, 2,
+		                             1, 5,
+		                             3232, 7);
+		_assert_vlan_flags (ifindex, NM_VLAN_FLAG_REORDER_HEADERS);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 6, .to = 145 },
+			{ .from = 4, .to = 1 },
+			{ .from = 6, .to = 12 },
+		};
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 1, .to = 7 },
+			{ .from = 64, .to = 10 },
+			{ .from = 64, .to = 10 },
+			{ .from = 64, .to = 10 },
+			{ .from = 64, .to = 10 },
+			{ .from = 3232, .to = 0 },
+			{ .from = 64, .to = 4 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        NM_VLAN_FLAG_GVRP,
+		                                        NM_VLAN_FLAG_GVRP,
+		                                        FALSE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_ingress_qos_mappings (ifindex, 2,
+		                             4, 1,
+		                             6, 12);
+		_assert_egress_qos_mappings (ifindex, 2,
+		                             1, 7,
+		                             64, 4);
+		_assert_vlan_flags (ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP);
+	}
+
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
+	nmtstp_link_del (-1, ifindex_parent, PARENT_NAME);
+}
+
+/*****************************************************************************/
+
+static void
+test_create_many_links_do (guint n_devices)
+{
+	gint64 time, start_time = nm_utils_get_monotonic_timestamp_ns ();
+	guint i;
+	char name[64];
+	const NMPlatformLink *pllink;
+	gs_unref_array GArray *ifindexes = g_array_sized_new (FALSE, FALSE, sizeof (int), n_devices);
+	const gint EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
+
+	g_assert (EX >= -1 && EX <= 2);
+
+	_LOGI (">>> create devices (EX=%d)...", EX);
+
+	for (i = 0; i < n_devices; i++) {
+		nm_sprintf_buf (name, "t-%05u", i);
+		if (EX == 2) {
+			/* This mode is different from letting nmtstp_link_dummy_add()
+			 * because in this case we don't process any platform events
+			 * while adding all the links. */
+			nmtstp_run_command_check ("ip link add %s type dummy", name);
+		} else
+			nmtstp_link_dummy_add (EX, name);
+	}
+
+	_LOGI (">>> process events after creating devices...");
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	_LOGI (">>> check devices...");
+
+	for (i = 0; i < n_devices; i++) {
+		nm_sprintf_buf (name, "t-%05u", i);
+
+		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+		g_assert (pllink);
+		g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_DUMMY);
+		g_assert_cmpstr (pllink->name, ==, name);
+
+		g_array_append_val (ifindexes, pllink->ifindex);
+	}
+
+	_LOGI (">>> delete devices...");
+
+	g_assert_cmpint (ifindexes->len, ==, n_devices);
+	for (i = 0; i < n_devices; i++) {
+		nm_sprintf_buf (name, "t-%05u", i);
+
+		if (EX == 2)
+			nmtstp_run_command_check ("ip link delete %s", name);
+		else
+			nmtstp_link_del (EX, g_array_index (ifindexes, int, i), name);
+	}
+
+	_LOGI (">>> process events after deleting devices...");
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	time = nm_utils_get_monotonic_timestamp_ns () - start_time;
+	_LOGI (">>> finished in %ld.%09ld seconds", (long) (time / NM_UTILS_NS_PER_SECOND), (long) (time % NM_UTILS_NS_PER_SECOND));
+}
+
+static void
+test_create_many_links (gconstpointer user_data)
+{
+	guint n_devices = GPOINTER_TO_UINT (user_data);
+
+	if (n_devices > 100 && nmtst_test_quick ()) {
+		g_print ("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", str_if_set (g_get_prgname (), "test-link-linux"));
+		g_test_skip ("Skip long running test");
+		return;
+	}
+
+	test_create_many_links_do (n_devices);
+}
+
+/*****************************************************************************/
+
+static void
+test_nl_bugs_veth (void)
+{
+	const char *IFACE_VETH0 = "nm-test-veth0";
+	const char *IFACE_VETH1 = "nm-test-veth1";
+	int ifindex_veth0, ifindex_veth1;
+	int i;
+	const NMPlatformLink *pllink_veth0, *pllink_veth1;
+	gs_free_error GError *error = NULL;
+	NMTstpNamespaceHandle *ns_handle = NULL;
+
+	/* create veth pair. */
+	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
+	ifindex_veth0 = nmtstp_assert_wait_for_link (IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
+	ifindex_veth1 = nmtstp_assert_wait_for_link (IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+
+	/* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */
+	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth0, &i));
+	g_assert_cmpint (i, ==, ifindex_veth1);
+
+	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth1, &i));
+	g_assert_cmpint (i, ==, ifindex_veth0);
+
+	/* assert that NMPlatformLink.parent is the peer-ifindex. */
+	pllink_veth0 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth0);
+	g_assert (pllink_veth0);
+	if (pllink_veth0->parent == 0) {
+		/* pre-4.1 kernels don't support exposing the veth peer as IFA_LINK. skip the remainder
+		 * of the test. */
+		goto out;
+	}
+	g_assert_cmpint (pllink_veth0->parent, ==, ifindex_veth1);
+
+
+	/* The following tests whether we have a workaround for kernel bug
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=1285827 in place. */
+	pllink_veth1 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth1);
+	g_assert (pllink_veth1);
+	g_assert_cmpint (pllink_veth1->parent, ==, ifindex_veth0);
+
+
+	/* move one veth peer to another namespace and check that the
+	 * parent/IFLA_LINK of the remaining peer properly updates
+	 * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */
+	ns_handle = nmtstp_namespace_create (CLONE_NEWNET, &error);
+	g_assert_no_error (error);
+	g_assert (ns_handle);
+
+	nmtstp_run_command_check ("ip link set %s netns %ld", IFACE_VETH1, (long) nmtstp_namespace_handle_get_pid (ns_handle));
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (50);
+		nm_platform_process_events (NM_PLATFORM_GET);
+
+		pllink_veth1 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth1);
+		pllink_veth0 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth0);
+		if (   !pllink_veth1
+		    && pllink_veth0
+		    && pllink_veth0->parent == NM_PLATFORM_LINK_OTHER_NETNS) {
+			break;
+		}
+	});
+
+out:
+	nmtstp_link_del (-1, ifindex_veth0, IFACE_VETH0);
+	g_assert (!nmtstp_link_get (ifindex_veth0, IFACE_VETH0));
+	g_assert (!nmtstp_link_get (ifindex_veth1, IFACE_VETH1));
+	nmtstp_namespace_handle_release (ns_handle);
+}
+
+/*****************************************************************************/
+
+static void
+test_nl_bugs_spuroius_newlink (void)
+{
+	const char *IFACE_BOND0 = "nm-test-bond0";
+	const char *IFACE_DUMMY0 = "nm-test-dummy0";
+	int ifindex_bond0, ifindex_dummy0;
+	const NMPlatformLink *pllink;
+	gboolean wait_for_settle;
+
+	/* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
+
+	nmtstp_run_command_check ("ip link add %s type dummy", IFACE_DUMMY0);
+	ifindex_dummy0 = nmtstp_assert_wait_for_link (IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	nmtstp_run_command_check ("ip link add %s type bond", IFACE_BOND0);
+	ifindex_bond0 = nmtstp_assert_wait_for_link (IFACE_BOND0, NM_LINK_TYPE_BOND, 100)->ifindex;
+
+	nmtstp_link_set_updown (-1, ifindex_bond0, TRUE);
+
+	nmtstp_run_command_check ("ip link set %s master %s", IFACE_DUMMY0, IFACE_BOND0);
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (50);
+
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
+		g_assert (pllink);
+		if (pllink->master == ifindex_bond0)
+			break;
+	});
+
+	nmtstp_run_command_check ("ip link del %s",  IFACE_BOND0);
+
+	wait_for_settle = TRUE;
+	nmtstp_wait_for_signal (50);
+again:
+	nm_platform_process_events (NM_PLATFORM_GET);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_bond0);
+	g_assert (!pllink);
+
+	if (wait_for_settle) {
+		wait_for_settle = FALSE;
+		NMTST_WAIT (300, { nmtstp_wait_for_signal (50); });
+		goto again;
+	}
+
+	g_assert (!nmtstp_link_get (ifindex_bond0, IFACE_BOND0));
+	nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
+}
+
+/*****************************************************************************/
+
+static void
+test_nl_bugs_spuroius_dellink (void)
+{
+	const char *IFACE_BRIDGE0 = "nm-test-bridge0";
+	const char *IFACE_DUMMY0 = "nm-test-dummy0";
+	int ifindex_bridge0, ifindex_dummy0;
+	const NMPlatformLink *pllink;
+	gboolean wait_for_settle;
+
+	/* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
+
+	nmtstp_run_command_check ("ip link add %s type dummy", IFACE_DUMMY0);
+	ifindex_dummy0 = nmtstp_assert_wait_for_link (IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	nmtstp_run_command_check ("ip link add %s type bridge", IFACE_BRIDGE0);
+	ifindex_bridge0 = nmtstp_assert_wait_for_link (IFACE_BRIDGE0, NM_LINK_TYPE_BRIDGE, 100)->ifindex;
+
+	nmtstp_link_set_updown (-1, ifindex_bridge0, TRUE);
+
+	nmtstp_run_command_check ("ip link set %s master %s", IFACE_DUMMY0, IFACE_BRIDGE0);
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (50);
+
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
+		g_assert (pllink);
+		if (pllink->master == ifindex_bridge0)
+			break;
+	});
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	nmtstp_run_command_check ("ip link set %s nomaster",  IFACE_DUMMY0);
+
+	wait_for_settle = TRUE;
+	nmtstp_wait_for_signal (50);
+again:
+	nm_platform_process_events (NM_PLATFORM_GET);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_bridge0);
+	g_assert (pllink);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
+	g_assert (pllink);
+	g_assert_cmpint (pllink->parent, ==, 0);
+
+	if (wait_for_settle) {
+		wait_for_settle = FALSE;
+		NMTST_WAIT (300, { nmtstp_wait_for_signal (50); });
+		goto again;
+	}
+
+	nmtstp_link_del (-1, ifindex_bridge0, IFACE_BRIDGE0);
+	nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
@@ -599,7 +1843,28 @@ setup_tests (void)
 	g_test_add_func ("/link/software/bond", test_bond);
 	g_test_add_func ("/link/software/team", test_team);
 	g_test_add_func ("/link/software/vlan", test_vlan);
+	g_test_add_func ("/link/software/bridge/addr", test_bridge_addr);
 
-	if (strcmp (g_type_name (G_TYPE_FROM_INSTANCE (nm_platform_get ())), "NMFakePlatform"))
+	if (nmtstp_is_root_test ()) {
 		g_test_add_func ("/link/external", test_external);
+
+		test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0);
+		test_software_detect_add ("/link/software/detect/ip6tnl", NM_LINK_TYPE_IP6TNL, 0);
+		test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
+		test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
+		test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
+		test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0);
+		test_software_detect_add ("/link/software/detect/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);
+
+		g_test_add_func ("/link/software/vlan/set-xgress", test_vlan_set_xgress);
+
+		g_test_add_data_func ("/link/create-many-links/20", GUINT_TO_POINTER (20), test_create_many_links);
+		g_test_add_data_func ("/link/create-many-links/1000", GUINT_TO_POINTER (1000), test_create_many_links);
+
+		g_test_add_func ("/link/nl-bugs/veth", test_nl_bugs_veth);
+		g_test_add_func ("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink);
+		g_test_add_func ("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink);
+	}
 }
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 88bb0707..eba2383b 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -20,7 +20,7 @@
 
 #include "nmp-object.h"
 
-#include "nm-logging.h"
+#include "nm-default.h"
 
 #include "nm-test-utils.h"
 
@@ -148,8 +148,8 @@ _nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj,
 	NMPCacheOpsType ops_type;
 	NMPObject *obj2;
 	gboolean was_visible;
-	auto_nmp_obj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
-	auto_nmp_obj NMPObject *new_clone = NULL;
+	nm_auto_nmpobj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
+	nm_auto_nmpobj NMPObject *new_clone = NULL;
 	const NMPObject *obj_old;
 	_NMPCacheUpdateData data = {
 		.cache = cache,
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index cf322dec..60a0c15c 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -9,7 +9,7 @@
 #define DEVICE_NAME "nm-test-device"
 
 static void
-ip4_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP4Route *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip4_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP4Route *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -25,11 +25,11 @@ ip4_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, c
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
-ip6_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP6Route *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip6_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP6Route *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -45,7 +45,7 @@ ip6_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, c
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
@@ -282,6 +282,31 @@ test_ip6_route (void)
 	free_signal (route_removed);
 }
 
+/*****************************************************************************/
+
+static void
+test_ip4_zero_gateway (void)
+{
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+
+	nmtstp_run_command_check ("ip route add 1.2.3.1/32 via 0.0.0.0 dev %s", DEVICE_NAME);
+	nmtstp_run_command_check ("ip route add 1.2.3.2/32 dev %s", DEVICE_NAME);
+
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (10);
+		if (   nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0)
+		    && nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0))
+			break;
+	});
+
+	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (50);
+	nm_platform_process_events (NM_PLATFORM_GET);
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
@@ -295,7 +320,7 @@ 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));
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
 	accept_signal (link_added);
 	free_signal (link_added);
 
@@ -304,4 +329,7 @@ setup_tests (void)
 	g_test_add_func ("/route/ip4", test_ip4_route);
 	g_test_add_func ("/route/ip6", test_ip6_route);
 	g_test_add_func ("/route/ip4_metric0", test_ip4_route_metric0);
+
+	if (nmtstp_is_root_test ())
+		g_test_add_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
 }
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c
index b31ffa1c..e05104f3 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/wifi-utils-nl80211.c
@@ -28,21 +28,208 @@
 #include <net/ethernet.h>
 #include <unistd.h>
 #include <math.h>
-
-#include <glib.h>
-
-#include <netlink/genl/genl.h>
-#include <netlink/genl/family.h>
-#include <netlink/genl/ctrl.h>
-
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
 #include <linux/nl80211.h>
 
+#include "nm-default.h"
 #include "wifi-utils-private.h"
 #include "wifi-utils-nl80211.h"
 #include "nm-platform.h"
-#include "nm-logging.h"
 #include "nm-utils.h"
 
+
+/*****************************************************************************
+ * Copied from libnl3/genl:
+ *****************************************************************************/
+
+static void *
+genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family,
+             int hdrlen, int flags, uint8_t cmd, uint8_t version)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr hdr = {
+		.cmd = cmd,
+		.version = version,
+	};
+
+	nlh = nlmsg_put (msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
+	if (nlh == NULL)
+		return NULL;
+
+	memcpy (nlmsg_data (nlh), &hdr, sizeof (hdr));
+
+	return (char *) nlmsg_data (nlh) + GENL_HDRLEN;
+}
+
+static void *
+genlmsg_data (const struct genlmsghdr *gnlh)
+{
+	return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+static void *
+genlmsg_user_hdr (const struct genlmsghdr *gnlh)
+{
+	return genlmsg_data (gnlh);
+}
+
+static struct genlmsghdr *
+genlmsg_hdr (struct nlmsghdr *nlh)
+{
+	return nlmsg_data (nlh);
+}
+
+static void *
+genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen)
+{
+	return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen);
+}
+
+static struct nlattr *
+genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_user_data (gnlh, hdrlen);
+}
+
+static int
+genlmsg_len (const struct genlmsghdr *gnlh)
+{
+	const struct nlmsghdr *nlh;
+
+	nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
+	return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
+}
+
+static int
+genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen);
+}
+
+static int
+genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen)
+{
+	struct genlmsghdr *ghdr;
+
+	if (!nlmsg_valid_hdr (nlh, GENL_HDRLEN))
+		return 0;
+
+	ghdr = nlmsg_data (nlh);
+	if (genlmsg_len (ghdr) < NLMSG_ALIGN (hdrlen))
+		return 0;
+
+	return 1;
+}
+
+static int
+genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+               int maxtype, struct nla_policy *policy)
+{
+	struct genlmsghdr *ghdr;
+
+	if (!genlmsg_valid_hdr (nlh, hdrlen))
+		return -NLE_MSG_TOOSHORT;
+
+	ghdr = nlmsg_data (nlh);
+	return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen),
+	                  genlmsg_attrlen (ghdr, hdrlen), policy);
+}
+
+/*****************************************************************************
+ * Reimplementation of libnl3/genl functions:
+ *****************************************************************************/
+
+static int
+probe_response (struct nl_msg *msg, void *arg)
+{
+	static struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
+		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
+		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
+		                            .maxlen = GENL_NAMSIZ },
+		[CTRL_ATTR_VERSION]      = { .type = NLA_U32 },
+		[CTRL_ATTR_HDRSIZE]      = { .type = NLA_U32 },
+		[CTRL_ATTR_MAXATTR]      = { .type = NLA_U32 },
+		[CTRL_ATTR_OPS]          = { .type = NLA_NESTED },
+		[CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED },
+	};
+	struct nlattr *tb[CTRL_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr (msg);
+	gint32 *response_data = arg;
+
+	if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
+		return NL_SKIP;
+
+	if (tb[CTRL_ATTR_FAMILY_ID])
+		*response_data = nla_get_u16 (tb[CTRL_ATTR_FAMILY_ID]);
+
+	return NL_STOP;
+}
+
+static int
+genl_ctrl_resolve (struct nl_sock *sk, const char *name)
+{
+	struct nl_msg *msg;
+	struct nl_cb *cb, *orig;
+	int rc;
+	int result = -NLE_OBJ_NOTFOUND;
+	gint32 response_data = -1;
+
+	if (!(orig = nl_socket_get_cb (sk)))
+		goto out;
+
+	cb = nl_cb_clone (orig);
+	nl_cb_put (orig);
+	if (!cb)
+		goto out;
+
+	msg = nlmsg_alloc ();
+	if (!msg)
+		goto out_cb_free;
+
+	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
+	                  0, 0, CTRL_CMD_GETFAMILY, 1))
+		goto out_msg_free;
+
+	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
+		goto out_msg_free;
+
+	rc = nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, probe_response, &response_data);
+	if (rc < 0)
+		goto out_msg_free;
+
+	rc = nl_send_auto_complete (sk, msg);
+	if (rc < 0)
+		goto out_msg_free;
+
+	rc = nl_recvmsgs (sk, cb);
+	if (rc < 0)
+		goto out_msg_free;
+
+	/* If search was successful, request may be ACKed after data */
+	rc = nl_wait_for_ack (sk);
+	if (rc < 0)
+		goto out_msg_free;
+
+	if (response_data > 0)
+		result = response_data;
+
+out_msg_free:
+	nlmsg_free (msg);
+out_cb_free:
+	nl_cb_put (cb);
+out:
+	if (result >= 0)
+		nm_log_dbg (LOGD_WIFI, "genl_ctrl_resolve: resolved \"%s\" as 0x%x", name, result);
+	else
+		nm_log_err (LOGD_WIFI, "genl_ctrl_resolve: failed resolve \"%s\"", name);
+	return result;
+}
+
+/*****************************************************************************
+ * </libn-genl-3>
+ *****************************************************************************/
+
 typedef struct {
 	WifiData parent;
 	struct nl_sock *nl_sock;
@@ -144,7 +331,7 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 			 * not warn on DUMP_INTR error for get scan command.
 			 */
 			if (err == -NLE_DUMP_INTR &&
-			    genlmsg_hdr(nlmsg_hdr(msg))->cmd == NL80211_CMD_GET_SCAN)
+			    genlmsg_hdr (nlmsg_hdr (msg))->cmd == NL80211_CMD_GET_SCAN)
 				break;
 
 			nm_log_warn (LOGD_WIFI, "nl_recvmsgs() error: (%d) %s",
@@ -265,6 +452,24 @@ wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
 	return FALSE;
 }
 
+static gboolean
+wifi_nl80211_set_powersave (WifiData *data, guint32 powersave)
+{
+	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	struct nl_msg *msg;
+	int err;
+
+	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_POWER_SAVE, 0);
+	NLA_PUT_U32 (msg, NL80211_ATTR_PS_STATE,
+	             powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED);
+	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	return err ? FALSE : TRUE;
+
+nla_put_failure:
+	nlmsg_free (msg);
+	return FALSE;
+}
+
 /* @divisor: pass what value @xbm should be divided by to get dBm */
 static guint32
 nl80211_xbm_to_percent (gint32 xbm, guint32 divisor)
@@ -273,7 +478,7 @@ nl80211_xbm_to_percent (gint32 xbm, guint32 divisor)
 #define SIGNAL_MAX_DBM   -20
 
 	xbm /= divisor;
-	xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM);
+	xbm = CLAMP (xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM);
 
 	return 100 - 70 * (((float) SIGNAL_MAX_DBM - (float) xbm) /
 			   ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM));
@@ -353,7 +558,7 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 
 	if (bss[NL80211_BSS_BSSID] == NULL)
 		return NL_SKIP;
-	memcpy(info->bssid, nla_data (bss[NL80211_BSS_BSSID]), ETH_ALEN);
+	memcpy (info->bssid, nla_data (bss[NL80211_BSS_BSSID]), ETH_ALEN);
 
 	if (bss[NL80211_BSS_FREQUENCY])
 		info->freq = nla_get_u32 (bss[NL80211_BSS_FREQUENCY]);
@@ -370,7 +575,7 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 		guint8 *ssid;
 		guint32 ssid_len;
 
-		find_ssid(nla_data (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
+		find_ssid (nla_data (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
 			  nla_len (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
 			  &ssid, &ssid_len);
 		if (ssid && ssid_len && ssid_len <= sizeof (info->ssid)) {
@@ -390,7 +595,7 @@ nl80211_get_bss_info (WifiDataNl80211 *nl80211,
 {
 	struct nl_msg *msg;
 
-	memset(bss_info, 0, sizeof (*bss_info));
+	memset (bss_info, 0, sizeof (*bss_info));
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
 
@@ -424,24 +629,6 @@ wifi_nl80211_find_freq (WifiData *data, const guint32 *freqs)
 	return 0;
 }
 
-static GByteArray *
-wifi_nl80211_get_ssid (WifiData *data)
-{
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	GByteArray *array = NULL;
-	struct nl80211_bss_info bss_info;
-
-	nl80211_get_bss_info (nl80211, &bss_info);
-
-	if (bss_info.valid) {
-		array = g_byte_array_sized_new (bss_info.ssid_len);
-		g_byte_array_append (array, (const guint8 *) bss_info.ssid,
-				     bss_info.ssid_len);
-	}
-
-	return array;
-}
-
 static gboolean
 wifi_nl80211_get_bssid (WifiData *data, guint8 *out_bssid)
 {
@@ -451,7 +638,7 @@ wifi_nl80211_get_bssid (WifiData *data, guint8 *out_bssid)
 	nl80211_get_bss_info (nl80211, &bss_info);
 
 	if (bss_info.valid)
-		memcpy(out_bssid, bss_info.bssid, ETH_ALEN);
+		memcpy (out_bssid, bss_info.bssid, ETH_ALEN);
 
 	return bss_info.valid;
 }
@@ -533,7 +720,7 @@ nl80211_get_ap_info (WifiDataNl80211 *nl80211,
 	struct nl_msg *msg;
 	struct nl80211_bss_info bss_info;
 
-	memset(sta_info, 0, sizeof (*sta_info));
+	memset (sta_info, 0, sizeof (*sta_info));
 
 	nl80211_get_bss_info (nl80211, &bss_info);
 	if (!bss_info.valid)
@@ -621,7 +808,7 @@ nl80211_wowlan_handler (struct nl_msg *msg, void *arg)
 
 	info->enabled = FALSE;
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
 	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
 		return NL_SKIP;
 
@@ -695,7 +882,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	G_STATIC_ASSERT (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS);
 #endif
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
 	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
 		return NL_SKIP;
 
@@ -743,13 +930,13 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 
 	nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
 		if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band,
-				      NULL) < 0)
+		                      NULL) < 0)
 			return NL_SKIP;
 
-		nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
-				    rem_freq) {
+		nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
+		                     rem_freq) {
 			nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX,
-					  nl_freq, freq_policy);
+			                  nl_freq, freq_policy);
 
 			if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
 				continue;
@@ -767,7 +954,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 		                      NULL) < 0)
 			return NL_SKIP;
 
-		nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
+		nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
 		                    rem_freq) {
 			nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX,
 			                  nl_freq, freq_policy);
@@ -853,9 +1040,9 @@ wifi_nl80211_init (const char *iface, int ifindex)
 	nl80211 = wifi_data_new (iface, ifindex, sizeof (*nl80211));
 	nl80211->parent.get_mode = wifi_nl80211_get_mode;
 	nl80211->parent.set_mode = wifi_nl80211_set_mode;
+	nl80211->parent.set_powersave = wifi_nl80211_set_powersave;
 	nl80211->parent.get_freq = wifi_nl80211_get_freq;
 	nl80211->parent.find_freq = wifi_nl80211_find_freq;
-	nl80211->parent.get_ssid = wifi_nl80211_get_ssid;
 	nl80211->parent.get_bssid = wifi_nl80211_get_bssid;
 	nl80211->parent.get_rate = wifi_nl80211_get_rate;
 	nl80211->parent.get_qual = wifi_nl80211_get_qual;
@@ -868,7 +1055,7 @@ wifi_nl80211_init (const char *iface, int ifindex)
 	if (nl80211->nl_sock == NULL)
 		goto error;
 
-	if (genl_connect (nl80211->nl_sock))
+	if (nl_connect (nl80211->nl_sock, NETLINK_GENERIC))
 		goto error;
 
 	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
@@ -886,22 +1073,22 @@ wifi_nl80211_init (const char *iface, int ifindex)
 	if (nl80211_send_and_recv (nl80211, msg, nl80211_wiphy_info_handler,
 	                           &device_info) < 0) {
 		nm_log_dbg (LOGD_HW | LOGD_WIFI,
-				    "(%s): NL80211_CMD_GET_WIPHY request failed",
-				    nl80211->parent.iface);
+		            "(%s): NL80211_CMD_GET_WIPHY request failed",
+		            nl80211->parent.iface);
 		goto error;
 	}
 
 	if (!device_info.success) {
 		nm_log_dbg (LOGD_HW | LOGD_WIFI,
-				    "(%s): NL80211_CMD_GET_WIPHY request indicated failure",
-				    nl80211->parent.iface);
+		            "(%s): NL80211_CMD_GET_WIPHY request indicated failure",
+		            nl80211->parent.iface);
 		goto error;
 	}
 
 	if (!device_info.supported) {
 		nm_log_dbg (LOGD_HW | LOGD_WIFI,
-				    "(%s): driver does not fully support nl80211, falling back to WEXT",
-				    nl80211->parent.iface);
+		            "(%s): driver does not fully support nl80211, falling back to WEXT",
+		            nl80211->parent.iface);
 		goto error;
 	}
 
@@ -914,8 +1101,8 @@ wifi_nl80211_init (const char *iface, int ifindex)
 
 	if (device_info.num_freqs == 0 || device_info.freqs == NULL) {
 		nm_log_err (LOGD_HW | LOGD_WIFI,
-				    "(%s): driver reports no supported frequencies",
-				    nl80211->parent.iface);
+		            "(%s): driver reports no supported frequencies",
+		            nl80211->parent.iface);
 		goto error;
 	}
 
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/wifi-utils-private.h
index aaedd5b7..21a4c57b 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/wifi-utils-private.h
@@ -21,8 +21,8 @@
 #ifndef __WIFI_UTILS_PRIVATE_H__
 #define __WIFI_UTILS_PRIVATE_H__
 
-#include <glib.h>
 
+#include "nm-default.h"
 #include "nm-dbus-interface.h"
 #include "wifi-utils.h"
 
@@ -35,15 +35,15 @@ struct WifiData {
 
 	gboolean (*set_mode) (WifiData *data, const NM80211Mode mode);
 
+	/* Set power saving mode on an interface */
+	gboolean (*set_powersave) (WifiData *data, guint32 powersave);
+
 	/* Return current frequency in MHz (really associated BSS frequency) */
 	guint32 (*get_freq) (WifiData *data);
 
 	/* Return first supported frequency in the zero-terminated list */
 	guint32 (*find_freq) (WifiData *data, const guint32 *freqs);
 
-	/* If SSID is empty/blank (zero-length or all \0s) return NULL */
-	GByteArray * (*get_ssid) (WifiData *data);
-
 	/* Return current bitrate in Kbps */
 	guint32 (*get_rate) (WifiData *data);
 
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c
index 470b73d1..96bcf53d 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/wifi-utils-wext.c
@@ -28,11 +28,10 @@
 #include <unistd.h>
 #include <math.h>
 
-#include <glib.h>
 
+#include "nm-default.h"
 #include "wifi-utils-private.h"
 #include "wifi-utils-wext.h"
-#include "nm-logging.h"
 #include "nm-utils.h"
 #include "nm-platform-utils.h"
 
@@ -45,7 +44,6 @@
 #include <sys/socket.h>
 #include <linux/wireless.h>
 
-
 typedef struct {
 	WifiData parent;
 	int fd;
@@ -166,6 +164,30 @@ wifi_wext_set_mode (WifiData *data, const NM80211Mode mode)
 	return TRUE;
 }
 
+static gboolean
+wifi_wext_set_powersave (WifiData *data, guint32 powersave)
+{
+	WifiDataWext *wext = (WifiDataWext *) data;
+	struct iwreq wrq;
+
+	memset (&wrq, 0, sizeof (struct iwreq));
+	if (powersave == 1) {
+		wrq.u.power.flags = IW_POWER_ALL_R;
+	} else
+		wrq.u.power.disabled = 1;
+
+	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	if (ioctl (wext->fd, SIOCSIWPOWER, &wrq) < 0) {
+		if (errno != ENODEV) {
+			nm_log_err (LOGD_HW | LOGD_WIFI, "(%s): error setting powersave %" G_GUINT32_FORMAT,
+			            wext->parent.iface, powersave);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 static guint32
 wifi_wext_get_freq (WifiData *data)
 {
@@ -200,36 +222,6 @@ wifi_wext_find_freq (WifiData *data, const guint32 *freqs)
 	return 0;
 }
 
-static GByteArray *
-wifi_wext_get_ssid (WifiData *data)
-{
-	WifiDataWext *wext = (WifiDataWext *) data;
-	struct iwreq wrq;
-	char ssid[IW_ESSID_MAX_SIZE + 2];
-	guint32 len;
-	GByteArray *array = NULL;
-
-	memset (ssid, 0, sizeof (ssid));
-	wrq.u.essid.pointer = (caddr_t) &ssid;
-	wrq.u.essid.length = sizeof (ssid);
-	wrq.u.essid.flags = 0;
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
-
-	if (ioctl (wext->fd, SIOCGIWESSID, &wrq) < 0) {
-		nm_log_err (LOGD_HW | LOGD_WIFI, "(%s): couldn't get SSID: %d",
-		            wext->parent.iface, errno);
-		return NULL;
-	}
-
-	len = wrq.u.essid.length;
-	if (nm_utils_is_empty_ssid ((guint8 *) ssid, len) == FALSE) {
-		array = g_byte_array_sized_new (len);
-		g_byte_array_append (array, (const guint8 *) ssid, len);
-	}
-
-	return array;
-}
-
 static gboolean
 wifi_wext_get_bssid (WifiData *data, guint8 *out_bssid)
 {
@@ -575,9 +567,9 @@ wifi_wext_init (const char *iface, int ifindex, gboolean check_scan)
 	wext = wifi_data_new (iface, ifindex, sizeof (*wext));
 	wext->parent.get_mode = wifi_wext_get_mode;
 	wext->parent.set_mode = wifi_wext_set_mode;
+	wext->parent.set_powersave = wifi_wext_set_powersave;
 	wext->parent.get_freq = wifi_wext_get_freq;
 	wext->parent.find_freq = wifi_wext_find_freq;
-	wext->parent.get_ssid = wifi_wext_get_ssid;
 	wext->parent.get_bssid = wifi_wext_get_bssid;
 	wext->parent.get_rate = wifi_wext_get_rate;
 	wext->parent.get_qual = wifi_wext_get_qual;
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
index 068c404e..5f2f61b0 100644
--- a/src/platform/wifi/wifi-utils.c
+++ b/src/platform/wifi/wifi-utils.c
@@ -24,8 +24,8 @@
 #include <sys/stat.h>
 #include <stdio.h>
 #include <string.h>
-#include <glib.h>
 
+#include "nm-default.h"
 #include "wifi-utils.h"
 #include "wifi-utils-private.h"
 #include "wifi-utils-nl80211.h"
@@ -98,6 +98,14 @@ wifi_utils_set_mode (WifiData *data, const NM80211Mode mode)
 	return data->set_mode ? data->set_mode (data, mode) : TRUE;
 }
 
+gboolean
+wifi_utils_set_powersave (WifiData *data, guint32 powersave)
+{
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	return data->set_powersave ? data->set_powersave (data, powersave) : TRUE;
+}
+
 guint32
 wifi_utils_get_freq (WifiData *data)
 {
@@ -113,13 +121,6 @@ wifi_utils_find_freq (WifiData *data, const guint32 *freqs)
 	return data->find_freq (data, freqs);
 }
 
-GByteArray *
-wifi_utils_get_ssid (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, NULL);
-	return data->get_ssid (data);
-}
-
 gboolean
 wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid)
 {
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index 588386ab..21dac9e9 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -23,8 +23,8 @@
 #define __WIFI_UTILS_H__
 
 #include <net/ethernet.h>
-#include <glib.h>
 
+#include "nm-default.h"
 #include "nm-dbus-interface.h"
 
 typedef struct WifiData WifiData;
@@ -48,9 +48,6 @@ guint32 wifi_utils_get_freq (WifiData *data);
  * Frequencies are specified in MHz. */
 guint32 wifi_utils_find_freq (WifiData *data, const guint32 *freqs);
 
-/* Caller must free returned byte array */
-GByteArray *wifi_utils_get_ssid (WifiData *data);
-
 /* out_bssid must be ETH_ALEN bytes */
 gboolean wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid);
 
@@ -66,6 +63,8 @@ gboolean wifi_utils_indicate_addressing_running (WifiData *data, gboolean runnin
 /* Returns true if WoWLAN is enabled on device */
 gboolean wifi_utils_get_wowlan (WifiData *data);
 
+gboolean wifi_utils_set_powersave (WifiData *data, guint32 powersave);
+
 
 /* OLPC Mesh-only functions */
 guint32 wifi_utils_get_mesh_channel (WifiData *data);