summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
committerMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
commit50a58f0fabd8a34c1b6108a107e08abe3c1ccd24 (patch)
tree6790165f39daee79e2b6c6617483320613493367 /src/platform
parentf408e27bccfacf347605a8d98649975a68f38a17 (diff)
Imported Upstream version 1.0.4 upstream/1.0.4
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/Makefile.in2
-rw-r--r--src/platform/nm-fake-platform.c651
-rw-r--r--src/platform/nm-linux-platform.c4949
-rw-r--r--src/platform/nm-linux-platform.h4
-rw-r--r--src/platform/nm-platform-utils.c436
-rw-r--r--src/platform/nm-platform-utils.h66
-rw-r--r--src/platform/nm-platform.c2093
-rw-r--r--src/platform/nm-platform.h445
-rw-r--r--src/platform/nmp-object.c1924
-rw-r--r--src/platform/nmp-object.h361
-rw-r--r--src/platform/tests/Makefile.am26
-rw-r--r--src/platform/tests/Makefile.in545
-rw-r--r--src/platform/tests/dump.c33
-rw-r--r--src/platform/tests/platform.c119
-rw-r--r--src/platform/tests/test-address.c108
-rw-r--r--src/platform/tests/test-cleanup.c48
-rw-r--r--src/platform/tests/test-common.c201
-rw-r--r--src/platform/tests/test-common.h23
-rw-r--r--src/platform/tests/test-general.c67
-rw-r--r--src/platform/tests/test-link.c435
-rw-r--r--src/platform/tests/test-nmp-object.c425
-rw-r--r--src/platform/tests/test-route.c131
-rw-r--r--src/platform/wifi/wifi-utils.c9
-rw-r--r--src/platform/wifi/wifi-utils.h2
24 files changed, 8772 insertions, 4331 deletions
diff --git a/src/platform/Makefile.in b/src/platform/Makefile.in
index a41de1ae..0616c88b 100644
--- a/src/platform/Makefile.in
+++ b/src/platform/Makefile.in
@@ -195,6 +195,7 @@ ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -308,6 +309,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 7b0d2d64..d5aeb030 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -24,12 +24,16 @@
 #include <unistd.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
+#include <linux/rtnetlink.h>
 
 #include "gsystem-local-alloc.h"
+#include "nm-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-fake-platform.h"
 #include "nm-logging.h"
 
+#include "nm-test-utils.h"
+
 #define debug(format, ...) nm_log_dbg (LOGD_PLATFORM, format, __VA_ARGS__)
 
 typedef struct {
@@ -45,8 +49,9 @@ typedef struct {
 	NMPlatformLink link;
 
 	char *udi;
-	GBytes *address;
 	int vlan_id;
+	int ib_p_key;
+	struct in6_addr ip6_lladdr;
 } NMFakePlatformLink;
 
 #define NM_FAKE_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_FAKE_PLATFORM, NMFakePlatformPrivate))
@@ -55,11 +60,12 @@ G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
 
 /******************************************************************/
 
-void
-nm_fake_platform_setup (void)
-{
-	nm_platform_setup (NM_TYPE_FAKE_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_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen);
 
 /******************************************************************/
 
@@ -110,25 +116,31 @@ type_to_type_name (NMLinkType type)
 static void
 link_init (NMFakePlatformLink *device, int ifindex, int type, const char *name)
 {
+	gs_free char *ip6_lladdr = NULL;
+
 	g_assert (!name || strlen (name) < sizeof(device->link.name));
 
 	memset (device, 0, sizeof (*device));
 
+	ip6_lladdr = ifindex > 0 ? g_strdup_printf ("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL;
+
 	device->link.ifindex = name ? ifindex : 0;
 	device->link.type = type;
-	device->link.type_name = type_to_type_name (type);
+	device->link.kind = type_to_type_name (type);
 	device->link.driver = type_to_type_name (type);
-	device->link.udi = device->udi = g_strdup_printf ("fake:%d", ifindex);
+	device->udi = g_strdup_printf ("fake:%d", ifindex);
+	device->link.initialized = TRUE;
+	device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr);
 	if (name)
 		strcpy (device->link.name, name);
 	switch (device->link.type) {
 	case NM_LINK_TYPE_DUMMY:
-		device->link.arp = FALSE;
+		device->link.flags = NM_FLAGS_SET (device->link.flags, IFF_NOARP);
 		break;
 	default:
-		device->link.arp = TRUE;
+		device->link.flags = NM_FLAGS_UNSET (device->link.flags, IFF_NOARP);
+		break;
 	}
-	device->address = NULL;
 }
 
 static NMFakePlatformLink *
@@ -146,7 +158,6 @@ link_get (NMPlatform *platform, int ifindex)
 	return device;
 not_found:
 	debug ("link not found: %d", ifindex);
-	platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
 	return NULL;
 }
 
@@ -164,18 +175,60 @@ link_get_all (NMPlatform *platform)
 	return links;
 }
 
-static gboolean
-_nm_platform_link_get (NMPlatform *platform, int ifindex, NMPlatformLink *l)
+static const NMPlatformLink *
+_nm_platform_link_get (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
 
-	if (device)
-		*l = device->link;
-	return !!device;
+	return device ? &device->link : NULL;
+}
+
+static const NMPlatformLink *
+_nm_platform_link_get_by_ifname (NMPlatform *platform, const char *ifname)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	guint i;
+
+	for (i = 0; i < priv->links->len; i++) {
+		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+
+		if (!strcmp (device->link.name, ifname))
+			return &device->link;
+	}
+	return NULL;
+}
+
+static const NMPlatformLink *
+_nm_platform_link_get_by_address (NMPlatform *platform,
+                                  gconstpointer address,
+                                  size_t length)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	guint i;
+
+	if (   length == 0
+	    || length > NM_UTILS_HWADDR_LEN_MAX
+	    || !address)
+		g_return_val_if_reached (NULL);
+
+	for (i = 0; i < priv->links->len; i++) {
+		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+
+		if (   device->link.addr.len == length
+		    && memcmp (device->link.addr.data, address, length) == 0) {
+			return &device->link;
+		}
+	}
+	return NULL;
 }
 
 static gboolean
-link_add (NMPlatform *platform, const char *name, NMLinkType type, const void *address, size_t address_len)
+link_add (NMPlatform *platform,
+          const char *name,
+          NMLinkType type,
+          const void *address,
+          size_t address_len,
+          NMPlatformLink *out_link)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMFakePlatformLink device;
@@ -184,9 +237,14 @@ link_add (NMPlatform *platform, const char *name, NMLinkType type, const void *a
 
 	g_array_append_val (priv->links, device);
 
-	if (device.link.ifindex)
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, device.link.ifindex, &device, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+	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);
+
+		link_changed (platform, &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1), FALSE);
+	}
 
+	if (out_link)
+		*out_link = device.link;
 	return TRUE;
 }
 
@@ -230,99 +288,88 @@ link_delete (NMPlatform *platform, int ifindex)
 			memset (route, 0, sizeof (*route));
 	}
 
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 
 	return TRUE;
 }
 
-static int
-link_get_ifindex (NMPlatform *platform, const char *name)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
-	int i;
-
-	for (i = 0; i < priv->links->len; i++) {
-		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
-
-		if (device && !g_strcmp0 (device->link.name, name))
-			return device->link.ifindex;
-	}
-
-	return 0;
-}
-
-static const char *
-link_get_name (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	return device ? device->link.name : NULL;
-}
-
-static NMLinkType
-link_get_type (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	return device ? device->link.type : NM_LINK_TYPE_NONE;
-}
-
 static const char *
 link_get_type_name (NMPlatform *platform, int ifindex)
 {
-	return type_to_type_name (link_get_type (platform, ifindex));
+	return type_to_type_name (nm_platform_link_get_type (platform, ifindex));
 }
 
 static void
-link_changed (NMPlatform *platform, NMFakePlatformLink *device)
+link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	int i;
 
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, device->link.ifindex, &device->link, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+	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);
+
+	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);
+		else
+			ip6_address_delete (platform, device->link.ifindex, device->ip6_lladdr, 64);
+	}
 
 	if (device->link.master) {
+		gboolean connected = FALSE;
+
 		NMFakePlatformLink *master = link_get (platform, device->link.master);
 
-		g_return_if_fail (master != device);
+		g_return_if_fail (master && master != device);
 
-		master->link.connected = FALSE;
 		for (i = 0; i < priv->links->len; i++) {
 			NMFakePlatformLink *slave = &g_array_index (priv->links, NMFakePlatformLink, i);
 
 			if (slave && slave->link.master == master->link.ifindex && slave->link.connected)
-				master->link.connected = TRUE;
+				connected = TRUE;
 		}
 
-		link_changed (platform, master);
+		if (master->link.connected != connected) {
+			master->link.connected = connected;
+			link_changed (platform, master, TRUE);
+		}
 	}
 }
 
 static gboolean
-link_set_up (NMPlatform *platform, int ifindex)
+link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
+	gboolean up, connected;
+
+	if (out_no_firmware)
+		*out_no_firmware = FALSE;
 
 	if (!device)
 		return FALSE;
 
-	device->link.up = TRUE;
+	up = TRUE;
+	connected = TRUE;
 	switch (device->link.type) {
 	case NM_LINK_TYPE_DUMMY:
 	case NM_LINK_TYPE_VLAN:
-		device->link.connected = TRUE;
 		break;
 	case NM_LINK_TYPE_BRIDGE:
 	case NM_LINK_TYPE_BOND:
 	case NM_LINK_TYPE_TEAM:
-		device->link.connected = FALSE;
+		connected = FALSE;
 		break;
 	default:
-		device->link.connected = FALSE;
+		connected = FALSE;
 		g_error ("Unexpected device type: %d", device->link.type);
 	}
 
-	link_changed (platform, device);
+	if (   NM_FLAGS_HAS (device->link.flags, IFF_UP) != !!up
+	    || device->link.connected != connected) {
+		device->link.flags = NM_FLAGS_ASSIGN (device->link.flags, IFF_UP, up);
+		device->link.connected = connected;
+		link_changed (platform, device, TRUE);
+	}
 
 	return TRUE;
 }
@@ -335,10 +382,12 @@ link_set_down (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	device->link.up = FALSE;
-	device->link.connected = FALSE;
+	if (NM_FLAGS_HAS (device->link.flags, IFF_UP) || device->link.connected) {
+		device->link.flags = NM_FLAGS_UNSET (device->link.flags, IFF_UP);
+		device->link.connected = FALSE;
 
-	link_changed (platform, device);
+		link_changed (platform, device, TRUE);
+	}
 
 	return TRUE;
 }
@@ -351,9 +400,9 @@ link_set_arp (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	device->link.arp = TRUE;
+	device->link.flags = NM_FLAGS_UNSET (device->link.flags, IFF_NOARP);
 
-	link_changed (platform, device);
+	link_changed (platform, device, TRUE);
 
 	return TRUE;
 }
@@ -366,64 +415,32 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	device->link.arp = FALSE;
+	device->link.flags = NM_FLAGS_SET (device->link.flags, IFF_NOARP);
 
-	link_changed (platform, device);
+	link_changed (platform, device, TRUE);
 
 	return TRUE;
 }
 
 static gboolean
-link_is_up (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	return device ? device->link.up : FALSE;
-}
-
-static gboolean
-link_is_connected (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	return device ? device->link.connected : FALSE;
-}
-
-static gboolean
-link_uses_arp (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	return device ? device->link.arp : FALSE;
-}
-
-static gboolean
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
 
-	if (device->address)
-		g_bytes_unref (device->address);
-
-	device->address = g_bytes_new (addr, len);
-
-	link_changed (platform, link_get (platform, ifindex));
-
-	return TRUE;
-}
-
-static gconstpointer
-link_get_address (NMPlatform *platform, int ifindex, size_t *length)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	if (!device || !device->address) {
-		if (length)
-			*length = 0;
-		return NULL;
+	if (   len == 0
+	    || len > NM_UTILS_HWADDR_LEN_MAX
+	    || !addr)
+		g_return_val_if_reached (FALSE);
+
+	if (   device->link.addr.len != len
+	    || (   len > 0
+	        && memcmp (device->link.addr.data, addr, len) != 0)) {
+		memcpy (device->link.addr.data, addr, len);
+		device->link.addr.len = len;
+		link_changed (platform, link_get (platform, ifindex), TRUE);
 	}
 
-	return g_bytes_get_data (device->address, length);
+	return TRUE;
 }
 
 static gboolean
@@ -433,45 +450,37 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 
 	if (device) {
 		device->link.mtu = mtu;
-		link_changed (platform, device);
+		link_changed (platform, device, TRUE);
 	}
 
 	return !!device;
 }
 
-static guint32
-link_get_mtu (NMPlatform *platform, int ifindex)
+static const char *
+link_get_udi (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
 
-	return device ? device->link.mtu : 0;
-}
-
-static char *
-link_get_physical_port_id (NMPlatform *platform, int ifindex)
-{
-	/* We call link_get just to cause an error to be set if @ifindex is bad. */
-	link_get (platform, ifindex);
-
-	return NULL;
-}
-
-static guint
-link_get_dev_id (NMPlatform *platform, int ifindex)
-{
-	/* We call link_get just to cause an error to be set if @ifindex is bad. */
-	link_get (platform, ifindex);
-
-	return 0;
+	if (!device)
+		return NULL;
+	return device->udi;
 }
 
 static gboolean
-link_get_wake_on_lan (NMPlatform *platform, int ifindex)
-{
-	/* We call link_get just to cause an error to be set if @ifindex is bad. */
-	link_get (platform, ifindex);
+link_get_driver_info (NMPlatform *platform,
+                      int ifindex,
+                      char **out_driver_name,
+                      char **out_driver_version,
+                      char **out_fw_version)
+{
+	if (out_driver_name)
+		*out_driver_name = NULL;
+	if (out_driver_version)
+		*out_driver_version = NULL;
+	if (out_fw_version)
+		*out_fw_version = NULL;
 
-	return FALSE;
+	return TRUE;
 }
 
 static gboolean
@@ -510,12 +519,21 @@ static gboolean
 link_enslave (NMPlatform *platform, int master, int slave)
 {
 	NMFakePlatformLink *device = link_get (platform, slave);
+	NMFakePlatformLink *master_device = link_get (platform, master);
 
 	g_return_val_if_fail (device, FALSE);
+	g_return_val_if_fail (master_device, FALSE);
 
-	device->link.master = master;
+	if (device->link.master != master) {
+		device->link.master = master;
 
-	link_changed (platform, device);
+		if (NM_IN_SET (master_device->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) {
+			device->link.flags = NM_FLAGS_SET (device->link.flags, IFF_UP);
+			device->link.connected = TRUE;
+		}
+
+		link_changed (platform, device, TRUE);
+	}
 
 	return TRUE;
 }
@@ -529,29 +547,17 @@ link_release (NMPlatform *platform, int master_idx, int slave_idx)
 	g_return_val_if_fail (master, FALSE);
 	g_return_val_if_fail (slave, FALSE);
 
-	if (slave->link.master != master->link.ifindex) {
-		platform->error = NM_PLATFORM_ERROR_NOT_SLAVE;
+	if (slave->link.master != master->link.ifindex)
 		return FALSE;
-	}
 
 	slave->link.master = 0;
 
-	link_changed (platform, slave);
-	link_changed (platform, master);
+	link_changed (platform, slave, TRUE);
+	link_changed (platform, master, TRUE);
 
 	return TRUE;
 }
 
-static int
-link_get_master (NMPlatform *platform, int slave)
-{
-	NMFakePlatformLink *device = link_get (platform, slave);
-
-	g_return_val_if_fail (device, FALSE);
-
-	return device->link.master;
-}
-
 static gboolean
 master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
 {
@@ -585,20 +591,22 @@ slave_get_option (NMPlatform *platform, int slave, const char *option)
 }
 
 static gboolean
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags)
+vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, NMPlatformLink *out_link)
 {
 	NMFakePlatformLink *device;
 
-	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0))
+	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, NULL))
 		return FALSE;
 
-	device = link_get (platform, link_get_ifindex (platform, name));
+	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
 
 	g_return_val_if_fail (device, FALSE);
 
 	device->vlan_id = vlan_id;
 	device->link.parent = parent;
 
+	if (out_link)
+		*out_link = device->link;
 	return TRUE;
 }
 
@@ -630,20 +638,44 @@ vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to)
 }
 
 static gboolean
-infiniband_partition_add (NMPlatform *platform, int parent, int p_key)
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link)
 {
-	NMFakePlatformLink *parent_device;
-	char *name;
-	gboolean success;
+	NMFakePlatformLink *device, *parent_device;
+	gs_free char *name = NULL;
 
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
 	name = g_strdup_printf ("%s.%04x", parent_device->link.name, p_key);
-	success = link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0);
-	g_free (name);
+	if (!link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0, out_link))
+		return FALSE;
+
+	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
+	g_return_val_if_fail (device, FALSE);
+
+	device->ib_p_key = p_key;
+	device->link.parent = parent;
 
-	return success;
+	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
@@ -861,12 +893,12 @@ ip4_address_add (NMPlatform *platform, int ifindex,
 			continue;
 
 		memcpy (item, &address, sizeof (address));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED, 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_CHANGED, NM_PLATFORM_REASON_INTERNAL);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip4_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 
 	return TRUE;
 }
@@ -901,13 +933,15 @@ ip6_address_add (NMPlatform *platform, int ifindex,
 		if (item->plen != address.plen)
 			continue;
 
-		memcpy (item, &address, sizeof (address));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		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);
+		}
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip6_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 
 	return TRUE;
 }
@@ -927,7 +961,7 @@ ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen,
 
 			memcpy (&deleted_address, address, sizeof (deleted_address));
 			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 			return TRUE;
 		}
 	}
@@ -950,7 +984,7 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int
 
 			memcpy (&deleted_address, address, sizeof (deleted_address));
 			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 			return TRUE;
 		}
 	}
@@ -958,8 +992,8 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int
 	return TRUE;
 }
 
-static gboolean
-ip4_address_exists (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+static const NMPlatformIP4Address *
+ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	int i;
@@ -968,14 +1002,14 @@ ip4_address_exists (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
 
 		if (address->ifindex == ifindex && address->plen == plen && address->address == addr)
-			return TRUE;
+			return address;
 	}
 
-	return FALSE;
+	return NULL;
 }
 
-static gboolean
-ip6_address_exists (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
+static const NMPlatformIP6Address *
+ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	int i;
@@ -985,41 +1019,36 @@ ip6_address_exists (NMPlatform *platform, int ifindex, struct in6_addr addr, int
 
 		if (address->ifindex == ifindex && address->plen == plen &&
 				IN6_ARE_ADDR_EQUAL (&address->address, &addr))
-			return TRUE;
+			return address;
 	}
 
-	return FALSE;
-}
-
-static gboolean
-ip4_check_reinstall_device_route (NMPlatform *platform, int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric)
-{
-	return FALSE;
+	return NULL;
 }
 
 /******************************************************************/
 
 static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mode)
+ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	GArray *routes;
 	NMPlatformIP4Route *route;
 	guint i;
 
-	g_return_val_if_fail (NM_IN_SET (mode, NM_PLATFORM_GET_ROUTE_MODE_ALL, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT), NULL);
-
 	routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route));
 
+	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
+		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
+
 	/* Fill routes */
 	for (i = 0; i < priv->ip4_routes->len; i++) {
 		route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
 		if (route && (!ifindex || route->ifindex == ifindex)) {
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-				if (mode != NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT)
+				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
 					g_array_append_val (routes, *route);
 			} else {
-				if (mode != NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT)
+				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
 					g_array_append_val (routes, *route);
 			}
 		}
@@ -1029,26 +1058,27 @@ ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mod
 }
 
 static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mode)
+ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	GArray *routes;
 	NMPlatformIP6Route *route;
 	guint i;
 
-	g_return_val_if_fail (NM_IN_SET (mode, NM_PLATFORM_GET_ROUTE_MODE_ALL, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT), NULL);
-
 	routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));
 
+	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
+		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
+
 	/* Fill routes */
 	for (i = 0; i < priv->ip6_routes->len; i++) {
 		route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
 		if (route && (!ifindex || route->ifindex == ifindex)) {
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-				if (mode != NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT)
+				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
 					g_array_append_val (routes, *route);
 			} else {
-				if (mode != NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT)
+				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
 					g_array_append_val (routes, *route);
 			}
 		}
@@ -1058,6 +1088,56 @@ ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mod
 }
 
 static gboolean
+ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip4_routes->len; i++) {
+		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+		NMPlatformIP4Route deleted_route;
+
+		if (   route->ifindex != ifindex
+		    || route->network != network
+		    || route->plen != plen
+		    || route->metric != metric)
+			continue;
+
+		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);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	metric = nm_utils_ip6_route_metric_normalize (metric);
+
+	for (i = 0; i < priv->ip6_routes->len; i++) {
+		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
+		NMPlatformIP6Route deleted_route;
+
+		if (   route->ifindex != ifindex
+		    || !IN6_ARE_ADDR_EQUAL (&route->network, &network)
+		    || route->plen != plen
+		    || route->metric != metric)
+			continue;
+
+		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);
+	}
+
+	return TRUE;
+}
+
+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)
@@ -1065,22 +1145,41 @@ ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMPlatformIP4Route route;
 	guint i;
+	guint8 scope;
+
+	scope = gateway == 0 ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE;
 
 	memset (&route, 0, sizeof (route));
 	route.source = NM_IP_CONFIG_SOURCE_KERNEL;
 	route.ifindex = ifindex;
 	route.source = source;
-	route.network = network;
+	route.network = nm_utils_ip4_address_clear_host_address (network, plen);
 	route.plen = plen;
 	route.gateway = gateway;
 	route.metric = metric;
 	route.mss = mss;
+	route.scope_inv = nm_platform_route_scope_inv (scope);
+
+	if (gateway) {
+		for (i = 0; i < priv->ip4_routes->len; i++) {
+			NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes,
+			                                           NMPlatformIP4Route, i);
+			guint32 gate = ntohl (item->network) >> (32 - item->plen);
+			guint32 host = ntohl (gateway) >> (32 - item->plen);
+
+			if (ifindex == item->ifindex && gate == host)
+				break;
+		}
+		if (i == priv->ip4_routes->len) {
+			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
+			             route.ifindex, nm_utils_inet4_ntop (route.network, NULL), route.plen, route.metric);
+			return FALSE;
+		}
+	}
 
 	for (i = 0; i < priv->ip4_routes->len; i++) {
 		NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
 
-		if (item->ifindex != route.ifindex)
-			continue;
 		if (item->network != route.network)
 			continue;
 		if (item->plen != route.plen)
@@ -1088,13 +1187,19 @@ ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 		if (item->metric != metric)
 			continue;
 
+		if (item->ifindex != route.ifindex) {
+			ip4_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
+			i--;
+			continue;
+		}
+
 		memcpy (item, &route, sizeof (route));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip4_routes, route);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 
 	return TRUE;
 }
@@ -1114,17 +1219,34 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 	route.source = NM_IP_CONFIG_SOURCE_KERNEL;
 	route.ifindex = ifindex;
 	route.source = source;
-	route.network = network;
+	nm_utils_ip6_address_clear_host_address (&route.network, &network, plen);
 	route.plen = plen;
 	route.gateway = gateway;
 	route.metric = metric;
 	route.mss = mss;
 
+	if (!IN6_IS_ADDR_UNSPECIFIED(&gateway)) {
+		for (i = 0; i < priv->ip6_routes->len; i++) {
+			NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes,
+			                                           NMPlatformIP6Route, i);
+			guint8 gate_bits = gateway.s6_addr[item->plen / 8] >> (8 - item->plen % 8);
+			guint8 host_bits = item->network.s6_addr[item->plen / 8] >> (8 - item->plen % 8);
+
+			if (   ifindex == item->ifindex
+			    && memcmp (&gateway, &item->network, item->plen / 8) == 0
+			    && gate_bits == host_bits)
+				break;
+		}
+		if (i == priv->ip6_routes->len) {
+			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
+			             route.ifindex, nm_utils_inet6_ntop (&route.network, NULL), route.plen, route.metric);
+			return FALSE;
+		}
+	}
+
 	for (i = 0; i < priv->ip6_routes->len; i++) {
 		NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
 
-		if (item->ifindex != route.ifindex)
-			continue;
 		if (!IN6_ARE_ADDR_EQUAL (&item->network, &route.network))
 			continue;
 		if (item->plen != route.plen)
@@ -1132,18 +1254,24 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 		if (item->metric != metric)
 			continue;
 
+		if (item->ifindex != route.ifindex) {
+			ip6_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
+			i--;
+			continue;
+		}
+
 		memcpy (item, &route, sizeof (route));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 		return TRUE;
 	}
 
 	g_array_append_val (priv->ip6_routes, route);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, 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, NM_PLATFORM_REASON_INTERNAL);
 
 	return TRUE;
 }
 
-static NMPlatformIP4Route *
+static const NMPlatformIP4Route *
 ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
@@ -1162,7 +1290,7 @@ ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, g
 	return NULL;
 }
 
-static NMPlatformIP6Route *
+static const NMPlatformIP6Route *
 ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
@@ -1183,48 +1311,6 @@ ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int p
 	return NULL;
 }
 
-static gboolean
-ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
-{
-	NMPlatformIP4Route *route = ip4_route_get (platform, ifindex, network, plen, metric);
-	NMPlatformIP4Route deleted_route;
-
-	if (route) {
-		memcpy (&deleted_route, route, sizeof (deleted_route));
-		memset (route, 0, sizeof (*route));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
-	}
-
-	return TRUE;
-}
-
-static gboolean
-ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
-{
-	NMPlatformIP6Route *route = ip6_route_get (platform, ifindex, network, plen, metric);
-	NMPlatformIP6Route deleted_route;
-
-	if (route) {
-		memcpy (&deleted_route, route, sizeof (deleted_route));
-		memset (route, 0, sizeof (*route));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
-	}
-
-	return TRUE;
-}
-
-static gboolean
-ip4_route_exists (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
-{
-	return !!ip4_route_get (platform, ifindex, network, plen, metric);
-}
-
-static gboolean
-ip6_route_exists (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
-{
-	return !!ip6_route_get (platform, ifindex, network, plen, metric);
-}
-
 /******************************************************************/
 
 static void
@@ -1240,21 +1326,25 @@ nm_fake_platform_init (NMFakePlatform *fake_platform)
 	priv->ip6_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));
 }
 
-static gboolean
-setup (NMPlatform *platform)
+void
+nm_fake_platform_setup (void)
 {
+	NMPlatform *platform;
+
+	platform = g_object_new (NM_TYPE_FAKE_PLATFORM, NULL);
+
+	nm_platform_setup (platform);
+
 	/* skip zero element */
-	link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0);
+	link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0, NULL);
 
 	/* add loopback interface */
-	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0);
+	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0, NULL);
 
 	/* add some ethernets */
-	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, 0);
-	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, 0);
-	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, 0);
-
-	return TRUE;
+	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
+	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
+	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
 }
 
 static void
@@ -1267,7 +1357,6 @@ nm_fake_platform_finalize (GObject *object)
 	for (i = 0; i < priv->links->len; i++) {
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
-		g_bytes_unref (device->address);
 		g_free (device->udi);
 	}
 	g_array_unref (priv->links);
@@ -1290,43 +1379,34 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	/* virtual methods */
 	object_class->finalize = nm_fake_platform_finalize;
 
-	platform_class->setup = setup;
-
 	platform_class->sysctl_set = sysctl_set;
 	platform_class->sysctl_get = sysctl_get;
 
 	platform_class->link_get = _nm_platform_link_get;
+	platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname;
+	platform_class->link_get_by_address = _nm_platform_link_get_by_address;
 	platform_class->link_get_all = link_get_all;
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
-	platform_class->link_get_ifindex = link_get_ifindex;
-	platform_class->link_get_name = link_get_name;
-	platform_class->link_get_type = link_get_type;
 	platform_class->link_get_type_name = link_get_type_name;
 
+	platform_class->link_get_udi = link_get_udi;
+
 	platform_class->link_set_up = link_set_up;
 	platform_class->link_set_down = link_set_down;
 	platform_class->link_set_arp = link_set_arp;
 	platform_class->link_set_noarp = link_set_noarp;
-	platform_class->link_is_up = link_is_up;
-	platform_class->link_is_connected = link_is_connected;
-	platform_class->link_uses_arp = link_uses_arp;
 
 	platform_class->link_set_address = link_set_address;
-	platform_class->link_get_address = link_get_address;
-	platform_class->link_get_mtu = link_get_mtu;
 	platform_class->link_set_mtu = link_set_mtu;
 
-	platform_class->link_get_physical_port_id = link_get_physical_port_id;
-	platform_class->link_get_dev_id = link_get_dev_id;
-	platform_class->link_get_wake_on_lan = link_get_wake_on_lan;
+	platform_class->link_get_driver_info = link_get_driver_info;
 
 	platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
 	platform_class->link_supports_vlans = link_supports_vlans;
 
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
-	platform_class->link_get_master = link_get_master;
 	platform_class->master_set_option = master_set_option;
 	platform_class->master_get_option = master_get_option;
 	platform_class->slave_set_option = slave_set_option;
@@ -1338,6 +1418,7 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->vlan_set_egress_map = vlan_set_egress_map;
 
 	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;
@@ -1360,23 +1441,21 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
+	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;
 	platform_class->ip6_address_get_all = ip6_address_get_all;
 	platform_class->ip4_address_add = ip4_address_add;
 	platform_class->ip6_address_add = ip6_address_add;
 	platform_class->ip4_address_delete = ip4_address_delete;
 	platform_class->ip6_address_delete = ip6_address_delete;
-	platform_class->ip4_address_exists = ip4_address_exists;
-	platform_class->ip6_address_exists = ip6_address_exists;
-
-	platform_class->ip4_check_reinstall_device_route = ip4_check_reinstall_device_route;
 
+	platform_class->ip4_route_get = ip4_route_get;
+	platform_class->ip6_route_get = ip6_route_get;
 	platform_class->ip4_route_get_all = ip4_route_get_all;
 	platform_class->ip6_route_get_all = ip6_route_get_all;
 	platform_class->ip4_route_add = ip4_route_add;
 	platform_class->ip6_route_add = ip6_route_add;
 	platform_class->ip4_route_delete = ip4_route_delete;
 	platform_class->ip6_route_delete = ip6_route_delete;
-	platform_class->ip4_route_exists = ip4_route_exists;
-	platform_class->ip6_route_exists = ip6_route_exists;
 }
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index c337ee9c..80757f4e 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012-2013 Red Hat, Inc.
+ * Copyright (C) 2012-2015 Red Hat, Inc.
  */
 #include "config.h"
 
@@ -31,10 +31,6 @@
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
-#include <sys/ioctl.h>
-#include <linux/sockios.h>
-#include <linux/ethtool.h>
-#include <linux/mii.h>
 #include <netlink/netlink.h>
 #include <netlink/object.h>
 #include <netlink/cache.h>
@@ -44,9 +40,9 @@
 #include <netlink/route/route.h>
 #include <gudev/gudev.h>
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE || HAVE_LIBNL_INET6_TOKEN
 #include <netlink/route/link/inet6.h>
-#if HAVE_KERNEL_INET6_ADDR_GEN_MODE
+#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
@@ -55,21 +51,101 @@
 #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 "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
+#include "nmp-object.h"
 
 /* This is only included for the translation of VLAN flags */
 #include "nm-setting-vlan.h"
 
-#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
-#define warning(...) nm_log_warn (LOGD_PLATFORM, __VA_ARGS__)
-#define error(...) nm_log_err (LOGD_PLATFORM, __VA_ARGS__)
+/*********************************************************************************************/
+
+#define _LOG_DOMAIN LOGD_PLATFORM
+#define _LOG_PREFIX_NAME "platform-linux"
+
+#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 = _LOG_PREFIX_NAME; \
+            const void *const __self = (self); \
+            \
+            if (__self && __self != nm_platform_try_get ()) { \
+                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _LOG_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
+#define _LOG_LEVEL_ENABLED(level, domain) \
+    ( nm_logging_enabled ((level), (domain)) )
+
+#ifdef NM_MORE_LOGGING
+#define _LOGT_ENABLED()     _LOG_LEVEL_ENABLED (LOGL_TRACE, _LOG_DOMAIN)
+#define _LOGT(...)          _LOG (LOGL_TRACE, _LOG_DOMAIN, platform, __VA_ARGS__)
+#else
+#define _LOGT_ENABLED()     FALSE
+#define _LOGT(...)          G_STMT_START { if (FALSE) { _LOG (LOGL_TRACE, _LOG_DOMAIN, platform, __VA_ARGS__); } } G_STMT_END
+#endif
+
+#define _LOGD(...)      _LOG (LOGL_DEBUG, _LOG_DOMAIN, platform, __VA_ARGS__)
+#define _LOGI(...)      _LOG (LOGL_INFO , _LOG_DOMAIN, platform, __VA_ARGS__)
+#define _LOGW(...)      _LOG (LOGL_WARN , _LOG_DOMAIN, platform, __VA_ARGS__)
+#define _LOGE(...)      _LOG (LOGL_ERR  , _LOG_DOMAIN, platform, __VA_ARGS__)
+
+#define debug(...)      _LOG (LOGL_DEBUG, _LOG_DOMAIN, NULL, __VA_ARGS__)
+#define warning(...)    _LOG (LOGL_WARN , _LOG_DOMAIN, NULL, __VA_ARGS__)
+#define error(...)      _LOG (LOGL_ERR  , _LOG_DOMAIN, NULL, __VA_ARGS__)
+
+/******************************************************************
+ * Forward declarations and enums
+ ******************************************************************/
 
+typedef enum {
+	DELAYED_ACTION_TYPE_NONE                        = 0,
+	DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS           = (1LL << 0),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES   = (1LL << 1),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES   = (1LL << 2),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES      = (1LL << 3),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES      = (1LL << 4),
+	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL << 5),
+	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL << 6),
+	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL << 7),
+	__DELAYED_ACTION_TYPE_MAX,
+
+	DELAYED_ACTION_TYPE_REFRESH_ALL                 = 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,
+
+	DELAYED_ACTION_TYPE_MAX                         = __DELAYED_ACTION_TYPE_MAX -1,
+} DelayedActionType;
+
+static gboolean tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props);
+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 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
 {
@@ -78,41 +154,6 @@ struct libnl_vtable
 	int (*f_nl_has_capability) (int capability);
 };
 
-
-typedef struct {
-	struct nl_sock *nlh;
-	struct nl_sock *nlh_event;
-	struct nl_cache *link_cache;
-	struct nl_cache *address_cache;
-	struct nl_cache *route_cache;
-	GIOChannel *event_channel;
-	guint event_id;
-
-	GUdevClient *udev_client;
-	GHashTable *udev_devices;
-
-	GHashTable *wifi_data;
-
-	int support_kernel_extended_ifa_flags;
-	int support_user_ipv6ll;
-} NMLinuxPlatformPrivate;
-
-#define NM_LINUX_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate))
-
-G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-static const char *to_string_object (NMPlatform *platform, struct nl_object *obj);
-static gboolean _address_match (struct rtnl_addr *addr, int family, int ifindex);
-static gboolean _route_match (struct rtnl_route *rtnlroute, int family, int ifindex, gboolean include_proto_kernel);
-
-void
-nm_linux_platform_setup (void)
-{
-	nm_platform_setup (NM_TYPE_LINUX_PLATFORM);
-}
-
-/******************************************************************/
-
 static int
 _nl_f_nl_has_capability (int capability)
 {
@@ -148,73 +189,10 @@ _nl_has_capability (int capability)
 	return (_nl_get_vtable ()->f_nl_has_capability) (capability);
 }
 
-/******************************************************************/
-
-static guint32
-_get_expiry (guint32 now_s, guint32 lifetime_s)
-{
-	gint64 t = ((gint64) now_s) + ((gint64) lifetime_s);
-
-	return MIN (t, 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.
- *
- * As we cache the rtnl_addr object we must know the absolute expiries.
- * As a hack, modify the relative timestamps valid and preferred into absolute
- * timestamps of scale nm_utils_get_monotonic_timestamp_s().
- **/
-static void
-_rtnl_addr_hack_lifetimes_rel_to_abs (struct rtnl_addr *rtnladdr)
-{
-	guint32 a_valid  = rtnl_addr_get_valid_lifetime (rtnladdr);
-	guint32 a_preferred = rtnl_addr_get_preferred_lifetime (rtnladdr);
-	guint32 now;
-
-	if (a_valid == NM_PLATFORM_LIFETIME_PERMANENT &&
-	    a_preferred == NM_PLATFORM_LIFETIME_PERMANENT)
-		return;
-
-	now = (guint32) nm_utils_get_monotonic_timestamp_s ();
-
-	if (a_preferred > a_valid)
-		a_preferred = a_valid;
-
-	if (a_valid != NM_PLATFORM_LIFETIME_PERMANENT)
-		rtnl_addr_set_valid_lifetime (rtnladdr, _get_expiry (now, a_valid));
-	rtnl_addr_set_preferred_lifetime (rtnladdr, _get_expiry (now, a_preferred));
-}
-
-/******************************************************************/
-
-/* libnl library workarounds and additions */
-
 /* Automatic deallocation of local variables */
-#define auto_nl_cache __attribute__((cleanup(put_nl_cache)))
+#define auto_nl_object __attribute__((cleanup(_nl_auto_nl_object)))
 static void
-put_nl_cache (void *ptr)
-{
-	struct nl_cache **cache = ptr;
-
-	if (cache && *cache) {
-		nl_cache_free (*cache);
-		*cache = NULL;
-	}
-}
-
-#define auto_nl_object __attribute__((cleanup(put_nl_object)))
-static void
-put_nl_object (void *ptr)
+_nl_auto_nl_object (void *ptr)
 {
 	struct nl_object **object = ptr;
 
@@ -224,9 +202,9 @@ put_nl_object (void *ptr)
 	}
 }
 
-#define auto_nl_addr __attribute__((cleanup(put_nl_addr)))
+#define auto_nl_addr __attribute__((cleanup(_nl_auto_nl_addr)))
 static void
-put_nl_addr (void *ptr)
+_nl_auto_nl_addr (void *ptr)
 {
 	struct nl_addr **object = ptr;
 
@@ -236,12 +214,10 @@ put_nl_addr (void *ptr)
 	}
 }
 
-/*******************************************************************/
-
 /* wrap the libnl alloc functions and abort on out-of-memory*/
 
 static struct nl_addr *
-_nm_nl_addr_build (int family, const void *buf, size_t size)
+_nl_addr_build (int family, const void *buf, size_t size)
 {
 	struct nl_addr *addr;
 
@@ -253,7 +229,7 @@ _nm_nl_addr_build (int family, const void *buf, size_t size)
 }
 
 static struct rtnl_link *
-_nm_rtnl_link_alloc (int ifindex, const char*name)
+_nl_rtnl_link_alloc (int ifindex, const char*name)
 {
 	struct rtnl_link *rtnllink;
 
@@ -269,7 +245,7 @@ _nm_rtnl_link_alloc (int ifindex, const char*name)
 }
 
 static struct rtnl_addr *
-_nm_rtnl_addr_alloc (int ifindex)
+_nl_rtnl_addr_alloc (int ifindex)
 {
 	struct rtnl_addr *rtnladdr;
 
@@ -282,7 +258,7 @@ _nm_rtnl_addr_alloc (int ifindex)
 }
 
 static struct rtnl_route *
-_nm_rtnl_route_alloc (void)
+_nl_rtnl_route_alloc (void)
 {
 	struct rtnl_route *rtnlroute = rtnl_route_alloc ();
 
@@ -292,7 +268,7 @@ _nm_rtnl_route_alloc (void)
 }
 
 static struct rtnl_nexthop *
-_nm_rtnl_route_nh_alloc (void)
+_nl_rtnl_route_nh_alloc (void)
 {
 	struct rtnl_nexthop *nexthop;
 
@@ -302,11 +278,9 @@ _nm_rtnl_route_nh_alloc (void)
 	return nexthop;
 }
 
-/*******************************************************************/
-
 /* rtnl_addr_set_prefixlen fails to update the nl_addr prefixlen */
 static void
-nm_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
+_nl_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
 {
 	struct nl_addr *nladdr;
 
@@ -316,188 +290,30 @@ nm_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
 	if (nladdr)
 		nl_addr_set_prefixlen (nladdr, plen);
 }
-#define rtnl_addr_set_prefixlen nm_rtnl_addr_set_prefixlen
-
-typedef enum {
-	OBJECT_TYPE_UNKNOWN,
-	OBJECT_TYPE_LINK,
-	OBJECT_TYPE_IP4_ADDRESS,
-	OBJECT_TYPE_IP6_ADDRESS,
-	OBJECT_TYPE_IP4_ROUTE,
-	OBJECT_TYPE_IP6_ROUTE,
-	__OBJECT_TYPE_LAST,
-} ObjectType;
-
-static ObjectType
-object_type_from_nl_object (const struct nl_object *object)
-{
-	const char *type_str;
 
-	if (!object || !(type_str = nl_object_get_type (object)))
-		return OBJECT_TYPE_UNKNOWN;
-
-	if (!strcmp (type_str, "route/link"))
-		return OBJECT_TYPE_LINK;
-	else if (!strcmp (type_str, "route/addr")) {
-		switch (rtnl_addr_get_family ((struct rtnl_addr *) object)) {
-		case AF_INET:
-			return OBJECT_TYPE_IP4_ADDRESS;
-		case AF_INET6:
-			return OBJECT_TYPE_IP6_ADDRESS;
-		default:
-			return OBJECT_TYPE_UNKNOWN;
-		}
-	} else if (!strcmp (type_str, "route/route")) {
-		switch (rtnl_route_get_family ((struct rtnl_route *) object)) {
-		case AF_INET:
-			return OBJECT_TYPE_IP4_ROUTE;
-		case AF_INET6:
-			return OBJECT_TYPE_IP6_ROUTE;
-		default:
-			return OBJECT_TYPE_UNKNOWN;
-		}
-	} else
-		return OBJECT_TYPE_UNKNOWN;
-}
-
-static void
-_nl_link_family_unset (struct nl_object *obj, int *family)
-{
-	if (!obj || object_type_from_nl_object (obj) != OBJECT_TYPE_LINK)
-		*family = AF_UNSPEC;
-	else {
-		*family = rtnl_link_get_family ((struct rtnl_link *) obj);
-
-		/* Always explicitly set the family to AF_UNSPEC, even if rtnl_link_get_family() might
-		 * already return %AF_UNSPEC. The reason is, that %AF_UNSPEC is the default family
-		 * and libnl nl_object_identical() function will only succeed, if the family is
-		 * explicitly set (which we cannot be sure, unless setting it). */
-		rtnl_link_set_family ((struct rtnl_link *) obj, AF_UNSPEC);
-	}
-}
-
-/* In our link cache, we coerce the family of all link objects to AF_UNSPEC.
- * Thus, before searching for an object, we fixup @needle to have the right
- * id (by resetting the family). */
-static struct nl_object *
-nm_nl_cache_search (struct nl_cache *cache, struct nl_object *needle)
-{
-	int family;
-	struct nl_object *obj;
-
-	_nl_link_family_unset (needle, &family);
-	obj = nl_cache_search (cache, needle);
-	if (family != AF_UNSPEC) {
-		/* restore the family of the @needle instance. If the family was
-		 * unset before, we cannot make it unset again. Thus, in that case
-		 * we cannot undo _nl_link_family_unset() entirely. */
-		rtnl_link_set_family ((struct rtnl_link *) needle, family);
-	}
-
-	return obj;
-}
-
-/* Ask the kernel for an object identical (as in nl_cache_identical) to the
- * needle argument. This is a kernel counterpart for nl_cache_search.
- *
- * The returned object must be freed by the caller with nl_object_put().
- */
-static struct nl_object *
-get_kernel_object (struct nl_sock *sock, struct nl_object *needle)
+static const char *
+_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
 {
-	struct nl_object *object = NULL;
-	ObjectType type = object_type_from_nl_object (needle);
+	const char *str_type = NULL;
 
 	switch (type) {
-	case OBJECT_TYPE_LINK:
-		{
-			int ifindex = rtnl_link_get_ifindex ((struct rtnl_link *) needle);
-			const char *name = rtnl_link_get_name ((struct rtnl_link *) needle);
-			int nle;
-
-			nle = rtnl_link_get_kernel (sock, ifindex, name, (struct rtnl_link **) &object);
-			switch (nle) {
-			case -NLE_SUCCESS:
-				if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-					name = rtnl_link_get_name ((struct rtnl_link *) object);
-					debug ("get_kernel_object for link: %s (%d, family %d)",
-					       name ? name : "(unknown)",
-					       rtnl_link_get_ifindex ((struct rtnl_link *) object),
-					       rtnl_link_get_family ((struct rtnl_link *) object));
-				}
-
-				_nl_link_family_unset (object, &nle);
-				return object;
-			case -NLE_NODEV:
-				debug ("get_kernel_object for link %s (%d) had no result",
-				       name ? name : "(unknown)", ifindex);
-				return NULL;
-			default:
-				error ("get_kernel_object for link %s (%d) failed: %s (%d)",
-				       name ? name : "(unknown)", ifindex, nl_geterror (nle), nle);
-				return NULL;
-			}
-		}
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		/* Fallback to a one-time cache allocation. */
-		{
-			struct nl_cache *cache;
-			int nle;
-
-			/* FIXME: every time we refresh *one* object, we request an
-			 * entire dump. E.g. check_cache_items() gets O(n2) complexitly. */
-
-			nle = nl_cache_alloc_and_fill (
-					nl_cache_ops_lookup (nl_object_get_type (needle)),
-					sock, &cache);
-			if (nle) {
-				error ("get_kernel_object for type %d failed: %s (%d)",
-				       type, nl_geterror (nle), nle);
-				return NULL;
-			}
-
-			object = nl_cache_search (cache, needle);
-
-			nl_cache_free (cache);
-
-			if (object && (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS))
-				_rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object);
-
-			if (object)
-				debug ("get_kernel_object for type %d returned %p", type, object);
-			else
-				debug ("get_kernel_object for type %d had no result", type);
-			return object;
-		}
-	default:
-		g_return_val_if_reached (NULL);
-		return NULL;
+	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;
 }
 
-/* libnl 3.2 doesn't seem to provide such a generic way to add libnl-route objects. */
-static int
-add_kernel_object (struct nl_sock *sock, struct nl_object *object)
-{
-	switch (object_type_from_nl_object (object)) {
-	case OBJECT_TYPE_LINK:
-		return rtnl_link_add (sock, (struct rtnl_link *) object, NLM_F_CREATE);
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return rtnl_addr_add (sock, (struct rtnl_addr *) object, NLM_F_CREATE | NLM_F_REPLACE);
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		return rtnl_route_add (sock, (struct rtnl_route *) object, NLM_F_CREATE | NLM_F_REPLACE);
-	default:
-		g_return_val_if_reached (-NLE_INVAL);
-		return -NLE_INVAL;
-	}
-}
+/******************************************************************/
 
-/* nm_rtnl_link_parse_info_data(): Re-fetches a link from the kernel
+/* _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().
@@ -519,7 +335,7 @@ static struct nla_policy info_data_link_info_policy[IFLA_INFO_MAX + 1] = {
 };
 
 static int
-info_data_parser (struct nl_msg *msg, void *arg)
+_nl_link_parse_info_data_cb (struct nl_msg *msg, void *arg)
 {
 	NMNLInfoDataClosure *closure = arg;
 	struct nlmsghdr *n = nlmsg_hdr (msg);
@@ -548,8 +364,8 @@ info_data_parser (struct nl_msg *msg, void *arg)
 }
 
 static int
-nm_rtnl_link_parse_info_data (struct nl_sock *sk, int ifindex,
-                              NMNLInfoDataParser parser, gpointer parser_data)
+_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;
@@ -568,7 +384,7 @@ nm_rtnl_link_parse_info_data (struct nl_sock *sk, int ifindex,
 	cb = nl_cb_clone (nl_socket_get_cb (sk));
 	if (cb == NULL)
 		return -NLE_NOMEM;
-	nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, info_data_parser, &data);
+	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);
@@ -581,75 +397,171 @@ nm_rtnl_link_parse_info_data (struct nl_sock *sk, int ifindex,
 
 /******************************************************************/
 
-static gboolean
-ethtool_get (const char *name, gpointer edata)
+static int
+_nl_sock_flush_data (struct nl_sock *sk)
+{
+	int nle;
+	struct nl_cb *cb;
+
+	cb = nl_cb_clone (nl_socket_get_cb (sk));
+	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)
 {
-	struct ifreq ifr;
-	int fd;
+	guint32 seq;
 
-	memset (&ifr, 0, sizeof (ifr));
-	strncpy (ifr.ifr_name, name, IFNAMSIZ);
-	ifr.ifr_data = edata;
+	/* 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);
 
-	fd = socket (PF_INET, SOCK_DGRAM, 0);
-	if (fd < 0) {
-		error ("ethtool: Could not open socket.");
-		return FALSE;
-	}
+	nlmsg_hdr (msg)->nlmsg_seq = seq;
+	if (out_seq)
+		*out_seq = seq;
+}
 
-	if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) {
-		debug ("ethtool: Request failed: %s", strerror (errno));
-		close (fd);
-		return FALSE;
-	}
+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;
 
-	close (fd);
-	return TRUE;
+	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
-ethtool_get_stringset_index (const char *ifname, int stringset_id, const char *string)
+_nl_sock_request_all (NMPlatform *platform, struct nl_sock *sk, NMPObjectType obj_type, guint32 *out_seq)
 {
-	gs_free struct ethtool_sset_info *info = NULL;
-	gs_free struct ethtool_gstrings *strings = NULL;
-	guint32 len, i;
+	const NMPClass *klass;
+	struct rtgenmsg gmsg = { 0 };
+	struct nl_msg *msg;
+	int err;
 
-	info = g_malloc0 (sizeof (*info) + sizeof (guint32));
-	info->cmd = ETHTOOL_GSSET_INFO;
-	info->reserved = 0;
-	info->sset_mask = 1ULL << stringset_id;
+	klass = nmp_class_from_type (obj_type);
 
-	if (!ethtool_get (ifname, info))
-		return -1;
-	if (!info->sset_mask)
-		return -1;
+	_LOGT ("sock: request-all-%s", klass->obj_type_name);
 
-	len = info->data[0];
+	/* 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;
 
-	strings = g_malloc0 (sizeof (*strings) + len * ETH_GSTRING_LEN);
-	strings->cmd = ETHTOOL_GSTRINGS;
-	strings->string_set = stringset_id;
-	strings->len = len;
-	if (!ethtool_get (ifname, strings))
-		return -1;
+	_nl_msg_set_seq (sk, msg, out_seq);
 
-	for (i = 0; i < len; i++) {
-		if (!strcmp ((char *) &strings->data[i * ETH_GSTRING_LEN], string))
-			return i;
-	}
+	err = nl_send_auto (sk, msg);
+errout:
+	nlmsg_free(msg);
+
+	return err >= 0 ? 0 : err;
+}
+
+/******************************************************************/
 
-	return -1;
+#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
+
+	return FALSE;
+}
+
+static void
+_support_user_ipv6ll_detect (const struct rtnl_link *rtnl_link)
+{
+#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) {
+			_support_user_ipv6ll = 1;
+			nm_log_dbg (LOGD_PLATFORM, "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");
+		}
+	}
+#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
-_check_support_kernel_extended_ifa_flags_init (NMLinuxPlatformPrivate *priv, struct nl_msg *msg)
+_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
 {
-	struct nlmsghdr *msg_hdr = nlmsg_hdr (msg);
+	struct nlmsghdr *msg_hdr;
+
+	if (!_support_kernel_extended_ifa_flags_still_undecided ())
+		return;
 
-	g_return_if_fail (priv->support_kernel_extended_ifa_flags == 0);
-	g_return_if_fail (msg_hdr->nlmsg_type == RTM_NEWADDR);
+	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)
@@ -659,424 +571,507 @@ _check_support_kernel_extended_ifa_flags_init (NMLinuxPlatformPrivate *priv, str
 	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
 	 * and IFA_F_NOPREFIXROUTE (they were added together).
 	 **/
-	priv->support_kernel_extended_ifa_flags =
+	_support_kernel_extended_ifa_flags =
 	    nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), 8 /* IFA_FLAGS */)
 	    ? 1 : -1;
 }
 
 static gboolean
-check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+_support_kernel_extended_ifa_flags_get (void)
 {
-	NMLinuxPlatformPrivate *priv;
+	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;
+}
 
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+/******************************************************************
+ * Object type specific utilities
+ ******************************************************************/
 
-	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+static guint
+_nm_ip_config_source_to_rtprot (NMIPConfigSource source)
+{
+	switch (source) {
+	case NM_IP_CONFIG_SOURCE_UNKNOWN:
+		return RTPROT_UNSPEC;
+	case NM_IP_CONFIG_SOURCE_KERNEL:
+	case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
+		return RTPROT_KERNEL;
+	case NM_IP_CONFIG_SOURCE_DHCP:
+		return RTPROT_DHCP;
+	case NM_IP_CONFIG_SOURCE_RDISC:
+		return RTPROT_RA;
 
-	if (priv->support_kernel_extended_ifa_flags == 0) {
-		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for extended IFA_FLAGS. Assume no kernel support.");
-		priv->support_kernel_extended_ifa_flags = -1;
+	default:
+		return RTPROT_STATIC;
 	}
-
-	return priv->support_kernel_extended_ifa_flags > 0;
 }
 
-static gboolean
-check_support_user_ipv6ll (NMPlatform *platform)
+static NMIPConfigSource
+_nm_ip_config_source_from_rtprot (guint rtprot)
 {
-	NMLinuxPlatformPrivate *priv;
+	switch (rtprot) {
+	case RTPROT_UNSPEC:
+		return NM_IP_CONFIG_SOURCE_UNKNOWN;
+	case RTPROT_KERNEL:
+		return NM_IP_CONFIG_SOURCE_RTPROT_KERNEL;
+	case RTPROT_REDIRECT:
+		return NM_IP_CONFIG_SOURCE_KERNEL;
+	case RTPROT_RA:
+		return NM_IP_CONFIG_SOURCE_RDISC;
+	case RTPROT_DHCP:
+		return NM_IP_CONFIG_SOURCE_DHCP;
 
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	default:
+		return NM_IP_CONFIG_SOURCE_USER;
+	}
+}
 
-	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+/******************************************************************/
+
+typedef struct {
+	const NMLinkType nm_type;
+	const char *type_string;
+
+	/* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type
+	 * should only be specified if the device type can be created without
+	 * additional parameters, and if the device type can be determined from
+	 * the rtnl_type.  eg, tun/tap should not be specified since both
+	 * tun and tap devices use "tun", and InfiniBand should not be
+	 * specified because a PKey is required at creation. Drivers set this
+	 * value from their 'struct rtnl_link_ops' structure.
+	 */
+	const char *rtnl_type;
+
+	/* uevent DEVTYPE where applicable, from /sys/class/net/<ifname>/uevent;
+	 * drivers set this value from their SET_NETDEV_DEV() call and the
+	 * 'struct device_type' name member.
+	 */
+	const char *devtype;
+} LinkDesc;
+
+static const LinkDesc linktypes[] = {
+	{ NM_LINK_TYPE_NONE,          "none",        NULL,          NULL },
+	{ NM_LINK_TYPE_UNKNOWN,       "unknown",     NULL,          NULL },
+
+	{ NM_LINK_TYPE_ETHERNET,      "ethernet",    NULL,          NULL },
+	{ NM_LINK_TYPE_INFINIBAND,    "infiniband",  NULL,          NULL },
+	{ NM_LINK_TYPE_OLPC_MESH,     "olpc-mesh",   NULL,          NULL },
+	{ NM_LINK_TYPE_WIFI,          "wifi",        NULL,          "wlan" },
+	{ NM_LINK_TYPE_WWAN_ETHERNET, "wwan",        NULL,          "wwan" },
+	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
+
+	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
+	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
+	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
+	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         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_TAP,           "tap",         NULL,          NULL },
+	{ NM_LINK_TYPE_TUN,           "tun",         NULL,          NULL },
+	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
+	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
+	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
+	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
+
+	{ NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
+	{ NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
+	{ NM_LINK_TYPE_TEAM,          "team",        "team",        NULL },
+};
+
+static const char *
+nm_link_type_to_rtnl_type_string (NMLinkType type)
+{
+	int i;
 
-	if (priv->support_user_ipv6ll == 0) {
-		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for IFLA_INET6_ADDR_GEN_MODE. Assume no kernel support.");
-		priv->support_user_ipv6ll = -1;
+	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
+		if (type == linktypes[i].nm_type)
+			return linktypes[i].rtnl_type;
 	}
+	g_return_val_if_reached (NULL);
+}
+
+const char *
+nm_link_type_to_string (NMLinkType type)
+{
+	int i;
 
-	return priv->support_user_ipv6ll > 0;
+	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
+		if (type == linktypes[i].nm_type)
+			return linktypes[i].type_string;
+	}
+	g_return_val_if_reached (NULL);
 }
 
+/******************************************************************
+ * NMPlatform types and functions
+ ******************************************************************/
 
-/* Object type specific utilities */
+typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate;
 
-static const char *
-type_to_string (NMLinkType type)
+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;
+	GHashTable *delayed_deletion;
+
+	GHashTable *wifi_data;
+};
+
+static inline NMLinuxPlatformPrivate *
+NM_LINUX_PLATFORM_GET_PRIVATE (const void *self)
 {
-	/* Note that this only has to support virtual types */
-	switch (type) {
-	case NM_LINK_TYPE_DUMMY:
-		return "dummy";
-	case NM_LINK_TYPE_GRE:
-		return "gre";
-	case NM_LINK_TYPE_GRETAP:
-		return "gretap";
-	case NM_LINK_TYPE_IFB:
-		return "ifb";
-	case NM_LINK_TYPE_MACVLAN:
-		return "macvlan";
-	case NM_LINK_TYPE_MACVTAP:
-		return "macvtap";
-	case NM_LINK_TYPE_TAP:
-		return "tap";
-	case NM_LINK_TYPE_TUN:
-		return "tun";
-	case NM_LINK_TYPE_VETH:
-		return "veth";
-	case NM_LINK_TYPE_VLAN:
-		return "vlan";
-	case NM_LINK_TYPE_VXLAN:
-		return "vxlan";
-	case NM_LINK_TYPE_BRIDGE:
-		return "bridge";
-	case NM_LINK_TYPE_BOND:
-		return "bond";
-	case NM_LINK_TYPE_TEAM:
-		return "team";
-	default:
-		g_warning ("Wrong type: %d", type);
-		return NULL;
-	}
+	nm_assert (NM_IS_LINUX_PLATFORM (self));
+
+	return ((NMLinuxPlatform *) self)->priv;
 }
 
-#define return_type(t, name) \
-	G_STMT_START { \
-		if (out_name) \
-			*out_name = name; \
-		return t; \
-	} G_STMT_END
+G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
 
-static NMLinkType
-link_type_from_udev (NMPlatform *platform, int ifindex, const char *ifname, int arptype, const char **out_name)
+void
+nm_linux_platform_setup (void)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GUdevDevice *udev_device;
-	const char *prop, *sysfs_path;
+	g_object_new (NM_TYPE_LINUX_PLATFORM,
+	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
+	              NULL);
+}
 
-	g_assert (ifname);
+/******************************************************************/
 
-	udev_device = g_hash_table_lookup (priv->udev_devices, GINT_TO_POINTER (ifindex));
-	if (!udev_device)
-		return_type (NM_LINK_TYPE_UNKNOWN, "unknown");
+NMPObjectType
+_nlo_get_object_type (const struct nl_object *object)
+{
+	const char *type_str;
+
+	if (!object || !(type_str = nl_object_get_type (object)))
+		return NMP_OBJECT_TYPE_UNKNOWN;
 
-	if (   g_udev_device_get_property (udev_device, "ID_NM_OLPC_MESH")
-	    || g_udev_device_get_sysfs_attr (udev_device, "anycast_mask"))
-		return_type (NM_LINK_TYPE_OLPC_MESH, "olpc-mesh");
+	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;
+}
 
-	prop = g_udev_device_get_property (udev_device, "DEVTYPE");
-	sysfs_path = g_udev_device_get_sysfs_path (udev_device);
-	if (wifi_utils_is_wifi (ifname, sysfs_path, prop))
-		return_type (NM_LINK_TYPE_WIFI, "wifi");
-	else if (g_strcmp0 (prop, "wwan") == 0)
-		return_type (NM_LINK_TYPE_WWAN_ETHERNET, "wwan");
-	else if (g_strcmp0 (prop, "wimax") == 0)
-		return_type (NM_LINK_TYPE_WIMAX, "wimax");
+/******************************************************************/
 
-	if (arptype == ARPHRD_ETHER)
-		return_type (NM_LINK_TYPE_ETHERNET, "ethernet");
+static gboolean
+check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
 
-	return_type (NM_LINK_TYPE_UNKNOWN, "unknown");
+	return _support_kernel_extended_ifa_flags_get ();
 }
 
 static gboolean
-link_is_software (struct rtnl_link *rtnllink)
+check_support_user_ipv6ll (NMPlatform *platform)
 {
-	const char *type;
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
 
-	/* FIXME: replace somehow with NMLinkType or nm_platform_is_software(), but
-	 * solve the infinite callstack problems that getting the type of a TUN/TAP
-	 * device causes.
-	 */
+	return _support_user_ipv6ll_get ();
+}
 
-	if (   rtnl_link_get_arptype (rtnllink) == ARPHRD_INFINIBAND
-	    && strchr (rtnl_link_get_name (rtnllink), '.'))
-		return TRUE;
+static void
+process_events (NMPlatform *platform)
+{
+	delayed_action_handle_all (platform, TRUE);
+}
 
-	type = rtnl_link_get_type (rtnllink);
-	if (type == NULL)
-		return FALSE;
+/******************************************************************/
 
-	if (!strcmp (type, "dummy") ||
-	    !strcmp (type, "gre") ||
-	    !strcmp (type, "gretap") ||
-	    !strcmp (type, "macvlan") ||
-	    !strcmp (type, "macvtap") ||
-	    !strcmp (type, "tun") ||
-	    !strcmp (type, "veth") ||
-	    !strcmp (type, "vlan") ||
-	    !strcmp (type, "vxlan") ||
-	    !strcmp (type, "bridge") ||
-	    !strcmp (type, "bond") ||
-	    !strcmp (type, "team"))
-		return 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))
 
-	return FALSE;
-}
 
-static const char *
-ethtool_get_driver (const char *ifname)
-{
-	struct ethtool_drvinfo drvinfo = { 0 };
+/******************************************************************/
 
-	g_return_val_if_fail (ifname != NULL, NULL);
+#define DEVTYPE_PREFIX "DEVTYPE="
 
-	drvinfo.cmd = ETHTOOL_GDRVINFO;
-	if (!ethtool_get (ifname, &drvinfo))
-		return NULL;
+static char *
+read_devtype (const char *sysfs_path)
+{
+	gs_free char *uevent = g_strdup_printf ("%s/uevent", sysfs_path);
+	char *contents = NULL;
+	char *cont, *end;
 
-	if (!*drvinfo.driver)
+	if (!g_file_get_contents (uevent, &contents, NULL, NULL))
 		return NULL;
-
-	return g_intern_string (drvinfo.driver);
+	for (cont = contents; cont; cont = end) {
+		end = strpbrk (cont, "\r\n");
+		if (end)
+			*end++ = '\0';
+		if (strncmp (cont, DEVTYPE_PREFIX, STRLEN (DEVTYPE_PREFIX)) == 0) {
+			cont += STRLEN (DEVTYPE_PREFIX);
+			memmove (contents, cont, strlen (cont) + 1);
+			return contents;
+		}
+	}
+	g_free (contents);
+	return NULL;
 }
 
-static gboolean
-link_is_announceable (NMPlatform *platform, struct rtnl_link *rtnllink)
+static const NMPObject *
+_lookup_link_cached (NMPlatform *platform, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj;
 
-	/* Software devices are always visible outside the platform */
-	if (link_is_software (rtnllink))
-		return TRUE;
+	nm_assert (completed_from_cache && link_cached);
 
-	/* Hardware devices must be found by udev so rules get run and tags set */
-	if (g_hash_table_lookup (priv->udev_devices,
-	                         GINT_TO_POINTER (rtnl_link_get_ifindex (rtnllink))))
-		return TRUE;
+	if (!*completed_from_cache) {
+		obj = ifindex > 0 ? nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex) : NULL;
 
-	return FALSE;
+		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, const char **out_name)
+link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *completed_from_cache, const NMPObject **link_cached, const char **out_kind)
 {
-	const char *type;
+	const char *rtnl_type, *ifname;
+	int i, arptype;
 
-	if (!rtnllink)
-		return_type (NM_LINK_TYPE_NONE, NULL);
+	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) {
+		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);
+		}
+	}
+	if (out_kind)
+		*out_kind = rtnl_type;
+	if (rtnl_type) {
+		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
+			if (g_strcmp0 (rtnl_type, linktypes[i].rtnl_type) == 0)
+				return linktypes[i].nm_type;
+		}
 
-	type = rtnl_link_get_type (rtnllink);
+		if (!strcmp (rtnl_type, "tun")) {
+			NMPlatformTunProperties props;
+			guint flags;
 
-	if (!type) {
-		int arptype = rtnl_link_get_arptype (rtnllink);
-		const char *driver;
-		const char *ifname;
+			if (tun_get_properties_ifname (platform, rtnl_link_get_name (rtnllink), &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)
+				return NM_LINK_TYPE_TUN;
+			return NM_LINK_TYPE_TAP;
+		}
+	}
 
-		if (arptype == ARPHRD_LOOPBACK)
-			return_type (NM_LINK_TYPE_LOOPBACK, "loopback");
-		else if (arptype == ARPHRD_INFINIBAND)
-			return_type (NM_LINK_TYPE_INFINIBAND, "infiniband");
+	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;
 
-		ifname = rtnl_link_get_name (rtnllink);
-		if (!ifname)
-			return_type (NM_LINK_TYPE_UNKNOWN, type);
+	ifname = rtnl_link_get_name (rtnllink);
+	if (ifname) {
+		gs_free char *driver = NULL;
+		gs_free char *sysfs_path = NULL;
+		gs_free char *anycast_mask = NULL;
+		gs_free char *devtype = NULL;
 
-		driver = ethtool_get_driver (ifname);
 		if (arptype == 256) {
 			/* Some s390 CTC-type devices report 256 for the encapsulation type
 			 * for some reason, but we need to call them Ethernet.
 			 */
 			if (!g_strcmp0 (driver, "ctcm"))
-				return_type (NM_LINK_TYPE_ETHERNET, "ethernet");
+				return NM_LINK_TYPE_ETHERNET;
 		}
 
-		if (!g_strcmp0 (driver, "openvswitch"))
-			return_type (NM_LINK_TYPE_OPENVSWITCH, "openvswitch");
-
-		return link_type_from_udev (platform,
-		                            rtnl_link_get_ifindex (rtnllink),
-		                            ifname,
-		                            arptype,
-		                            out_name);
-	} else if (!strcmp (type, "dummy"))
-		return_type (NM_LINK_TYPE_DUMMY, "dummy");
-	else if (!strcmp (type, "gre"))
-		return_type (NM_LINK_TYPE_GRE, "gre");
-	else if (!strcmp (type, "gretap"))
-		return_type (NM_LINK_TYPE_GRETAP, "gretap");
-	else if (!strcmp (type, "ifb"))
-		return_type (NM_LINK_TYPE_IFB, "ifb");
-	else if (!strcmp (type, "macvlan"))
-		return_type (NM_LINK_TYPE_MACVLAN, "macvlan");
-	else if (!strcmp (type, "macvtap"))
-		return_type (NM_LINK_TYPE_MACVTAP, "macvtap");
-	else if (!strcmp (type, "tun")) {
-		NMPlatformTunProperties props;
-		guint flags;
-
-		if (nm_platform_tun_get_properties (rtnl_link_get_ifindex (rtnllink), &props)) {
-			if (!g_strcmp0 (props.mode, "tap"))
-				return_type (NM_LINK_TYPE_TAP, "tap");
-			if (!g_strcmp0 (props.mode, "tun"))
-				return_type (NM_LINK_TYPE_TUN, "tun");
+		/* Fallback OVS detection for kernel <= 3.16 */
+		if (nmp_utils_ethtool_get_driver_info (ifname, &driver, NULL, NULL)) {
+			if (!g_strcmp0 (driver, "openvswitch"))
+				return NM_LINK_TYPE_OPENVSWITCH;
 		}
-		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)
-			return_type (NM_LINK_TYPE_TUN, "tun");
-		return_type (NM_LINK_TYPE_TAP, "tap");
-	} else if (!strcmp (type, "veth"))
-		return_type (NM_LINK_TYPE_VETH, "veth");
-	else if (!strcmp (type, "vlan"))
-		return_type (NM_LINK_TYPE_VLAN, "vlan");
-	else if (!strcmp (type, "vxlan"))
-		return_type (NM_LINK_TYPE_VXLAN, "vxlan");
-	else if (!strcmp (type, "bridge"))
-		return_type (NM_LINK_TYPE_BRIDGE, "bridge");
-	else if (!strcmp (type, "bond"))
-		return_type (NM_LINK_TYPE_BOND, "bond");
-	else if (!strcmp (type, "team"))
-		return_type (NM_LINK_TYPE_TEAM, "team");
-
-	return_type (NM_LINK_TYPE_UNKNOWN, type);
-}
 
-static const char *
-udev_get_driver (NMPlatform *platform, GUdevDevice *device, int ifindex)
-{
-	GUdevDevice *parent = NULL, *grandparent = NULL;
-	const char *driver, *subsys;
-
-	driver = g_udev_device_get_driver (device);
-	if (driver)
-		return driver;
-
-	/* Try the parent */
-	parent = g_udev_device_get_parent (device);
-	if (parent) {
-		driver = g_udev_device_get_driver (parent);
-		if (!driver) {
-			/* Try the grandparent if it's an ibmebus device or if the
-			 * subsys is NULL which usually indicates some sort of
-			 * platform device like a 'gadget' net interface.
-			 */
-			subsys = g_udev_device_get_subsystem (parent);
-			if (   (g_strcmp0 (subsys, "ibmebus") == 0)
-			    || (subsys == NULL)) {
-				grandparent = g_udev_device_get_parent (parent);
-				if (grandparent) {
-					driver = g_udev_device_get_driver (grandparent);
+		sysfs_path = g_strdup_printf ("/sys/class/net/%s", ifname);
+		anycast_mask = g_strdup_printf ("%s/anycast_mask", sysfs_path);
+		if (g_file_test (anycast_mask, G_FILE_TEST_EXISTS))
+			return NM_LINK_TYPE_OLPC_MESH;
+
+		devtype = 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) {
+					/* Both BNEP and 6lowpan use DEVTYPE=bluetooth, so we must
+					 * use arptype to distinguish between them.
+					 */
+					if (arptype != ARPHRD_ETHER)
+						continue;
 				}
+				return linktypes[i].nm_type;
 			}
 		}
-	}
 
-	/* Intern the string so we don't have to worry about memory
-	 * management in NMPlatformLink.
-	 */
-	if (driver)
-		driver = g_intern_string (driver);
+		/* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */
+		if (wifi_utils_is_wifi (ifname, sysfs_path))
+			return NM_LINK_TYPE_WIFI;
 
-	g_clear_object (&parent);
-	g_clear_object (&grandparent);
+		/* Standard wired ethernet interfaces don't report an rtnl_link_type, so
+		 * only allow fallback to Ethernet if no type is given.  This should
+		 * prevent future virtual network drivers from being treated as Ethernet
+		 * when they should be Generic instead.
+		 */
+		if (arptype == ARPHRD_ETHER && !rtnl_type && !devtype)
+			return NM_LINK_TYPE_ETHERNET;
+	}
 
-	return driver;
+	return NM_LINK_TYPE_UNKNOWN;
 }
 
-static gboolean
-init_link (NMPlatform *platform, NMPlatformLink *info, struct rtnl_link *rtnllink)
+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)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GUdevDevice *udev_device;
+	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;
 
-	g_return_val_if_fail (rtnllink, FALSE);
+	nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
 
-	name = rtnl_link_get_name (rtnllink);
-	memset (info, 0, sizeof (*info));
+	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));
 
-	info->ifindex = rtnl_link_get_ifindex (rtnllink);
-	if (name)
-		g_strlcpy (info->name, name, sizeof (info->name));
-	else
-		info->name[0] = '\0';
-	info->type = link_extract_type (platform, rtnllink, &info->type_name);
-	info->up = !!(rtnl_link_get_flags (rtnllink) & IFF_UP);
-	info->connected = !!(rtnl_link_get_flags (rtnllink) & IFF_LOWER_UP);
-	info->arp = !(rtnl_link_get_flags (rtnllink) & IFF_NOARP);
-	info->master = rtnl_link_get_master (rtnllink);
-	info->parent = rtnl_link_get_link (rtnllink);
-	info->mtu = rtnl_link_get_mtu (rtnllink);
-
-	udev_device = g_hash_table_lookup (priv->udev_devices, GINT_TO_POINTER (info->ifindex));
-	if (udev_device) {
-		info->driver = udev_get_driver (platform, udev_device, info->ifindex);
-		if (!info->driver)
-			info->driver = g_intern_string (rtnl_link_get_type (rtnllink));
-		if (!info->driver)
-			info->driver = ethtool_get_driver (info->name);
-		if (!info->driver)
-			info->driver = "unknown";
-		info->udi = g_udev_device_get_sysfs_path (udev_device);
-	}
+	obj->ifindex = rtnl_link_get_ifindex (nlo);
 
-	return TRUE;
-}
+	if (id_only)
+		return TRUE;
 
-/* Hack: Empty bridges and bonds have IFF_LOWER_UP flag and therefore they break
- * the carrier detection. This hack makes nm-platform think they don't have the
- * IFF_LOWER_UP flag. This seems to also apply to bonds (specifically) with all
- * slaves down.
- *
- * Note: This is still a bit racy but when NetworkManager asks for enslaving a slave,
- * nm-platform will do that synchronously and will immediately ask for both master
- * and slave information after the enslaving request. After the synchronous call, the
- * master carrier is already updated with the slave carrier in mind.
- *
- * https://bugzilla.redhat.com/show_bug.cgi?id=910348
- */
-static void
-hack_empty_master_iff_lower_up (NMPlatform *platform, struct nl_object *object)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct rtnl_link *rtnllink;
-	int ifindex;
-	struct nl_object *slave;
-	const char *type;
+	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);
+	obj->parent = rtnl_link_get_link (nlo);
+	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 (!object)
-		return;
-	if (strcmp (nl_object_get_type (object), "route/link"))
-		return;
+	if ((nladdr = rtnl_link_get_addr (nlo))) {
+		unsigned int l = 0;
 
-	rtnllink = (struct rtnl_link *) object;
+		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;
+		}
+	}
 
-	ifindex = rtnl_link_get_ifindex (rtnllink);
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+	if (_support_user_ipv6ll_get ()) {
+		guint8 mode = 0;
 
-	type = rtnl_link_get_type (rtnllink);
-	if (!type || (strcmp (type, "bridge") != 0 && strcmp (type, "bond") != 0))
-		return;
+		if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0)
+			obj->inet6_addr_gen_mode_inv = _nm_platform_uint8_inv (mode);
+	}
+#endif
 
-	for (slave = nl_cache_get_first (priv->link_cache); slave; slave = nl_cache_get_next (slave)) {
-		struct rtnl_link *rtnlslave = (struct rtnl_link *) slave;
-		if (rtnl_link_get_master (rtnlslave) == ifindex
-				&& rtnl_link_get_flags (rtnlslave) & IFF_LOWER_UP)
-			return;
+#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;
+		}
+		nl_addr_put (nladdr);
 	}
+#endif
 
-	rtnl_link_unset_flags (rtnllink, IFF_LOWER_UP);
-}
+	obj_priv->netlink.is_in_netlink = TRUE;
 
-static guint32
-_get_remaining_time (guint32 start_timestamp, guint32 end_timestamp)
-{
-	/* Return the remaining time between @start_timestamp until @end_timestamp.
-	 *
-	 * If @end_timestamp is NM_PLATFORM_LIFETIME_PERMANENT, it returns
-	 * NM_PLATFORM_LIFETIME_PERMANENT. If @start_timestamp already passed
-	 * @end_timestamp it returns 0. Beware, NMPlatformIPAddress treats a @lifetime
-	 * of 0 as permanent.
-	 */
-	if (end_timestamp == NM_PLATFORM_LIFETIME_PERMANENT)
-		return NM_PLATFORM_LIFETIME_PERMANENT;
-	if (start_timestamp >= end_timestamp)
-		return 0;
-	return end_timestamp - start_timestamp;
+	return TRUE;
 }
 
 /* _timestamp_nl_to_ms:
@@ -1112,15 +1107,18 @@ _timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
 }
 
 static guint32
-_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr)
+_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out_now_nm)
 {
 	guint32 last_update_time = rtnl_addr_get_last_update_time ((struct rtnl_addr *) rtnladdr);
 	struct timespec tp;
 	gint64 now_nl, now_nm, result;
 
 	/* timestamp is unset. Default to 1. */
-	if (!last_update_time)
+	if (!last_update_time) {
+		if (out_now_nm)
+			*out_now_nm = 0;
 		return 1;
+	}
 
 	/* do all the calculations in milliseconds scale */
 
@@ -1131,6 +1129,9 @@ _rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr)
 
 	result = now_nm - (now_nl - _timestamp_nl_to_ms (last_update_time, now_nl));
 
+	if (out_now_nm)
+		*out_now_nm = now_nm / 1000;
+
 	/* converting the last_update_time into nm_utils_get_monotonic_timestamp_ms() scale is
 	 * a good guess but fails in the following situations:
 	 *
@@ -1148,686 +1149,952 @@ _rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr)
 	return result / 1000;
 }
 
-static void
-_init_ip_address_lifetime (NMPlatformIPAddress *address, const struct rtnl_addr *rtnladdr)
+static guint32
+_extend_lifetime (guint32 lifetime, guint32 seconds)
 {
-	guint32 a_valid = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
-	guint32 a_preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
-
-	/* the meaning of the valid and preferred lifetimes is different from the
-	 * original meaning. See _rtnl_addr_hack_lifetimes_rel_to_abs().
-	 * Beware: this function expects hacked rtnl_addr objects.
-	 */
+	guint64 v;
 
-	if (a_valid == NM_PLATFORM_LIFETIME_PERMANENT &&
-	    a_preferred == NM_PLATFORM_LIFETIME_PERMANENT) {
-		address->timestamp = 0;
-		address->lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		address->preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-		return;
-	}
+	if (   lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    || seconds == 0)
+		return lifetime;
 
-	/* The valies are hacked and absolute expiry times. They must
-	 * be positive and preferred<=valid. */
-	g_assert (a_preferred <= a_valid &&
-	          a_valid > 0 &&
-	          a_preferred > 0);
-
-	if (a_valid <= 1) {
-		/* Since we want to have positive @timestamp and @valid != 0,
-		 * we must handle this case special. */
-		address->timestamp = 1;
-		address->lifetime = 1; /* Extend the lifetime by one second */
-		address->preferred = 0; /* no longer preferred. */
-		return;
-	}
+	v = (guint64) lifetime + (guint64) seconds;
+	return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+}
 
-	/* _rtnl_addr_last_update_time_to_nm() might be wrong, so don't rely on
-	 * timestamp to have any meaning beyond anchoring the relative durations
-	 * @lifetime and @preferred.
-	 */
-	address->timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr);
+/* 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)
+{
+	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);
+
+	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 (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;
 
-	/* We would expect @timestamp to be less then @a_valid. Just to be sure,
-	 * fix it up. */
-	address->timestamp = MIN (address->timestamp, a_valid - 1);
-	address->lifetime = _get_remaining_time (address->timestamp, a_valid);
-	address->preferred = _get_remaining_time (address->timestamp, a_preferred);
+			lifetime = _extend_lifetime (lifetime, diff);
+			preferred = _extend_lifetime (preferred, diff);
+		} else
+			nm_assert (timestamp == now);
+	}
+	*out_timestamp = timestamp;
+	*out_lifetime = lifetime;
+	*out_preferred = preferred;
 }
 
-static gboolean
-init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
+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)
 {
-	struct nl_addr *nladdr = rtnl_addr_get_local (rtnladdr);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (rtnladdr);
+	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;
 
-	g_return_val_if_fail (nladdr, FALSE);
+	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
+		g_return_val_if_reached (FALSE);
 
-	memset (address, 0, sizeof (*address));
+	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));
 
-	address->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	address->ifindex = rtnl_addr_get_ifindex (rtnladdr);
-	address->plen = rtnl_addr_get_prefixlen (rtnladdr);
-	_init_ip_address_lifetime ((NMPlatformIPAddress *) address, rtnladdr);
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) {
-		g_return_val_if_reached (FALSE);
-		return FALSE;
-	}
-	memcpy (&address->address, nl_addr_get_binary_addr (nladdr), sizeof (address->address));
+	if (id_only)
+		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 (address->peer_address)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&address->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (address->peer_address));
+		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));
 	}
-	label = rtnl_addr_get_label (rtnladdr);
+	label = rtnl_addr_get_label (nlo);
 	/* Check for ':'; we're only interested in labels used as interface aliases */
 	if (label && strchr (label, ':'))
-		g_strlcpy (address->label, label, sizeof (address->label));
+		g_strlcpy (obj->label, label, sizeof (obj->label));
 
 	return TRUE;
 }
 
-static gboolean
-init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
+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)
 {
-	struct nl_addr *nladdr = rtnl_addr_get_local (rtnladdr);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (rtnladdr);
+	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);
 
-	memset (address, 0, sizeof (*address));
-
-	address->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	address->ifindex = rtnl_addr_get_ifindex (rtnladdr);
-	address->plen = rtnl_addr_get_prefixlen (rtnladdr);
-	_init_ip_address_lifetime ((NMPlatformIPAddress *) address, rtnladdr);
-	address->flags = rtnl_addr_get_flags (rtnladdr);
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) {
+	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
 		g_return_val_if_reached (FALSE);
-		return FALSE;
-	}
-	memcpy (&address->address, nl_addr_get_binary_addr (nladdr), sizeof (address->address));
-	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (address->peer_address)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&address->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (address->peer_address));
-	}
 
-	return TRUE;
-}
+	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));
 
-static guint
-source_to_rtprot (NMIPConfigSource source)
-{
-	switch (source) {
-	case NM_IP_CONFIG_SOURCE_UNKNOWN:
-		return RTPROT_UNSPEC;
-	case NM_IP_CONFIG_SOURCE_KERNEL:
-		return RTPROT_KERNEL;
-	case NM_IP_CONFIG_SOURCE_DHCP:
-		return RTPROT_DHCP;
-	case NM_IP_CONFIG_SOURCE_RDISC:
-		return RTPROT_RA;
+	if (id_only)
+		return TRUE;
 
-	default:
-		return RTPROT_STATIC;
-	}
-}
+	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);
 
-static NMIPConfigSource
-rtprot_to_source (guint rtprot)
-{
-	switch (rtprot) {
-	case RTPROT_UNSPEC:
-		return NM_IP_CONFIG_SOURCE_UNKNOWN;
-	case RTPROT_REDIRECT:
-	case RTPROT_KERNEL:
-		return NM_IP_CONFIG_SOURCE_KERNEL;
-	case RTPROT_RA:
-		return NM_IP_CONFIG_SOURCE_RDISC;
-	case RTPROT_DHCP:
-		return NM_IP_CONFIG_SOURCE_DHCP;
-
-	default:
-		return NM_IP_CONFIG_SOURCE_USER;
+	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));
 	}
-}
 
-static gboolean
-_rtnl_route_is_default (const struct rtnl_route *rtnlroute)
-{
-	struct nl_addr *dst;
-
-	return    rtnlroute
-	       && (dst = rtnl_route_get_dst ((struct rtnl_route *) rtnlroute))
-	       && nl_addr_get_prefixlen (dst) == 0;
+	return TRUE;
 }
 
-static gboolean
-init_ip4_route (NMPlatformIP4Route *route, struct rtnl_route *rtnlroute)
+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)
 {
+	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;
 
-	memset (route, 0, sizeof (*route));
-
-	/* Multi-hop routes not supported. */
-	if (rtnl_route_get_nnexthops (rtnlroute) != 1)
+	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;
 
-	nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
-	dst = rtnl_route_get_dst (rtnlroute);
-	gw = rtnl_route_nh_get_gateway (nexthop);
+	nexthop = rtnl_route_nexthop_n (nlo, 0);
+	if (!nexthop)
+		g_return_val_if_reached (FALSE);
+
+	dst = rtnl_route_get_dst (nlo);
+	if (!dst)
+		g_return_val_if_reached (FALSE);
 
-	route->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	route->plen = nl_addr_get_prefixlen (dst);
-	/* Workaround on previous workaround for libnl default route prefixlen bug. */
 	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (route->network)) {
+		if (nl_addr_get_len (dst) != sizeof (obj->network))
 			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->network, nl_addr_get_binary_addr (dst), sizeof (route->network));
+		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
 	}
+	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 (route->network)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->gateway, nl_addr_get_binary_addr (gw), sizeof (route->gateway));
+		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));
+	}
+	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
+	if (rtnl_route_get_flags (nlo) & 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
+		 * one from the cache.
+		 *
+		 * This happens, because this route is not nmp_object_is_alive().
+		 * */
+		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+	} else
+		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
+
+	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));
 	}
-	route->metric = rtnl_route_get_priority (rtnlroute);
-	rtnl_route_get_metric (rtnlroute, RTAX_ADVMSS, &route->mss);
-	route->source = rtprot_to_source (rtnl_route_get_protocol (rtnlroute));
 
 	return TRUE;
 }
 
-static gboolean
-init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute)
+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)
 {
+	NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj;
+	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
 	struct nl_addr *dst, *gw;
 	struct rtnl_nexthop *nexthop;
 
-	memset (route, 0, sizeof (*route));
-
-	/* Multi-hop routes not supported. */
-	if (rtnl_route_get_nnexthops (rtnlroute) != 1)
+	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;
 
-	nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
-	dst = rtnl_route_get_dst (rtnlroute);
-	gw = rtnl_route_nh_get_gateway (nexthop);
+	nexthop = rtnl_route_nexthop_n (nlo, 0);
+	if (!nexthop)
+		g_return_val_if_reached (FALSE);
+
+	dst = rtnl_route_get_dst (nlo);
+	if (!dst)
+		g_return_val_if_reached (FALSE);
 
-	route->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	route->plen = nl_addr_get_prefixlen (dst);
-	/* Workaround on previous workaround for libnl default route prefixlen bug. */
 	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (route->network)) {
+		if (nl_addr_get_len (dst) != sizeof (obj->network))
 			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->network, nl_addr_get_binary_addr (dst), sizeof (route->network));
+		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
 	}
+	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;
+
+	gw = rtnl_route_nh_get_gateway (nexthop);
 	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (route->network)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->gateway, nl_addr_get_binary_addr (gw), sizeof (route->gateway));
+		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));
 	}
-	route->metric = rtnl_route_get_priority (rtnlroute);
-	rtnl_route_get_metric (rtnlroute, RTAX_ADVMSS, &route->mss);
-	route->source = rtprot_to_source (rtnl_route_get_protocol (rtnlroute));
+	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;
 }
 
-static char to_string_buffer[255];
-
-#define SET_AND_RETURN_STRING_BUFFER(...) \
-	G_STMT_START { \
-		g_snprintf (to_string_buffer, sizeof (to_string_buffer), ## __VA_ARGS__); \
-		return to_string_buffer; \
-	} G_STMT_END
+/******************************************************************/
 
-static const char *
-to_string_link (NMPlatform *platform, struct rtnl_link *obj)
+static void
+do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible, NMPlatformReason reason)
 {
-	NMPlatformLink pl_obj;
+	gboolean is_visible;
+	NMPObject obj_clone;
+	const NMPClass *klass;
 
-	if (init_link (platform, &pl_obj, obj))
-		return nm_platform_link_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid link %p)", obj);
-}
+	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED));
 
-static const char *
-to_string_ip4_address (struct rtnl_addr *obj)
-{
-	NMPlatformIP4Address pl_obj;
+	nm_assert (obj || cache_op == NMP_CACHE_OPS_UNCHANGED);
+	nm_assert (!obj || cache_op == NMP_CACHE_OPS_REMOVED || obj == nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
+	nm_assert (!obj || cache_op != NMP_CACHE_OPS_REMOVED || obj != nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
 
-	if (init_ip4_address (&pl_obj, obj))
-		return nm_platform_ip4_address_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip4 address %p)", obj);
+	switch (cache_op) {
+	case NMP_CACHE_OPS_ADDED:
+		if (!nmp_object_is_visible (obj))
+			return;
+		break;
+	case NMP_CACHE_OPS_UPDATED:
+		is_visible = nmp_object_is_visible (obj);
+		if (!was_visible && is_visible)
+			cache_op = NMP_CACHE_OPS_ADDED;
+		else if (was_visible && !is_visible) {
+			/* This is a bit ugly. The object was visible and changed in a way that it became invisible.
+			 * We raise a removed signal, but contrary to a real 'remove', @obj is already changed to be
+			 * different from what it was when the user saw it the last time.
+			 *
+			 * The more correct solution would be to have cache_pre_hook() create a clone of the original
+			 * value before it was changed to become invisible.
+			 *
+			 * But, don't bother. Probably nobody depends on the original values and only cares about the
+			 * id properties (which are still correct).
+			 */
+			cache_op = NMP_CACHE_OPS_REMOVED;
+		} else if (!is_visible)
+			return;
+		break;
+	case NMP_CACHE_OPS_REMOVED:
+		if (!was_visible)
+			return;
+		break;
+	default:
+		g_assert (cache_op == NMP_CACHE_OPS_UNCHANGED);
+		return;
+	}
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	_LOGT ("emit signal %s %s: %s (%ld)",
+	       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);
+
+	/* 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);
 }
 
-static const char *
-to_string_ip6_address (struct rtnl_addr *obj)
+/******************************************************************/
+
+static DelayedActionType
+delayed_action_refresh_from_object_type (NMPObjectType obj_type)
 {
-	NMPlatformIP6Address pl_obj;
+	switch (obj_type) {
+	case NMP_OBJECT_TYPE_LINK:          return DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS;
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:   return DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES;
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:   return DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:     return DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:     return DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES;
+	default: g_return_val_if_reached (DELAYED_ACTION_TYPE_NONE);
+	}
+}
 
-	if (init_ip6_address (&pl_obj, obj))
-		return nm_platform_ip6_address_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip6 address %p)", obj);
+static NMPObjectType
+delayed_action_refresh_to_object_type (DelayedActionType action_type)
+{
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS:             return NMP_OBJECT_TYPE_LINK;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES:     return NMP_OBJECT_TYPE_IP4_ADDRESS;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES:     return NMP_OBJECT_TYPE_IP6_ADDRESS;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES:        return NMP_OBJECT_TYPE_IP4_ROUTE;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES:        return NMP_OBJECT_TYPE_IP6_ROUTE;
+	default: g_return_val_if_reached (NMP_OBJECT_TYPE_UNKNOWN);
+	}
 }
 
 static const char *
-to_string_ip4_route (struct rtnl_route *obj)
+delayed_action_to_string (DelayedActionType action_type)
+{
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS              : return "refresh-all-links";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES      : return "refresh-all-ip4-addresses";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES      : return "refresh-all-ip6-addresses";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES         : return "refresh-all-ip4-routes";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES         : return "refresh-all-ip6-routes";
+	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";
+	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 void
+delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
 {
-	NMPlatformIP4Route pl_obj;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	auto_nmp_obj NMPObject *obj_cache = NULL;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-	if (init_ip4_route (&pl_obj, obj))
-		return nm_platform_ip4_route_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip4 route %p)", obj);
+	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);
 }
 
-static const char *
-to_string_ip6_route (struct rtnl_route *obj)
+static void
+delayed_action_handle_REFRESH_LINK (NMPlatform *platform, int ifindex)
 {
-	NMPlatformIP6Route pl_obj;
-
-	if (init_ip6_route (&pl_obj, obj))
-		return nm_platform_ip6_route_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip6 route %p)", obj);
+	do_request_link (platform, ifindex, NULL, FALSE);
 }
 
-static const char *
-to_string_object_with_type (NMPlatform *platform, struct nl_object *obj, ObjectType type)
+static void
+delayed_action_handle_REFRESH_ALL (NMPlatform *platform, DelayedActionType flags)
 {
-	switch (type) {
-	case OBJECT_TYPE_LINK:
-		return to_string_link (platform, (struct rtnl_link *) obj);
-	case OBJECT_TYPE_IP4_ADDRESS:
-		return to_string_ip4_address ((struct rtnl_addr *) obj);
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return to_string_ip6_address ((struct rtnl_addr *) obj);
-	case OBJECT_TYPE_IP4_ROUTE:
-		return to_string_ip4_route ((struct rtnl_route *) obj);
-	case OBJECT_TYPE_IP6_ROUTE:
-		return to_string_ip6_route ((struct rtnl_route *) obj);
-	default:
-		SET_AND_RETURN_STRING_BUFFER ("(unknown netlink object %p)", obj);
-	}
+	do_request_all (platform, flags, FALSE);
 }
 
-static const char *
-to_string_object (NMPlatform *platform, struct nl_object *obj)
+static void
+delayed_action_handle_READ_NETLINK (NMPlatform *platform)
 {
-	return to_string_object_with_type (platform, obj, object_type_from_nl_object (obj));
+	event_handler_read_netlink_all (platform, TRUE);
 }
 
-#undef SET_AND_RETURN_STRING_BUFFER
+static gboolean
+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);
+		return FALSE;
+	}
 
-/* Object and cache manipulation */
+	/* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions.
+	 * This type of action is entirely cache-internal and is here to resolve a
+	 * cache inconsistency. It should be fixed right away. */
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) {
+		nm_assert (priv->delayed_action.list_master_connected->len > 0);
 
-static const char *signal_by_type_and_status[__OBJECT_TYPE_LAST] = {
-	[OBJECT_TYPE_LINK]        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
-	[OBJECT_TYPE_IP4_ADDRESS] = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
-	[OBJECT_TYPE_IP6_ADDRESS] = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
-	[OBJECT_TYPE_IP4_ROUTE]   = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
-	[OBJECT_TYPE_IP6_ROUTE]   = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
-};
+		user_data = priv->delayed_action.list_master_connected->pdata[0];
+		g_ptr_array_remove_index_fast (priv->delayed_action.list_master_connected, 0);
+		if (priv->delayed_action.list_master_connected->len == 0)
+			priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_MASTER_CONNECTED;
+		nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_master_connected->pdata, priv->delayed_action.list_master_connected->len, user_data) < 0);
 
-static struct nl_cache *
-choose_cache_by_type (NMPlatform *platform, ObjectType object_type)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+		_LOGT_delayed_action (DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle");
+		delayed_action_handle_MASTER_CONNECTED (platform, GPOINTER_TO_INT (user_data));
+		return TRUE;
+	}
+	nm_assert (priv->delayed_action.list_master_connected->len == 0);
+
+	/* Next we prefer read-netlink, because the buffer size is limited and we want to process events
+	 * from netlink early. */
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) {
+		_LOGT_delayed_action (DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle");
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK;
+		delayed_action_handle_READ_NETLINK (platform);
+		return TRUE;
+	}
 
-	switch (object_type) {
-	case OBJECT_TYPE_LINK:
-		return priv->link_cache;
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return priv->address_cache;
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		return priv->route_cache;
-	default:
-		g_return_val_if_reached (NULL);
-		return NULL;
+	if (NM_FLAGS_ANY (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_ALL)) {
+		DelayedActionType flags, iflags;
+
+		flags = priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_ALL;
+
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
+
+		if (_LOGT_ENABLED ()) {
+			for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+				if (NM_FLAGS_HAS (flags, iflags))
+					_LOGT_delayed_action (iflags, NULL, "handle");
+			}
+		}
+
+		delayed_action_handle_REFRESH_ALL (platform, flags);
+		return TRUE;
 	}
+
+	nm_assert (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_master_connected->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");
+
+	delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
+
+	return TRUE;
 }
 
-static struct nl_cache *
-choose_cache (NMPlatform *platform, struct nl_object *object)
+static gboolean
+delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink)
 {
-	return choose_cache_by_type (platform, object_type_from_nl_object (object));
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	gboolean any = FALSE;
+
+	nm_clear_g_source (&priv->delayed_action.idle_id);
+	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
-object_has_ifindex (struct nl_object *object, int ifindex)
-{
-	switch (object_type_from_nl_object (object)) {
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return ifindex == rtnl_addr_get_ifindex ((struct rtnl_addr *) object);
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		{
-			struct rtnl_route *rtnlroute = (struct rtnl_route *) object;
-			struct rtnl_nexthop *nexthop;
+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;
+}
+
+static void
+delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	DelayedActionType iflags;
+
+	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));
+		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));
+		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
+		nm_assert (!user_data);
 
-			if (rtnl_route_get_nnexthops (rtnlroute) != 1)
-				return FALSE;
-			nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
+	priv->delayed_action.flags |= action_type;
 
-			return ifindex == rtnl_route_nh_get_ifindex (nexthop);
+	if (_LOGT_ENABLED ()) {
+		for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+			if (NM_FLAGS_HAS (action_type, iflags))
+				_LOGT_delayed_action (iflags, user_data, "schedule");
 		}
-	default:
-		g_assert_not_reached ();
 	}
+
+	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 gboolean refresh_object (NMPlatform *platform, struct nl_object *object, gboolean removed, NMPlatformReason reason);
+/******************************************************************/
 
 static void
-check_cache_items (NMPlatform *platform, struct nl_cache *cache, int ifindex)
+cache_prune_candidates_record_all (NMPlatform *platform, NMPObjectType obj_type)
 {
-	auto_nl_cache struct nl_cache *cloned_cache = nl_cache_clone (cache);
-	struct nl_object *object;
-	GPtrArray *objects_to_refresh = g_ptr_array_new_with_free_func ((GDestroyNotify) nl_object_put);
-	guint i;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	for (object = nl_cache_get_first (cloned_cache); object; object = nl_cache_get_next (object)) {
-		if (object_has_ifindex (object, ifindex)) {
-			nl_object_get (object);
-			g_ptr_array_add (objects_to_refresh, object);
-		}
-	}
+	priv->prune_candidates = nmp_cache_lookup_all_to_hash (priv->cache,
+	                                                       nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, obj_type, FALSE),
+	                                                       priv->prune_candidates);
+	_LOGT ("cache-prune: record %s (now %u candidates)", nmp_class_from_type (obj_type)->obj_type_name,
+	       priv->prune_candidates ? g_hash_table_size (priv->prune_candidates) : 0);
+}
+
+static void
+cache_prune_candidates_record_one (NMPlatform *platform, NMPObject *obj)
+{
+	NMLinuxPlatformPrivate *priv;
+
+	if (!obj)
+		return;
+
+	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	for (i = 0; i < objects_to_refresh->len; i++)
-		refresh_object (platform, objects_to_refresh->pdata[i], TRUE, NM_PLATFORM_REASON_CACHE_CHECK);
+	if (!priv->prune_candidates)
+		priv->prune_candidates = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
 
-	g_ptr_array_free (objects_to_refresh, TRUE);
+	if (_LOGT_ENABLED () && !g_hash_table_contains (priv->prune_candidates, obj))
+		_LOGT ("cache-prune: record-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+	g_hash_table_add (priv->prune_candidates, nmp_object_ref (obj));
 }
 
 static void
-announce_object (NMPlatform *platform, const struct nl_object *object, NMPlatformSignalChangeType change_type, NMPlatformReason reason)
+cache_prune_candidates_drop (NMPlatform *platform, const NMPObject *obj)
+{
+	NMLinuxPlatformPrivate *priv;
+
+	if (!obj)
+		return;
+
+	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	if (priv->prune_candidates) {
+		if (_LOGT_ENABLED () && g_hash_table_contains (priv->prune_candidates, obj))
+			_LOGT ("cache-prune: drop-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+		g_hash_table_remove (priv->prune_candidates, obj);
+	}
+}
+
+static void
+cache_prune_candidates_prune (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	ObjectType object_type = object_type_from_nl_object (object);
-	const char *sig = signal_by_type_and_status[object_type];
+	GHashTable *prune_candidates;
+	GHashTableIter iter;
+	const NMPObject *obj;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-	switch (object_type) {
-	case OBJECT_TYPE_LINK:
-		{
-			struct rtnl_link *rtnl_link = (struct rtnl_link *) object;
-			NMPlatformLink device;
+	if (!priv->prune_candidates)
+		return;
 
-#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 (priv->support_user_ipv6ll == 0) {
-				uint8_t mode;
+	prune_candidates = priv->prune_candidates;
+	priv->prune_candidates = NULL;
 
-				if (rtnl_link_inet6_get_addr_gen_mode (rtnl_link, &mode) == 0)
-					priv->support_user_ipv6ll = 1;
-			}
-#endif
+	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;
 
-			if (!init_link (platform, &device, rtnl_link))
-				return;
+		_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);
+	}
 
-			/* Skip hardware devices not yet discovered by udev. They will be
-			 * announced by udev_device_added(). This doesn't apply to removed
-			 * devices, as those come either from udev_device_removed(),
-			 * event_notification() or link_delete() which block the announcment
-			 * themselves when appropriate.
-			 */
-			switch (change_type) {
-			case NM_PLATFORM_SIGNAL_ADDED:
-			case NM_PLATFORM_SIGNAL_CHANGED:
-				if (!link_is_software (rtnl_link) && !device.driver)
-					return;
-				break;
-			default:
-				break;
-			}
+	g_hash_table_unref (prune_candidates);
+}
 
-			/* Link deletion or setting down is sometimes accompanied by address
-			 * and/or route deletion.
-			 *
-			 * More precisely, kernel removes routes when interface goes !IFF_UP and
-			 * removes both addresses and routes when interface is removed.
-			 */
-			switch (change_type) {
-			case NM_PLATFORM_SIGNAL_CHANGED:
-				if (!device.connected)
-					check_cache_items (platform, priv->route_cache, device.ifindex);
-				break;
-			case NM_PLATFORM_SIGNAL_REMOVED:
-				check_cache_items (platform, priv->address_cache, device.ifindex);
-				check_cache_items (platform, priv->route_cache, device.ifindex);
-				g_hash_table_remove (priv->wifi_data, GINT_TO_POINTER (device.ifindex));
-				break;
-			default:
-				break;
-			}
+static void
+cache_delayed_deletion_prune (NMPlatform *platform)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	GPtrArray *prune_list = NULL;
+	GHashTableIter iter;
+	guint i;
+	NMPObject *obj;
 
-			g_signal_emit_by_name (platform, sig, device.ifindex, &device, change_type, reason);
-		}
+	if (g_hash_table_size (priv->delayed_deletion) == 0)
 		return;
-	case OBJECT_TYPE_IP4_ADDRESS:
-		{
-			NMPlatformIP4Address address;
 
-			/* Address deletion is sometimes accompanied by route deletion. We need to
-			 * check all routes belonging to the same interface.
-			 */
-			switch (change_type) {
-			case NM_PLATFORM_SIGNAL_REMOVED:
-				check_cache_items (platform,
-				                   priv->route_cache,
-				                   rtnl_addr_get_ifindex ((struct rtnl_addr *) object));
-				break;
-			default:
-				break;
-			}
+	g_hash_table_iter_init (&iter, priv->delayed_deletion);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &obj)) {
+		if (obj) {
+			if (!prune_list)
+				prune_list = g_ptr_array_new_full (g_hash_table_size (priv->delayed_deletion), (GDestroyNotify) nmp_object_unref);
+			g_ptr_array_add (prune_list, nmp_object_ref (obj));
+		}
+	}
 
-			if (!_address_match ((struct rtnl_addr *) object, AF_INET, 0)) {
-				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP4 address %s", to_string_ip4_address ((struct rtnl_addr *) object));
-				return;
-			}
-			if (!init_ip4_address (&address, (struct rtnl_addr *) object))
-				return;
-			g_signal_emit_by_name (platform, sig, address.ifindex, &address, change_type, reason);
+	g_hash_table_remove_all (priv->delayed_deletion);
+
+	if (prune_list) {
+		for (i = 0; i < prune_list->len; i++) {
+			obj = prune_list->pdata[i];
+			_LOGT ("delayed-deletion: delete %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			cache_remove_netlink (platform, obj, NULL, NULL, NM_PLATFORM_REASON_EXTERNAL);
 		}
-		return;
-	case OBJECT_TYPE_IP6_ADDRESS:
-		{
-			NMPlatformIP6Address address;
+		g_ptr_array_unref (prune_list);
+	}
+}
 
-			if (!_address_match ((struct rtnl_addr *) object, AF_INET6, 0)) {
-				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP6 address %s", to_string_ip6_address ((struct rtnl_addr *) object));
-				return;
+static void
+cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+{
+	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)];
+
+	nm_assert (old || new);
+	nm_assert (NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_REMOVED, NMP_CACHE_OPS_UPDATED));
+	nm_assert (ops_type != NMP_CACHE_OPS_ADDED   || (old == NULL && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new)));
+	nm_assert (ops_type != NMP_CACHE_OPS_REMOVED || (new == NULL && NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old)));
+	nm_assert (ops_type != NMP_CACHE_OPS_UPDATED || (NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old) && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new)));
+	nm_assert (new == NULL || old == NULL || nmp_object_id_equal (new, old));
+
+	klass = old ? NMP_OBJECT_GET_CLASS (old) : NMP_OBJECT_GET_CLASS (new);
+
+	nm_assert (klass == (new ? NMP_OBJECT_GET_CLASS (new) : NMP_OBJECT_GET_CLASS (old)));
+
+	_LOGT ("update-cache-%s: %s: %s%s%s",
+	       klass->obj_type_name,
+	       (ops_type == NMP_CACHE_OPS_UPDATED
+	           ? "UPDATE"
+	           : (ops_type == NMP_CACHE_OPS_REMOVED
+	                 ? "REMOVE"
+	                 : (ops_type == NMP_CACHE_OPS_ADDED) ? "ADD" : "???")),
+	       (ops_type != NMP_CACHE_OPS_ADDED
+	           ? nmp_object_to_string (old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))
+	           : nmp_object_to_string (new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))),
+	       (ops_type == NMP_CACHE_OPS_UPDATED) ? " -> " : "",
+	       (ops_type == NMP_CACHE_OPS_UPDATED
+	           ? nmp_object_to_string (new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof (str_buf))
+	           : ""));
+
+	switch (klass->obj_type) {
+	case NMP_OBJECT_TYPE_LINK:
+		{
+			/* check whether changing a slave link can cause a master link (bridge or bond) to go up/down */
+			if (   old
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (priv->cache, old->link.master, new, old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (old->link.master));
+			if (   new
+			    && (!old || old->link.master != new->link.master)
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (priv->cache, new->link.master, new, old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (new->link.master));
+		}
+		{
+			/* check whether we are about to change a master link that needs toggling connected state. */
+			if (   new /* <-- nonsensical, make coverity happy */
+			    && nmp_cache_link_connected_needs_toggle (cache, new, new, old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (new->link.ifindex));
+		}
+		{
+			int ifindex = 0;
+
+			/* if we remove a link (from netlink), we must refresh the addresses and routes */
+			if (   ops_type == NMP_CACHE_OPS_REMOVED
+			    && old /* <-- nonsensical, make coverity happy */)
+				ifindex = old->link.ifindex;
+			else if (   ops_type == NMP_CACHE_OPS_UPDATED
+			         && old && new /* <-- nonsensical, make coverity happy */
+			         && !new->_link.netlink.is_in_netlink
+			         && new->_link.netlink.is_in_netlink != old->_link.netlink.is_in_netlink)
+				ifindex = new->link.ifindex;
+
+			if (ifindex > 0) {
+				delayed_action_schedule (platform,
+				                         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);
 			}
-			if (!init_ip6_address (&address, (struct rtnl_addr *) object))
-				return;
-			g_signal_emit_by_name (platform, sig, address.ifindex, &address, change_type, reason);
 		}
-		return;
-	case OBJECT_TYPE_IP4_ROUTE:
 		{
-			NMPlatformIP4Route route;
-
-			if (reason == _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)
-				return;
-
-			if (!_route_match ((struct rtnl_route *) object, AF_INET, 0, FALSE)) {
-				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP4 route %s", to_string_ip4_route ((struct rtnl_route *) object));
-				return;
+			/* if a link goes down, we must refresh routes */
+			if (   ops_type == NMP_CACHE_OPS_UPDATED
+			    && old && new /* <-- nonsensical, make coverity happy */
+			    && old->_link.netlink.is_in_netlink
+			    && NM_FLAGS_HAS (old->link.flags, IFF_LOWER_UP)
+			    && new->_link.netlink.is_in_netlink
+			    && !NM_FLAGS_HAS (new->link.flags, IFF_LOWER_UP)) {
+				delayed_action_schedule (platform,
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+				                         NULL);
 			}
-			if (init_ip4_route (&route, (struct rtnl_route *) object))
-				g_signal_emit_by_name (platform, sig, route.ifindex, &route, change_type, reason);
 		}
-		return;
-	case OBJECT_TYPE_IP6_ROUTE:
 		{
-			NMPlatformIP6Route route;
-
-			if (reason == _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)
-				return;
+			/* on enslave/release, we also refresh the master. */
+			int ifindex1 = 0, ifindex2 = 0;
+			gboolean changed_master, changed_connected;
+
+			changed_master =    (new && new->_link.netlink.is_in_netlink && new->link.master > 0 ? new->link.master : 0)
+			                 != (old && old->_link.netlink.is_in_netlink && old->link.master > 0 ? old->link.master : 0);
+			changed_connected =    (new && new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (new->link.flags, IFF_LOWER_UP) : 2)
+			                    != (old && old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (old->link.flags, IFF_LOWER_UP) : 2);
+
+			if (changed_master || changed_connected) {
+				ifindex1 = (old && old->_link.netlink.is_in_netlink && old->link.master > 0) ? old->link.master : 0;
+				ifindex2 = (new && new->_link.netlink.is_in_netlink && new->link.master > 0) ? new->link.master : 0;
+
+				if (ifindex1 > 0)
+					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex1));
+				if (ifindex2 > 0 && ifindex1 != ifindex2)
+					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex2));
+			}
 
-			if (!_route_match ((struct rtnl_route *) object, AF_INET6, 0, FALSE)) {
-				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP6 route %s", to_string_ip6_route ((struct rtnl_route *) object));
-				return;
+		}
+		break;
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		{
+			/* Address deletion is sometimes accompanied by route deletion. We need to
+			 * check all routes belonging to the same interface. */
+			if (ops_type == NMP_CACHE_OPS_REMOVED) {
+				delayed_action_schedule (platform,
+				                         (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS)
+				                             ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
+				                             : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+				                         NULL);
 			}
-			if (init_ip6_route (&route, (struct rtnl_route *) object))
-				g_signal_emit_by_name (platform, sig, route.ifindex, &route, change_type, reason);
 		}
-		return;
 	default:
-		g_return_if_reached ();
+		break;
 	}
 }
 
-static struct nl_object * build_rtnl_link (int ifindex, const char *name, NMLinkType type);
+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;
 
-static gboolean
-refresh_object (NMPlatform *platform, struct nl_object *object, gboolean removed, NMPlatformReason reason)
+	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)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct nl_object *cached_object = NULL;
-	auto_nl_object struct nl_object *kernel_object = NULL;
-	struct nl_cache *cache;
-	int nle;
+	NMPObject *obj_cache;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-	cache = choose_cache (platform, object);
-	cached_object = nm_nl_cache_search (cache, object);
-	kernel_object = get_kernel_object (priv->nlh, object);
+	/* This is basically a convenience method to call nmp_cache_update() and do_emit_signal()
+	 * at once. */
 
-	if (removed) {
-		if (kernel_object)
-			return TRUE;
+	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);
 
-		/* Only announce object if it was still in the cache. */
-		if (cached_object) {
-			nl_cache_remove (cached_object);
+	if (out_obj_cache)
+		*out_obj_cache = obj_cache;
+	else
+		nmp_object_unref (obj_cache);
+	if (out_was_visible)
+		*out_was_visible = was_visible;
 
-			announce_object (platform, cached_object, NM_PLATFORM_SIGNAL_REMOVED, reason);
-		}
-	} else {
-		ObjectType type;
+	return cache_op;
+}
 
-		if (!kernel_object)
-			return FALSE;
+/******************************************************************/
 
-		/* Unsupported object types should never have reached the caches */
-		type = object_type_from_nl_object (kernel_object);
-		g_assert (type != OBJECT_TYPE_UNKNOWN);
+static void
+_new_sequence_number (NMPlatform *platform, guint32 seq)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-		hack_empty_master_iff_lower_up (platform, kernel_object);
+	_LOGT ("_new_sequence_number(): new sequence number %u", seq);
 
-		if (cached_object)
-			nl_cache_remove (cached_object);
-		nle = nl_cache_add (cache, kernel_object);
-		if (nle) {
-			nm_log_dbg (LOGD_PLATFORM, "refresh_object(reason %d) failed during nl_cache_add with %d", reason, nle);
-			return FALSE;
-		}
+	priv->nlh_seq_expect = seq;
+}
+
+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;
 
-		announce_object (platform, kernel_object, cached_object ? NM_PLATFORM_SIGNAL_CHANGED : NM_PLATFORM_SIGNAL_ADDED, reason);
+	_LOGT ("do_request_link (%d,%s)", ifindex, name ? name : "");
 
-		/* Refresh the master device (even on enslave/release) */
-		if (type == OBJECT_TYPE_LINK) {
-			int kernel_master = rtnl_link_get_master ((struct rtnl_link *) kernel_object);
-			int cached_master = cached_object ? rtnl_link_get_master ((struct rtnl_link *) cached_object) : 0;
-			struct nl_object *master_object;
+	if (ifindex > 0) {
+		NMPObject *obj;
 
-			if (kernel_master) {
-				master_object = build_rtnl_link (kernel_master, NULL, NM_LINK_TYPE_NONE);
-				refresh_object (platform, master_object, FALSE, NM_PLATFORM_REASON_INTERNAL);
-				nl_object_put (master_object);
-			}
-			if (cached_master && cached_master != kernel_master) {
-				master_object = build_rtnl_link (cached_master, NULL, NM_LINK_TYPE_NONE);
-				refresh_object (platform, master_object, FALSE, NM_PLATFORM_REASON_INTERNAL);
-				nl_object_put (master_object);
+		cache_prune_candidates_record_one (platform,
+		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
+		obj = nmp_object_new_link (ifindex);
+		_LOGT ("delayed-deletion: protect object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+		g_hash_table_insert (priv->delayed_deletion, obj, NULL);
+	}
+
+	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_all (platform, TRUE);
+
+	cache_delayed_deletion_prune (platform);
+	cache_prune_candidates_prune (platform);
+
+	if (handle_delayed_action)
+		delayed_action_handle_all (platform, FALSE);
+}
+
+static void
+do_request_one_type (NMPlatform *platform, NMPObjectType obj_type, gboolean handle_delayed_action)
+{
+	do_request_all (platform, delayed_action_refresh_from_object_type (obj_type), handle_delayed_action);
+}
+
+static void
+do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	guint32 seq;
+	DelayedActionType iflags;
+
+	nm_assert (!NM_FLAGS_ANY (action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
+	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
+
+	for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+		if (NM_FLAGS_HAS (action_type, iflags))
+			cache_prune_candidates_record_all (platform, delayed_action_refresh_to_object_type (iflags));
+	}
+
+	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);
+
+			/* clear any delayed action that request a refresh of this object type. */
+			priv->delayed_action.flags &= ~iflags;
+			if (obj_type == NMP_OBJECT_TYPE_LINK) {
+				priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+				g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
 			}
+
+			event_handler_read_netlink_all (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);
 
-	return TRUE;
+	cache_prune_candidates_prune (platform);
+
+	if (handle_delayed_action)
+		delayed_action_handle_all (platform, FALSE);
 }
 
-/* Decreases the reference count if @obj for convenience */
 static gboolean
-add_object (NMPlatform *platform, struct nl_object *obj)
+kernel_add_object (NMPlatform *platform, NMPObjectType obj_type, const struct nl_object *nlo)
 {
-	auto_nl_object struct nl_object *object = obj;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int nle;
 
-	g_return_val_if_fail (object, FALSE);
+	g_return_val_if_fail (nlo, FALSE);
 
-	nle = add_kernel_object (priv->nlh, object);
+	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);
+	}
+
+	_LOGT ("kernel-add-%s: returned %s (%d)",
+	       nmp_class_from_type (obj_type)->obj_type_name, nl_geterror (nle), -nle);
 
-	/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-	 * never know when something sends an identical object just before
-	 * NetworkManager.
-	 */
 	switch (nle) {
 	case -NLE_SUCCESS:
+		return -NLE_SUCCESS;
 	case -NLE_EXIST:
-		break;
+		/* 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:
-		error ("Netlink error adding %s: %s", to_string_object (platform, object),  nl_geterror (nle));
-		if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-			char buf[256];
-			struct nl_dump_params dp = {
-				.dp_type = NL_DUMP_DETAILS,
-				.dp_buf = buf,
-				.dp_buflen = sizeof (buf),
-			};
-
-			nl_object_dump (object, &dp);
-			buf[sizeof (buf) - 1] = '\0';
-			debug ("netlink object:\n%s", buf);
-		}
-		return FALSE;
+		return nle;
 	}
-
-	return refresh_object (platform, object, FALSE, NM_PLATFORM_REASON_INTERNAL);
 }
 
-/* Decreases the reference count if @obj for convenience */
-static gboolean
-delete_object (NMPlatform *platform, struct nl_object *object, gboolean do_refresh_object)
+static int
+kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const struct nl_object *object)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int object_type;
 	int nle;
-	gboolean result = FALSE;
-
-	object_type = object_type_from_nl_object (object);
-	g_return_val_if_fail (object_type != OBJECT_TYPE_UNKNOWN, FALSE);
 
 	switch (object_type) {
-	case OBJECT_TYPE_LINK:
+	case NMP_OBJECT_TYPE_LINK:
 		nle = rtnl_link_delete (priv->nlh, (struct rtnl_link *) object);
 		break;
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
+	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 OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
+	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:
@@ -1836,40 +2103,134 @@ delete_object (NMPlatform *platform, struct nl_object *object, gboolean do_refre
 
 	switch (nle) {
 	case -NLE_SUCCESS:
-		break;
+		return NLE_SUCCESS;
 	case -NLE_OBJ_NOTFOUND:
-		debug("delete_object failed with \"%s\" (%d), meaning the object was already removed",
-		      nl_geterror (nle), nle);
-		break;
+		_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 == OBJECT_TYPE_IP6_ADDRESS) {
+		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) */
-			debug("delete_object for address failed with \"%s\" (%d), meaning the address was already removed",
-			      nl_geterror (nle), nle);
-			break;
+			_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;
 		}
-		goto DEFAULT;
+		break;
 	case -NLE_NOADDR:
-		if (object_type == OBJECT_TYPE_IP4_ADDRESS || object_type == OBJECT_TYPE_IP6_ADDRESS) {
-			debug("delete_object for address failed with \"%s\" (%d), meaning the address was already removed",
-			      nl_geterror (nle), nle);
-			break;
+		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;
 		}
-		goto DEFAULT;
-	DEFAULT:
+		break;
 	default:
-		error ("Netlink error deleting %s: %s (%d)", to_string_object (platform, object), nl_geterror (nle), nle);
-		goto out;
+		break;
 	}
+	_LOGT ("kernel-delete-%s: failed with %s (%d)",
+	       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
+	return nle;
+}
 
-	if (do_refresh_object)
-		refresh_object (platform, object, TRUE, NM_PLATFORM_REASON_INTERNAL);
+static int
+kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *complete_from_cache)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	struct nl_msg *msg;
+	int nle;
+	const int nlflags = 0;
+	int ifindex;
 
-	result = TRUE;
+	ifindex = rtnl_link_get_ifindex (nlo);
 
-out:
-	nl_object_put (object);
-	return result;
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	/* 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;
+
+		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;
+		}
+
+		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;
 }
 
 static void
@@ -1881,47 +2242,40 @@ ref_object (struct nl_object *obj, void *data)
 	*out = obj;
 }
 
-static gboolean
-_rtnl_addr_timestamps_equal_fuzzy (guint32 ts1, guint32 ts2)
+static int
+event_seq_check (struct nl_msg *msg, gpointer user_data)
 {
-	guint32 diff;
+	NMPlatform *platform = NM_PLATFORM (user_data);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	struct nlmsghdr *hdr;
 
-	if (ts1 == ts2)
-		return TRUE;
-	if (ts1 == NM_PLATFORM_LIFETIME_PERMANENT ||
-	    ts2 == NM_PLATFORM_LIFETIME_PERMANENT)
-		return FALSE;
+	hdr = nlmsg_hdr (msg);
 
-	/** accept the timestamps as equal if they are within two seconds. */
-	diff = ts1 > ts2 ? ts1 - ts2 : ts2 - ts1;
-	return diff <= 2;
-}
+	if (hdr->nlmsg_seq == 0)
+		return NL_OK;
 
-static gboolean
-nm_nl_object_diff (ObjectType type, struct nl_object *_a, struct nl_object *_b)
-{
-	if (nl_object_diff (_a, _b)) {
-		/* libnl thinks objects are different*/
-		return TRUE;
-	}
+	priv->nlh_seq_last = hdr->nlmsg_seq;
 
-	if (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS) {
-		struct rtnl_addr *a = (struct rtnl_addr *) _a;
-		struct rtnl_addr *b = (struct rtnl_addr *) _b;
+	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);
 
-		/* libnl nl_object_diff() ignores differences in timestamp. Let's care about
-		 * them (if they are large enough).
-		 *
-		 * Note that these valid and preferred timestamps are absolute, after
-		 * _rtnl_addr_hack_lifetimes_rel_to_abs(). */
-		if (   !_rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_preferred_lifetime (a),
-							  rtnl_addr_get_preferred_lifetime (b))
-		    || !_rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_valid_lifetime (a),
-							  rtnl_addr_get_valid_lifetime (b)))
-			return TRUE;
-	}
+		priv->nlh_seq_expect = 0;
+	} else
+		_LOGT ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last);
 
-	return FALSE;
+	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
@@ -1934,123 +2288,71 @@ static int
 event_notification (struct nl_msg *msg, gpointer user_data)
 {
 	NMPlatform *platform = NM_PLATFORM (user_data);
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_cache *cache;
-	auto_nl_object struct nl_object *object = NULL;
-	auto_nl_object struct nl_object *cached_object = NULL;
-	auto_nl_object struct nl_object *kernel_object = NULL;
-	int event;
-	int nle;
-	ObjectType type;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (user_data);
+	auto_nl_object struct nl_object *nlo = NULL;
+	auto_nmp_obj NMPObject *obj = NULL;
+	struct nlmsghdr *msghdr;
+	char buf_nlmsg_type[16];
 
-	event = nlmsg_hdr (msg)->nlmsg_type;
+	msghdr = nlmsg_hdr (msg);
 
-	if (priv->support_kernel_extended_ifa_flags == 0 && event == RTM_NEWADDR) {
-		/* if kernel support for extended ifa flags is still undecided, use the opportunity
-		 * now and use @msg to decide it. This saves a blocking net link request.
-		 **/
-		_check_support_kernel_extended_ifa_flags_init (priv, 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, &object);
-	g_return_val_if_fail (object, NL_OK);
-
-	type = object_type_from_nl_object (object);
+	nl_msg_parse (msg, ref_object, &nlo);
+	if (!nlo)
+		return NL_OK;
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-		if (type == OBJECT_TYPE_LINK) {
-			const char *name = rtnl_link_get_name ((struct rtnl_link *) object);
+	if (_support_user_ipv6ll_still_undecided() && msghdr->nlmsg_type == RTM_NEWLINK)
+		_support_user_ipv6ll_detect ((struct rtnl_link *) nlo);
 
-			debug ("netlink event (type %d) for link: %s (%d, family %d)",
-			       event, name ? name : "(unknown)",
-			       rtnl_link_get_ifindex ((struct rtnl_link *) object),
-			       rtnl_link_get_family ((struct rtnl_link *) object));
-		} else
-			debug ("netlink event (type %d)", event);
-	}
+	obj = nmp_object_from_nl (platform, nlo, FALSE, TRUE);
 
-	cache = choose_cache_by_type (platform, type);
-	cached_object = nm_nl_cache_search (cache, object);
-	kernel_object = get_kernel_object (priv->nlh, object);
+	_LOGD ("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));
+	if (obj) {
+		auto_nmp_obj NMPObject *obj_cache = NULL;
 
-	hack_empty_master_iff_lower_up (platform, kernel_object);
+		switch (msghdr->nlmsg_type) {
 
-	/* Removed object */
-	switch (event) {
-	case RTM_DELLINK:
-	case RTM_DELADDR:
-	case RTM_DELROUTE:
-		/* Ignore inconsistent deletion
-		 *
-		 * Quick external deletion and addition can be occasionally
-		 * seen as just a change.
-		 */
-		if (kernel_object)
-			return NL_OK;
-		/* Ignore internal deletion */
-		if (!cached_object)
-			return NL_OK;
-
-		nl_cache_remove (cached_object);
-		/* Don't announce removed interfaces that are not recognized by
-		 * udev. They were either not yet discovered or they have been
-		 * already removed and announced.
-		 */
-		if (event == RTM_DELLINK) {
-			if (!link_is_announceable (platform, (struct rtnl_link *) cached_object))
-				return NL_OK;
-		}
-		announce_object (platform, cached_object, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL);
+		case RTM_NEWLINK:
+			if (   NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK
+			    && g_hash_table_lookup (priv->delayed_deletion, obj) != NULL) {
+				/* the object is scheduled for delayed deletion. Replace that object
+				 * by clearing the value from priv->delayed_deletion. */
+				_LOGT ("delayed-deletion: clear delayed deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), NULL);
+			}
+			/* fall-through */
+		case RTM_NEWADDR:
+		case RTM_NEWROUTE:
+			cache_update_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
+			break;
 
-		return NL_OK;
-	case RTM_NEWLINK:
-	case RTM_NEWADDR:
-	case RTM_NEWROUTE:
-		/* Ignore inconsistent addition or change (kernel will send a good one)
-		 *
-		 * Quick sequence of RTM_NEWLINK notifications can be occasionally
-		 * collapsed to just one addition or deletion, depending of whether we
-		 * already have the object in cache.
-		 */
-		if (!kernel_object)
-			return NL_OK;
-
-		/* Ignore unsupported object types (e.g. AF_PHONET family addresses) */
-		if (type == OBJECT_TYPE_UNKNOWN)
-			return NL_OK;
-
-		/* Handle external addition */
-		if (!cached_object) {
-			nle = nl_cache_add (cache, kernel_object);
-			if (nle) {
-				error ("netlink cache error: %s", nl_geterror (nle));
-				return NL_OK;
+		case RTM_DELLINK:
+			if (   NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK
+			    && g_hash_table_contains (priv->delayed_deletion, obj)) {
+				/* We sometimes receive spurious RTM_DELLINK events. In this case, we want to delay
+				 * the deletion of the object until later. */
+				_LOGT ("delayed-deletion: delay deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), nmp_object_ref (obj));
+				break;
 			}
-			announce_object (platform, kernel_object, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL);
-			return NL_OK;
-		}
-		/* Ignore non-change
-		 *
-		 * This also catches notifications for internal addition or change, unless
-		 * another action occured very soon after it.
-		 */
-		if (!nm_nl_object_diff (type, kernel_object, cached_object))
-			return NL_OK;
-
-		/* Handle external change */
-		nl_cache_remove (cached_object);
-		nle = nl_cache_add (cache, kernel_object);
-		if (nle) {
-			error ("netlink cache error: %s", nl_geterror (nle));
-			return NL_OK;
+			/* fall-through */
+		case RTM_DELADDR:
+		case RTM_DELROUTE:
+			cache_remove_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
+			break;
+
+		default:
+			break;
 		}
-		announce_object (platform, kernel_object, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_EXTERNAL);
 
-		return NL_OK;
-	default:
-		error ("Unknown netlink event: %d", event);
-		return NL_OK;
+		cache_prune_candidates_drop (platform, obj_cache);
 	}
+
+	return NL_OK;
 }
 
 /******************************************************************/
@@ -2219,40 +2521,81 @@ sysctl_get (NMPlatform *platform, const char *path)
 
 /******************************************************************/
 
+static const NMPObject *
+cache_lookup_link (NMPlatform *platform, int ifindex)
+{
+	const NMPObject *obj_cache;
+
+	obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
+	if (!nmp_object_is_visible (obj_cache))
+		return NULL;
+
+	return obj_cache;
+}
+
 static GArray *
 link_get_all (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *links = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformLink), nl_cache_nitems (priv->link_cache));
-	NMPlatformLink device;
-	struct nl_object *object;
 
-	for (object = nl_cache_get_first (priv->link_cache); object; object = nl_cache_get_next (object)) {
-		struct rtnl_link *rtnl_link = (struct rtnl_link *) object;
+	return nmp_cache_lookup_multi_to_array (priv->cache,
+	                                        NMP_OBJECT_TYPE_LINK,
+	                                        nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, NMP_OBJECT_TYPE_LINK, TRUE));
+}
 
-		if (link_is_announceable (platform, rtnl_link)) {
-			if (init_link (platform, &device, rtnl_link))
-				g_array_append_val (links, device);
-		}
-	}
+static const NMPlatformLink *
+_nm_platform_link_get (NMPlatform *platform, int ifindex)
+{
+	const NMPObject *obj;
 
-	return links;
+	obj = cache_lookup_link (platform, ifindex);
+	return obj ? &obj->link : NULL;
 }
 
-static gboolean
-_nm_platform_link_get (NMPlatform *platform, int ifindex, NMPlatformLink *l)
+static const NMPlatformLink *
+_nm_platform_link_get_by_ifname (NMPlatform *platform,
+                                 const char *ifname)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
+	const NMPObject *obj = NULL;
 
-	rtnllink = rtnl_link_get (priv->link_cache, ifindex);
-	if (rtnllink) {
-		if (link_is_announceable (platform, rtnllink)) {
-			if (init_link (platform, l, rtnllink))
-				return TRUE;
-		}
+	if (ifname && *ifname) {
+		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+		                                  0, ifname, TRUE, NM_LINK_TYPE_NONE, NULL, NULL);
 	}
-	return FALSE;
+	return obj ? &obj->link : NULL;
+}
+
+struct _nm_platform_link_get_by_address_data {
+	gconstpointer address;
+	guint8 length;
+};
+
+static gboolean
+_nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_platform_link_get_by_address_data *d)
+{
+	return obj->link.addr.len == d->length && !memcmp (obj->link.addr.data, d->address, d->length);
+}
+
+static const NMPlatformLink *
+_nm_platform_link_get_by_address (NMPlatform *platform,
+                                  gconstpointer address,
+                                  size_t length)
+{
+	const NMPObject *obj;
+	struct _nm_platform_link_get_by_address_data d = {
+		.address = address,
+		.length = length,
+	};
+
+	if (length <= 0 || length > NM_UTILS_HWADDR_LEN_MAX)
+		return NULL;
+	if (!address)
+		return NULL;
+
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, NULL, TRUE, NM_LINK_TYPE_NONE,
+	                                  (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, &d);
+	return obj ? &obj->link : NULL;
 }
 
 static struct nl_object *
@@ -2261,331 +2604,392 @@ build_rtnl_link (int ifindex, const char *name, NMLinkType type)
 	struct rtnl_link *rtnllink;
 	int nle;
 
-	rtnllink = _nm_rtnl_link_alloc (ifindex, name);
+	rtnllink = _nl_rtnl_link_alloc (ifindex, name);
 	if (type) {
-		nle = rtnl_link_set_type (rtnllink, type_to_string (type));
+		nle = rtnl_link_set_type (rtnllink, nm_link_type_to_rtnl_type_string (type));
 		g_assert (!nle);
 	}
 	return (struct nl_object *) rtnllink;
 }
 
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+	const NMPlatformLink *obj = (const NMPlatformLink *) _obj;
+
+	return build_rtnl_link (obj->ifindex,
+	                        obj->name[0] ? obj->name : NULL,
+	                        obj->type);
+}
+
 static gboolean
-link_add (NMPlatform *platform, const char *name, NMLinkType type, const void *address, size_t address_len)
+do_add_link (NMPlatform *platform, const char *name, const struct rtnl_link *nlo)
 {
-	int r;
-	struct nl_object *l;
+	NMPObject obj_needle;
+	int nle;
 
-	if (type == NM_LINK_TYPE_BOND) {
-		/* When the kernel loads the bond module, either via explicit modprobe
-		 * or automatically in response to creating a bond master, it will also
-		 * create a 'bond0' interface.  Since the bond we're about to create may
-		 * or may not be named 'bond0' prevent potential confusion about a bond
-		 * that the user didn't want by telling the bonding module not to create
-		 * bond0 automatically.
-		 */
-		if (!g_file_test ("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS))
-			/* Ignore return value to shut up the compiler */
-			r = system ("modprobe bonding max_bonds=0");
-	}
+	event_handler_read_netlink_all (platform, FALSE);
 
-	debug ("link: add link '%s' of type '%s' (%d)",
-	       name, type_to_string (type), (int) type);
+	nle = kernel_add_object (platform, NMP_OBJECT_TYPE_LINK, (const struct nl_object *) nlo);
+	if (nle < 0) {
+		_LOGE ("do-add-link: failure adding link '%s': %s", name, nl_geterror (nle));
+		return FALSE;
+	}
+	_LOGD ("do-add-link: success adding link '%s'", name);
 
-	l = build_rtnl_link (0, name, type);
+	nmp_object_stackinit_id_link (&obj_needle, 0);
+	g_strlcpy (obj_needle.link.name, name, sizeof (obj_needle.link.name));
 
-	g_assert ( (address != NULL) ^ (address_len == 0) );
-	if (address) {
-		auto_nl_addr struct nl_addr *nladdr = _nm_nl_addr_build (AF_LLC, address, address_len);
+	delayed_action_handle_all (platform, TRUE);
 
-		rtnl_link_set_addr ((struct rtnl_link *) l, nladdr);
+	/* 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);
 	}
-	return add_object (platform, l);
+
+	/* 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 struct rtnl_link *
-link_get (NMPlatform *platform, int ifindex)
+static gboolean
+do_add_link_with_lookup (NMPlatform *platform, const char *name, const struct rtnl_link *nlo, NMLinkType expected_link_type, NMPlatformLink *out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct rtnl_link *rtnllink = rtnl_link_get (priv->link_cache, ifindex);
+	const NMPObject *obj;
 
-	if (!rtnllink) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		return NULL;
+	do_add_link (platform, name, nlo);
+
+	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;
+	return !!obj;
+}
+
+static gboolean
+do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
+{
+	int nle;
+
+	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);
+
+	nle = kernel_add_object (platform, NMP_OBJECT_GET_CLASS (obj_id)->obj_type, (const struct nl_object *) nlo);
+	if (nle < 0) {
+		_LOGW ("do-add-%s: failure adding %s '%s': %s (%d)",
+		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+		       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));
 
-	/* physical interfaces must be found by udev before they can be used */
-	if (!link_is_announceable (platform, rtnllink)) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		rtnl_link_put (rtnllink);
-		return NULL;
+	delayed_action_handle_all (platform, TRUE);
+
+	/* 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);
 	}
 
-	return rtnllink;
+	/* 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;
 }
 
+
 static gboolean
-link_change (NMPlatform *platform, int ifindex, struct rtnl_link *change)
+do_delete_object (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+	auto_nl_object struct nl_object *nlo_free = NULL;
 	int nle;
 
-	if (!rtnllink)
-		return FALSE;
-	g_return_val_if_fail (rtnl_link_get_ifindex (change) > 0, FALSE);
+	event_handler_read_netlink_all (platform, FALSE);
 
-	nle = rtnl_link_change (priv->nlh, rtnllink, change, 0);
+	if (!nlo)
+		nlo = nlo_free = nmp_object_to_nl (platform, obj_id, FALSE);
+
+	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, TRUE);
+
+	/* 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;
+
+		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);
+		}
+	}
+
+	/* 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;
+}
+
+static NMPlatformError
+do_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean complete_from_cache)
+{
+	int nle;
+	int ifindex;
+	gboolean complete_from_cache2 = complete_from_cache;
+
+	ifindex = rtnl_link_get_ifindex (nlo);
+	if (ifindex <= 0)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+
+	nle = kernel_change_link (platform, nlo, &complete_from_cache2);
 
-	/* 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:
+		_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:
-		error ("Firmware not found for changing link %s; Netlink error: %s)", to_string_link (platform, change), nl_geterror (nle));
-		platform->error = NM_PLATFORM_ERROR_NO_FIRMWARE;
-		return FALSE;
+		/* fall-through */
 	default:
-		error ("Netlink error changing link %s: %s", to_string_link (platform, change), nl_geterror (nle));
-		return FALSE;
+		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;
 	}
 
-	return refresh_object (platform, (struct nl_object *) rtnllink, FALSE, NM_PLATFORM_REASON_INTERNAL);
+	/* 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;
 }
 
 static gboolean
-link_delete (NMPlatform *platform, int ifindex)
+link_add (NMPlatform *platform,
+          const char *name,
+          NMLinkType type,
+          const void *address,
+          size_t address_len,
+          NMPlatformLink *out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct rtnl_link *rtnllink = rtnl_link_get (priv->link_cache, ifindex);
+	auto_nl_object struct nl_object *l = NULL;
 
-	if (!rtnllink) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		return FALSE;
+	if (type == NM_LINK_TYPE_BOND) {
+		/* When the kernel loads the bond module, either via explicit modprobe
+		 * or automatically in response to creating a bond master, it will also
+		 * create a 'bond0' interface.  Since the bond we're about to create may
+		 * or may not be named 'bond0' prevent potential confusion about a bond
+		 * that the user didn't want by telling the bonding module not to create
+		 * bond0 automatically.
+		 */
+		if (!g_file_test ("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS))
+			nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
-	return delete_object (platform, build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_NONE), TRUE);
-}
+	debug ("link: add link '%s' of type '%s' (%d)",
+	       name, nm_link_type_to_string (type), (int) type);
 
-static int
-link_get_ifindex (NMPlatform *platform, const char *ifname)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	l = build_rtnl_link (0, name, type);
 
-	return rtnl_link_name2i (priv->link_cache, ifname);
-}
+	g_assert ( (address != NULL) ^ (address_len == 0) );
+	if (address) {
+		auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, address_len);
 
-static const char *
-link_get_name (NMPlatform *platform, int ifindex)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+		rtnl_link_set_addr ((struct rtnl_link *) l, nladdr);
+	}
 
-	return rtnllink ? rtnl_link_get_name (rtnllink) : NULL;
+	return do_add_link_with_lookup (platform, name, (struct rtnl_link *) l, type, out_link);
 }
 
-static NMLinkType
-link_get_type (NMPlatform *platform, int ifindex)
+static gboolean
+link_delete (NMPlatform *platform, int ifindex)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	NMPObject obj_needle;
+	const NMPObject *obj;
+
+	obj = nmp_cache_lookup_link (priv->cache, ifindex);
+	if (!obj || !obj->_link.netlink.is_in_netlink)
+		return FALSE;
 
-	return link_extract_type (platform, rtnllink, NULL);
+	nmp_object_stackinit_id_link (&obj_needle, ifindex);
+	return do_delete_object (platform, &obj_needle, NULL);
 }
 
 static const char *
 link_get_type_name (NMPlatform *platform, int ifindex)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-	const char *type;
-
-	link_extract_type (platform, rtnllink, &type);
-	return type;
-}
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
 
-static guint32
-link_get_flags (NMPlatform *platform, int ifindex)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-
-	if (!rtnllink)
-		return IFF_NOARP;
+	if (!obj)
+		return NULL;
 
-	return rtnl_link_get_flags (rtnllink);
+	if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
+		/* We could detect the @link_type. In this case the function returns
+		 * our internel module names, which differs from rtnl_link_get_type():
+		 *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
+		 *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
+		 * Note that this functions is only used by NMDeviceGeneric to
+		 * set type_description. */
+		return nm_link_type_to_string (obj->link.type);
+	}
+	/* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */
+	return str_if_set (obj->link.kind, "unknown");
 }
 
 static gboolean
-link_refresh (NMPlatform *platform, int ifindex)
+link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged)
 {
-	auto_nl_object struct rtnl_link *rtnllink = _nm_rtnl_link_alloc (ifindex, NULL);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *link;
+	GUdevDevice *udev_device = NULL;
 
-	return refresh_object (platform, (struct nl_object *) rtnllink, FALSE, NM_PLATFORM_REASON_EXTERNAL);
-}
+	link = nmp_cache_lookup_link (priv->cache, ifindex);
+	if (link)
+		udev_device = link->_link.udev.device;
 
-static gboolean
-link_is_up (NMPlatform *platform, int ifindex)
-{
-	return !!(link_get_flags (platform, ifindex) & IFF_UP);
-}
+	if (udev_device && g_udev_device_get_property (udev_device, "NM_UNMANAGED")) {
+		*unmanaged = g_udev_device_get_property_as_boolean (udev_device, "NM_UNMANAGED");
+		return TRUE;
+	}
 
-static gboolean
-link_is_connected (NMPlatform *platform, int ifindex)
-{
-	return !!(link_get_flags (platform, ifindex) & IFF_LOWER_UP);
+	return FALSE;
 }
 
 static gboolean
-link_uses_arp (NMPlatform *platform, int ifindex)
+link_refresh (NMPlatform *platform, int ifindex)
 {
-	return !(link_get_flags (platform, ifindex) & IFF_NOARP);
+	do_request_link (platform, ifindex, NULL, TRUE);
+	return !!cache_lookup_link (platform, ifindex);
 }
 
-static gboolean
+static NMPlatformError
 link_change_flags (NMPlatform *platform, int ifindex, unsigned int flags, gboolean value)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
+	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);
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-		char buf[512];
-
-		rtnl_link_flags2str (flags, buf, sizeof (buf));
-		debug ("link: change %d: flags %s '%s' (%d)", ifindex, value ? "set" : "unset", buf, flags);
-	}
+	_LOGD ("link: change %d: flags %s '%s' (%d)", ifindex,
+	       value ? "set" : "unset",
+	       rtnl_link_flags2str (flags, buf, sizeof (buf)),
+	       flags);
 
-	return link_change (platform, ifindex, change);
+	return do_change_link (platform, change, FALSE);
 }
 
 static gboolean
-link_set_up (NMPlatform *platform, int ifindex)
+link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, TRUE);
+	NMPlatformError plerr;
+
+	plerr = link_change_flags (platform, ifindex, IFF_UP, TRUE);
+	if (out_no_firmware)
+		*out_no_firmware = plerr == NM_PLATFORM_ERROR_NO_FIRMWARE;
+	return plerr == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_down (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, FALSE);
+	return link_change_flags (platform, ifindex, IFF_UP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_arp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, FALSE);
+	return link_change_flags (platform, ifindex, IFF_NOARP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_noarp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, TRUE);
+	return link_change_flags (platform, ifindex, IFF_NOARP, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gboolean
-link_get_user_ipv6ll_enabled (NMPlatform *platform, int ifindex)
+static const char *
+link_get_udi (NMPlatform *platform, int ifindex)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
 
-	if (priv->support_user_ipv6ll > 0) {
-		auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-		uint8_t mode = 0;
+	if (   !obj
+	    || !obj->_link.netlink.is_in_netlink
+	    || !obj->_link.udev.device)
+		return NULL;
+	return g_udev_device_get_sysfs_path (obj->_link.udev.device);
+}
 
-		if (rtnllink) {
-			if (rtnl_link_inet6_get_addr_gen_mode (rtnllink, &mode) != 0) {
-				/* Default to "disabled" on error */
-				return FALSE;
-			}
-			return mode == IN6_ADDR_GEN_MODE_NONE;
-		}
-	}
-#endif
-	return FALSE;
+static GObject *
+link_get_udev_device (NMPlatform *platform, int ifindex)
+{
+	const NMPObject *obj_cache;
+
+	/* we don't use cache_lookup_link() because this would return NULL
+	 * if the link is not visible in libnl. For link_get_udev_device()
+	 * we want to return whatever we have, even if the link itself
+	 * appears invisible via other platform functions. */
+
+	obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
+	return obj_cache ? (GObject *) obj_cache->_link.udev.device : NULL;
 }
 
 static gboolean
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 #if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	if (check_support_user_ipv6ll (platform)) {
-		auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
+	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];
 
-		rtnl_link_inet6_set_addr_gen_mode (change, mode);
+		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 link_change (platform, ifindex, change);
+		return do_change_link (platform, nlo, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 	}
 #endif
 	return FALSE;
 }
 
 static gboolean
-supports_ethtool_carrier_detect (const char *ifname)
-{
-	struct ethtool_cmd edata = { .cmd = ETHTOOL_GLINK };
-
-	/* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we
-	 * assume the device supports carrier-detect, otherwise we assume it
-	 * doesn't.
-	 */
-	return ethtool_get (ifname, &edata);
-}
-
-static gboolean
-supports_mii_carrier_detect (const char *ifname)
-{
-	int fd;
-	struct ifreq ifr;
-	struct mii_ioctl_data *mii;
-	gboolean supports_mii = FALSE;
-
-	fd = socket (PF_INET, SOCK_DGRAM, 0);
-	if (fd < 0) {
-		nm_log_err (LOGD_PLATFORM, "couldn't open control socket.");
-		return FALSE;
-	}
-
-	memset (&ifr, 0, sizeof (struct ifreq));
-	strncpy (ifr.ifr_name, ifname, IFNAMSIZ);
-
-	errno = 0;
-	if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) {
-		nm_log_dbg (LOGD_PLATFORM, "SIOCGMIIPHY failed: %d", errno);
-		goto out;
-	}
-
-	/* If we can read the BMSR register, we assume that the card supports MII link detection */
-	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
-	mii->reg_num = MII_BMSR;
-
-	if (ioctl (fd, SIOCGMIIREG, &ifr) == 0) {
-		nm_log_dbg (LOGD_PLATFORM, "SIOCGMIIREG result 0x%X", mii->val_out);
-		supports_mii = TRUE;
-	} else {
-		nm_log_dbg (LOGD_PLATFORM, "SIOCGMIIREG failed: %d", errno);
-	}
-
- out:
-	close (fd);
-	nm_log_dbg (LOGD_PLATFORM, "MII %s supported", supports_mii ? "is" : "not");
-	return supports_mii;	
-}
-
-static gboolean
 link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 {
-	const char *name = nm_platform_link_get_name (ifindex);
+	const char *name = nm_platform_link_get_name (platform, ifindex);
 
 	if (!name)
 		return FALSE;
@@ -2594,103 +2998,56 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 	 * us whether the device actually supports carrier detection in the first
 	 * place. We assume any device that does implements one of these two APIs.
 	 */
-	return supports_ethtool_carrier_detect (name) || supports_mii_carrier_detect (name);
+	return nmp_utils_ethtool_supports_carrier_detect (name) || nmp_utils_mii_supports_carrier_detect (name);
 }
 
 static gboolean
 link_supports_vlans (NMPlatform *platform, int ifindex)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-	const char *name = nm_platform_link_get_name (ifindex);
-	gs_free struct ethtool_gfeatures *features = NULL;
-	int idx, block, bit, size;
+	const NMPObject *obj;
 
-	/* Only ARPHRD_ETHER links can possibly support VLANs. */
-	if (!rtnllink || rtnl_link_get_arptype (rtnllink) != ARPHRD_ETHER)
-		return FALSE;
+	obj = cache_lookup_link (platform, ifindex);
 
-	if (!name)
-		return FALSE;
-
-	idx = ethtool_get_stringset_index (name, ETH_SS_FEATURES, "vlan-challenged");
-	if (idx == -1) {
-		debug ("vlan-challenged ethtool feature does not exist?");
-		return FALSE;
-	}
-
-	block = idx /  32;
-	bit = idx % 32;
-	size = block + 1;
-
-	features = g_malloc0 (sizeof (*features) + size * sizeof (struct ethtool_get_features_block));
-	features->cmd = ETHTOOL_GFEATURES;
-	features->size = size;
-
-	if (!ethtool_get (name, features))
+	/* Only ARPHRD_ETHER links can possibly support VLANs. */
+	if (!obj || obj->link.arptype != ARPHRD_ETHER)
 		return FALSE;
 
-	return !(features->features[block].active & (1 << bit));
+	return nmp_utils_ethtool_supports_vlans (obj->link.name);
 }
 
 static gboolean
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
-	auto_nl_addr struct nl_addr *nladdr = _nm_nl_addr_build (AF_LLC, address, 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);
+	gs_free char *mac = NULL;
 
 	rtnl_link_set_addr (change, nladdr);
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-		char *mac = nm_utils_hwaddr_ntoa (address, length);
-
-		debug ("link: change %d: address %s (%lu bytes)", ifindex, mac, (unsigned long) length);
-		g_free (mac);
-	}
-
-	return link_change (platform, ifindex, change);
+	_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;
 }
 
-static gconstpointer
-link_get_address (NMPlatform *platform, int ifindex, size_t *length)
+static gboolean
+link_get_permanent_address (NMPlatform *platform,
+                            int ifindex,
+                            guint8 *buf,
+                            size_t *length)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-	struct nl_addr *nladdr;
-	size_t l = 0;
-	gconstpointer a = NULL;
-
-	if (rtnllink &&
-	    (nladdr = rtnl_link_get_addr (rtnllink))) {
-		l = nl_addr_get_len (nladdr);
-		if (l > NM_UTILS_HWADDR_LEN_MAX) {
-			if (length)
-				*length = 0;
-			g_return_val_if_reached (NULL);
-		} else if (l > 0)
-			a = nl_addr_get_binary_addr (nladdr);
-	}
-
-	if (length)
-		*length = l;
-	return a;
+	return nmp_utils_ethtool_get_permanent_address (nm_platform_link_get_name (platform, ifindex), buf, length);
 }
 
 static gboolean
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
+	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
 
 	rtnl_link_set_mtu (change, mtu);
 	debug ("link: change %d: mtu %lu", ifindex, (unsigned long)mtu);
 
-	return link_change (platform, ifindex, change);
-}
-
-static guint32
-link_get_mtu (NMPlatform *platform, int ifindex)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-
-	return rtnllink ? rtnl_link_get_mtu (rtnllink) : 0;
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static char *
@@ -2699,7 +3056,7 @@ link_get_physical_port_id (NMPlatform *platform, int ifindex)
 	const char *ifname;
 	char *path, *id;
 
-	ifname = nm_platform_link_get_name (ifindex);
+	ifname = nm_platform_link_get_name (platform, ifindex);
 	if (!ifname)
 		return NULL;
 
@@ -2719,7 +3076,7 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 	gs_free char *path = NULL, *id = NULL;
 	gint64 int_val;
 
-	ifname = nm_platform_link_get_name (ifindex);
+	ifname = nm_platform_link_get_name (platform, ifindex);
 	if (!ifname)
 		return 0;
 
@@ -2731,16 +3088,20 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 		return 0;
 
 	/* Value is reported as hex */
-	int_val = nm_utils_ascii_str_to_int64 (id, 16, 0, G_MAXUINT16, 0);
+	int_val = _nm_utils_ascii_str_to_int64 (id, 16, 0, G_MAXUINT16, 0);
 
 	return errno ? 0 : (int) int_val;
 }
 
 static int
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags)
-{
-	struct nl_object *object = build_rtnl_link (0, name, NM_LINK_TYPE_VLAN);
-	struct rtnl_link *rtnllink = (struct rtnl_link *) object;
+vlan_add (NMPlatform *platform,
+          const char *name,
+          int parent,
+          int vlan_id,
+          guint32 vlan_flags,
+          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;
 
 	kernel_flags = 0;
@@ -2758,63 +3119,61 @@ vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint
 	debug ("link: add vlan '%s', parent %d, vlan id %d, flags %X (native: %X)",
 	       name, parent, vlan_id, (unsigned int) vlan_flags, kernel_flags);
 
-	return add_object (platform, object);
+	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)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+	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 = rtnllink ? rtnl_link_get_link (rtnllink) : 0;
+		*parent = p;
 	if (vlan_id)
-		*vlan_id = rtnllink ? rtnl_link_vlan_get_id (rtnllink) : 0;
-
-	return !!rtnllink;
+		*vlan_id = v;
+	return !!obj;
 }
 
 static gboolean
 vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to)
 {
-	/* We have to use link_get() because a "blank" rtnl_link won't have the
-	 * right data structures to be able to call rtnl_link_vlan_set_ingress_map()
-	 * on it. (Likewise below in vlan_set_egress_map().)
-	 */
-	auto_nl_object struct rtnl_link *change = link_get (platform, ifindex);
+	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
 
-	if (!change)
-		return FALSE;
+	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 link_change (platform, ifindex, change);
+	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 = link_get (platform, ifindex);
+	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
 
-	if (!change)
-		return FALSE;
+	rtnl_link_set_type (change, "vlan");
 	rtnl_link_vlan_set_egress_map (change, from, to);
 
 	debug ("link: change %d: vlan egress map %d -> %d", ifindex, from, to);
 
-	return link_change (platform, ifindex, change);
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_enslave (NMPlatform *platform, int master, int slave)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (slave, NULL);
+	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (slave, NULL);
 
 	rtnl_link_set_master (change, master);
 	debug ("link: change %d: enslave to master %d", slave, master);
 
-	return link_change (platform, slave, change);
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -2823,19 +3182,11 @@ link_release (NMPlatform *platform, int master, int slave)
 	return link_enslave (platform, 0, slave);
 }
 
-static int
-link_get_master (NMPlatform *platform, int slave)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, slave);
-
-	return rtnllink ? rtnl_link_get_master (rtnllink) : 0;
-}
-
 static char *
-link_option_path (int master, const char *category, const char *option)
+link_option_path (NMPlatform *platform, int master, const char *category, const char *option)
 {
-	const char *name = nm_platform_link_get_name (master);
-   
+	const char *name = nm_platform_link_get_name (platform, master);
+
 	if (!name || !category || !option)
 		return NULL;
 
@@ -2846,31 +3197,30 @@ link_option_path (int master, const char *category, const char *option)
 }
 
 static gboolean
-link_set_option (int master, const char *category, const char *option, const char *value)
+link_set_option (NMPlatform *platform, int master, const char *category, const char *option, const char *value)
 {
-	gs_free char *path = link_option_path (master, category, option);
+	gs_free char *path = link_option_path (platform, master, category, option);
 
-	return path && nm_platform_sysctl_set (path, value);
+	return path && nm_platform_sysctl_set (platform, path, value);
 }
 
 static char *
-link_get_option (int master, const char *category, const char *option)
+link_get_option (NMPlatform *platform, int master, const char *category, const char *option)
 {
-	gs_free char *path = link_option_path (master, category, option);
+	gs_free char *path = link_option_path (platform, master, category, option);
 
-	return path ? nm_platform_sysctl_get (path) : NULL;
+	return path ? nm_platform_sysctl_get (platform, path) : NULL;
 }
 
 static const char *
 master_category (NMPlatform *platform, int master)
 {
-	switch (link_get_type (platform, master)) {
+	switch (nm_platform_link_get_type (platform, master)) {
 	case NM_LINK_TYPE_BRIDGE:
 		return "bridge";
 	case NM_LINK_TYPE_BOND:
 		return "bonding";
 	default:
-		g_return_val_if_reached (NULL);
 		return NULL;
 	}
 }
@@ -2878,18 +3228,15 @@ master_category (NMPlatform *platform, int master)
 static const char *
 slave_category (NMPlatform *platform, int slave)
 {
-	int master = link_get_master (platform, slave);
+	int master = nm_platform_link_get_master (platform, slave);
 
-	if (master <= 0) {
-		platform->error = NM_PLATFORM_ERROR_NOT_SLAVE;
+	if (master <= 0)
 		return NULL;
-	}
 
-	switch (link_get_type (platform, master)) {
+	switch (nm_platform_link_get_type (platform, master)) {
 	case NM_LINK_TYPE_BRIDGE:
 		return "brport";
 	default:
-		g_return_val_if_reached (NULL);
 		return NULL;
 	}
 }
@@ -2897,84 +3244,187 @@ slave_category (NMPlatform *platform, int slave)
 static gboolean
 master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
 {
-	return link_set_option (master, master_category (platform, master), option, value);
+	return link_set_option (platform, master, master_category (platform, master), option, value);
 }
 
 static char *
 master_get_option (NMPlatform *platform, int master, const char *option)
 {
-	return link_get_option (master, master_category (platform, master), option);
+	return link_get_option (platform, master, master_category (platform, master), option);
 }
 
 static gboolean
 slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value)
 {
-	return link_set_option (slave, slave_category (platform, slave), option, value);
+	return link_set_option (platform, slave, slave_category (platform, slave), option, value);
 }
 
 static char *
 slave_get_option (NMPlatform *platform, int slave, const char *option)
 {
-	return link_get_option (slave, slave_category (platform, slave), option);
+	return link_get_option (platform, slave, slave_category (platform, slave), option);
 }
 
 static gboolean
-infiniband_partition_add (NMPlatform *platform, int parent, int p_key)
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link)
 {
-	const char *parent_name;
-	char *path, *id;
-	gboolean success;
+	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;
+
+	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
+	if (!obj_parent || !obj_parent->link.name[0])
+		g_return_val_if_reached (FALSE);
 
-	parent_name = nm_platform_link_get_name (parent);
-	g_return_val_if_fail (parent_name != NULL, 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 (parent_name));
+	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);
-	success = nm_platform_sysctl_set (path, id);
-	g_free (id);
-	g_free (path);
+	if (!nm_platform_sysctl_set (platform, path, id))
+		return FALSE;
 
-	if (success) {
-		gs_free char *ifname = g_strdup_printf ("%s.%04x", parent_name, p_key);
-		auto_nl_object struct rtnl_link *rtnllink = _nm_rtnl_link_alloc (0, ifname);
+	do_request_link (platform, 0, ifname, TRUE);
 
-		success = refresh_object (platform, (struct nl_object *) rtnllink, FALSE, NM_PLATFORM_REASON_INTERNAL);
+	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;
+}
+
+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
+
+#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
+#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
+
+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 },
+};
+
+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;
+
+	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;
+
+	info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]);
+
+	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;
 	}
 
-	return success;
+	return 0;
+}
+
+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)
+		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 (strstr (contents, "datagram"))
+			info.mode = "datagram";
+		else if (strstr (contents, "connected"))
+			info.mode = "connected";
+		g_free (contents);
+
+		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;
+
+		info.p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
+		g_free (contents);
+
+		if (info.p_key < 0)
+			return FALSE;
+	}
+
+	if (p_key)
+		*p_key = info.p_key;
+	if (mode)
+		*mode = info.mode;
+	return TRUE;
 }
 
 static gboolean
 veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props)
 {
 	const char *ifname;
-	gs_free struct ethtool_stats *stats = NULL;
-	int peer_ifindex_stat;
+	int peer_ifindex;
 
-	ifname = nm_platform_link_get_name (ifindex);
+	ifname = nm_platform_link_get_name (platform, ifindex);
 	if (!ifname)
 		return FALSE;
 
-	peer_ifindex_stat = ethtool_get_stringset_index (ifname, ETH_SS_STATS, "peer_ifindex");
-	if (peer_ifindex_stat == -1) {
-		debug ("%s: peer_ifindex ethtool stat does not exist?", ifname);
+	peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (ifname);
+	if (peer_ifindex <= 0)
 		return FALSE;
-	}
 
-	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
-	stats->cmd = ETHTOOL_GSTATS;
-	stats->n_stats = peer_ifindex_stat + 1;
-	if (!ethtool_get (ifname, stats))
-		return FALSE;
-
-	props->peer = stats->data[peer_ifindex_stat];
+	props->peer = peer_ifindex;
 	return TRUE;
 }
 
 static gboolean
-tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
+tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props)
 {
-	const char *ifname;
 	char *path, *val;
 	gboolean success = TRUE;
 
@@ -2984,16 +3434,15 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 	props->owner = -1;
 	props->group = -1;
 
-	ifname = nm_platform_link_get_name (ifindex);
 	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 (path);
+	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);
+		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
 		if (errno)
 			success = FALSE;
 		g_free (val);
@@ -3001,10 +3450,10 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 		success = FALSE;
 
 	path = g_strdup_printf ("/sys/class/net/%s/group", ifname);
-	val = nm_platform_sysctl_get (path);
+	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);
+		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
 		if (errno)
 			success = FALSE;
 		g_free (val);
@@ -3012,12 +3461,12 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 		success = FALSE;
 
 	path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname);
-	val = nm_platform_sysctl_get (path);
+	val = nm_platform_sysctl_get (platform, path);
 	g_free (path);
 	if (val) {
 		gint64 flags;
 
-		flags = nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
+		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;
@@ -3035,6 +3484,12 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 	return success;
 }
 
+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);
+}
+
 static const struct nla_policy macvlan_info_policy[IFLA_MACVLAN_MAX + 1] = {
 	[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
 #ifdef MACVLAN_FLAG_NOPROMISC
@@ -3084,20 +3539,20 @@ static gboolean
 macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
 	int err;
+	const NMPObject *obj;
 
-	rtnllink = link_get (platform, ifindex);
-	if (!rtnllink)
+	obj = cache_lookup_link (platform, ifindex);
+	if (!obj)
 		return FALSE;
 
-	props->parent_ifindex = rtnl_link_get_link (rtnllink);
+	props->parent_ifindex = obj->link.parent;
 
-	err = nm_rtnl_link_parse_info_data (priv->nlh, ifindex,
-	                                    macvlan_info_data_parser, props);
+	err = _nl_link_parse_info_data (priv->nlh, ifindex,
+	                                macvlan_info_data_parser, props);
 	if (err != 0) {
 		warning ("(%s) could not read properties: %s",
-		         rtnl_link_get_name (rtnllink), nl_geterror (err));
+		         obj->link.name, nl_geterror (err));
 	}
 	return (err == 0);
 }
@@ -3224,11 +3679,11 @@ vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperti
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int err;
 
-	err = nm_rtnl_link_parse_info_data (priv->nlh, ifindex,
-	                                    vxlan_info_data_parser, props);
+	err = _nl_link_parse_info_data (priv->nlh, ifindex,
+	                                vxlan_info_data_parser, props);
 	if (err != 0) {
-		warning ("(%s) could not read properties: %s",
-		         link_get_name (platform, ifindex), nl_geterror (err));
+		warning ("(%s) could not read vxlan properties: %s",
+		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
 	}
 	return (err == 0);
 }
@@ -3278,11 +3733,11 @@ gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int err;
 
-	err = nm_rtnl_link_parse_info_data (priv->nlh, ifindex,
-	                                    gre_info_data_parser, props);
+	err = _nl_link_parse_info_data (priv->nlh, ifindex,
+	                                gre_info_data_parser, props);
 	if (err != 0) {
-		warning ("(%s) could not read properties: %s",
-		         link_get_name (platform, ifindex), nl_geterror (err));
+		warning ("(%s) could not read gre properties: %s",
+		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
 	}
 	return (err == 0);
 }
@@ -3295,26 +3750,25 @@ wifi_get_wifi_data (NMPlatform *platform, int ifindex)
 
 	wifi_data = g_hash_table_lookup (priv->wifi_data, GINT_TO_POINTER (ifindex));
 	if (!wifi_data) {
-		NMLinkType type;
-		const char *ifname;
-
-		type = link_get_type (platform, ifindex);
-		ifname = link_get_name (platform, ifindex);
-
-		if (type == NM_LINK_TYPE_WIFI)
-			wifi_data = wifi_utils_init (ifname, ifindex, TRUE);
-		else if (type == NM_LINK_TYPE_OLPC_MESH) {
-			/* The kernel driver now uses nl80211, but we force use of WEXT because
-			 * the cfg80211 interactions are not quite ready to support access to
-			 * mesh control through nl80211 just yet.
-			 */
+		const NMPlatformLink *pllink;
+
+		pllink = nm_platform_link_get (platform, ifindex);
+		if (pllink) {
+			if (pllink->type == NM_LINK_TYPE_WIFI)
+				wifi_data = wifi_utils_init (pllink->name, ifindex, TRUE);
+			else if (pllink->type == NM_LINK_TYPE_OLPC_MESH) {
+				/* The kernel driver now uses nl80211, but we force use of WEXT because
+				 * the cfg80211 interactions are not quite ready to support access to
+				 * mesh control through nl80211 just yet.
+				 */
 #if HAVE_WEXT
-			wifi_data = wifi_wext_init (ifname, ifindex, FALSE);
+				wifi_data = wifi_wext_init (pllink->name, ifindex, FALSE);
 #endif
-		}
+			}
 
-		if (wifi_data)
-			g_hash_table_insert (priv->wifi_data, GINT_TO_POINTER (ifindex), wifi_data);
+			if (wifi_data)
+				g_hash_table_insert (priv->wifi_data, GINT_TO_POINTER (ifindex), wifi_data);
+		}
 	}
 
 	return wifi_data;
@@ -3460,18 +3914,11 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 static gboolean
 link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 {
-	NMLinkType type = link_get_type (platform, ifindex);
-
-	if (type == NM_LINK_TYPE_ETHERNET) {
-		struct ethtool_wolinfo wol;
+	NMLinkType type = nm_platform_link_get_type (platform, ifindex);
 
-		memset (&wol, 0, sizeof (wol));
-		wol.cmd = ETHTOOL_GWOL;
-		if (!ethtool_get (link_get_name (platform, ifindex), &wol))
-			return FALSE;
-
-		return wol.wolopts != 0;
-	} else if (type == NM_LINK_TYPE_WIFI) {
+	if (type == NM_LINK_TYPE_ETHERNET)
+		return nmp_utils_ethtool_get_wake_on_lan (nm_platform_link_get_name (platform, ifindex));
+	else if (type == NM_LINK_TYPE_WIFI) {
 		WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
 
 		if (!wifi_data)
@@ -3482,55 +3929,45 @@ link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 		return FALSE;
 }
 
-/******************************************************************/
-
 static gboolean
-_address_match (struct rtnl_addr *addr, int family, int ifindex)
+link_get_driver_info (NMPlatform *platform,
+                      int ifindex,
+                      char **out_driver_name,
+                      char **out_driver_version,
+                      char **out_fw_version)
 {
-	g_return_val_if_fail (addr, FALSE);
-
-	return rtnl_addr_get_family (addr) == family &&
-	       (ifindex == 0 || rtnl_addr_get_ifindex (addr) == ifindex);
+	return nmp_utils_ethtool_get_driver_info (nm_platform_link_get_name (platform, ifindex),
+	                                          out_driver_name,
+	                                          out_driver_version,
+	                                          out_fw_version);
 }
 
+/******************************************************************/
+
 static GArray *
-ip4_address_get_all (NMPlatform *platform, int ifindex)
+ipx_address_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *addresses;
-	NMPlatformIP4Address address;
-	struct nl_object *object;
 
-	addresses = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Address));
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
 
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		if (_address_match ((struct rtnl_addr *) object, AF_INET, ifindex)) {
-			if (init_ip4_address (&address, (struct rtnl_addr *) object))
-				g_array_append_val (addresses, address);
-		}
-	}
+	return nmp_cache_lookup_multi_to_array (priv->cache,
+	                                        obj_type,
+	                                        nmp_cache_id_init_addrroute_visible_by_ifindex (NMP_CACHE_ID_STATIC,
+	                                                                                        obj_type,
+	                                                                                        ifindex));
+}
 
-	return addresses;
+static GArray *
+ip4_address_get_all (NMPlatform *platform, int ifindex)
+{
+	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS);
 }
 
 static GArray *
 ip6_address_get_all (NMPlatform *platform, int ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *addresses;
-	NMPlatformIP6Address address;
-	struct nl_object *object;
-
-	addresses = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Address));
-
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		if (_address_match ((struct rtnl_addr *) object, AF_INET6, ifindex)) {
-			if (init_ip6_address (&address, (struct rtnl_addr *) object))
-				g_array_append_val (addresses, address);
-		}
-	}
-
-	return addresses;
+	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
 }
 
 #define IPV4LL_NETWORK (htonl (0xA9FE0000L))
@@ -3543,7 +3980,8 @@ ip4_is_link_local (const struct in_addr *src)
 }
 
 static struct nl_object *
-build_rtnl_addr (int family,
+build_rtnl_addr (NMPlatform *platform,
+                 int family,
                  int ifindex,
                  gconstpointer addr,
                  gconstpointer peer_addr,
@@ -3553,10 +3991,10 @@ build_rtnl_addr (int family,
                  guint flags,
                  const char *label)
 {
-	auto_nl_object struct rtnl_addr *rtnladdr = _nm_rtnl_addr_alloc (ifindex);
+	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 = _nm_nl_addr_build (family, addr, addrlen);
+	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (family, addr, addrlen);
 	int nle;
 
 	/* IP address */
@@ -3568,7 +4006,7 @@ build_rtnl_addr (int family,
 
 	/* Tighten scope (IPv4 only) */
 	if (family == AF_INET && ip4_is_link_local (addr))
-		rtnl_addr_set_scope (rtnladdr, rtnl_str2scope ("link"));
+		rtnl_addr_set_scope (rtnladdr, RT_SCOPE_LINK);
 
 	/* IPv4 Broadcast address */
 	if (family == AF_INET) {
@@ -3576,14 +4014,14 @@ build_rtnl_addr (int family,
 		auto_nl_addr struct nl_addr *bcaddr = NULL;
 
 		bcast = *((in_addr_t *) addr) | ~nm_utils_ip4_prefix_to_netmask (plen);
-		bcaddr = _nm_nl_addr_build (family, &bcast, addrlen);
+		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 = _nm_nl_addr_build (family, peer_addr, addrlen);
+		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) {
@@ -3593,19 +4031,15 @@ build_rtnl_addr (int family,
 		}
 	}
 
-	rtnl_addr_set_prefixlen (rtnladdr, plen);
-	if (lifetime) {
-		/* note that here we set the relative timestamps (ticking from *now*).
-		 * Contrary to the rtnl_addr objects from our cache, which have absolute
-		 * timestamps (see _rtnl_addr_hack_lifetimes_rel_to_abs()).
-		 *
-		 * This is correct, because we only use build_rtnl_addr() for
-		 * add_object(), delete_object() and cache search (ip_address_exists). */
+	_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) && !check_support_kernel_extended_ifa_flags (nm_platform_get ())) {
+		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
@@ -3625,6 +4059,48 @@ build_rtnl_addr (int family,
 	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,
@@ -3635,10 +4111,16 @@ ip4_address_add (NMPlatform *platform,
                  guint32 preferred,
                  const char *label)
 {
-	return add_object (platform, build_rtnl_addr (AF_INET, ifindex, &addr,
-	                                              peer_addr ? &peer_addr : NULL,
-	                                              plen, lifetime, preferred, 0,
-	                                              label));
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = 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);
 }
 
 static gboolean
@@ -3651,169 +4133,114 @@ ip6_address_add (NMPlatform *platform,
                  guint32 preferred,
                  guint flags)
 {
-	return add_object (platform, build_rtnl_addr (AF_INET6, ifindex, &addr,
-	                                              IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
-	                                              plen, lifetime, preferred, flags,
-	                                              NULL));
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = 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);
 }
 
 static gboolean
 ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
-	return delete_object (platform, build_rtnl_addr (AF_INET, ifindex, &addr, peer_address ? &peer_address : NULL, plen, 0, 0, 0, NULL), TRUE);
+	NMPObject obj_needle;
+
+	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);
 }
 
 static gboolean
 ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	return delete_object (platform, build_rtnl_addr (AF_INET6, ifindex, &addr, NULL, plen, 0, 0, 0, NULL), TRUE);
+	NMPObject obj_needle;
+
+	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
+	return do_delete_object (platform, &obj_needle, NULL);
 }
 
-static gboolean
-ip_address_exists (NMPlatform *platform, int family, int ifindex, gconstpointer addr, int plen)
+static const NMPlatformIP4Address *
+ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 {
-	auto_nl_object struct nl_object *object = build_rtnl_addr (family, ifindex, addr, NULL, plen, 0, 0, 0, NULL);
-	auto_nl_object struct nl_object *cached_object = nl_cache_search (choose_cache (platform, object), object);
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-	return !!cached_object;
+	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);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip4_address;
+	return NULL;
 }
 
-static gboolean
-ip4_address_exists (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)
 {
-	return ip_address_exists (platform, AF_INET, ifindex, &addr, plen);
-}
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-static gboolean
-ip6_address_exists (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
-{
-	return ip_address_exists (platform, AF_INET6, ifindex, &addr, plen);
+	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);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip6_address;
+	return NULL;
 }
 
-static gboolean
-ip4_check_reinstall_device_route (NMPlatform *platform, int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric)
+/******************************************************************/
+
+static GArray *
+ipx_route_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type, NMPlatformGetRouteFlags flags)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPlatformIP4Address addr_candidate;
-	NMPlatformIP4Route route_candidate;
-	struct nl_object *object;
-	guint32 device_network;
-
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		if (_address_match ((struct rtnl_addr *) object, AF_INET, 0)) {
-			if (init_ip4_address (&addr_candidate, (struct rtnl_addr *) object))
-				if (   addr_candidate.plen == address->plen
-				    && addr_candidate.address == address->address) {
-					/* If we already have the same address installed on any interface,
-					 * we back off.
-					 * Perform this check first, as we expect to have significantly less
-					 * addresses to search. */
-					return FALSE;
-				}
-		}
-	}
+	NMPCacheId cache_id;
+	const NMPlatformIPRoute *const* routes;
+	GArray *array;
+	const NMPClass *klass;
+	gboolean with_rtprot_kernel;
+	guint i, len;
 
-	device_network = nm_utils_ip4_address_clear_host_address (address->address, address->plen);
-
-	for (object = nl_cache_get_first (priv->route_cache); object; object = nl_cache_get_next (object)) {
-		if (_route_match ((struct rtnl_route *) object, AF_INET, 0, TRUE)) {
-			if (init_ip4_route (&route_candidate, (struct rtnl_route *) object)) {
-				if (   route_candidate.network == device_network
-				    && route_candidate.plen == address->plen
-				    && (   route_candidate.metric == 0
-				        || route_candidate.metric == device_route_metric)) {
-					/* There is already any route with metric 0 or the metric we want to install
-					 * for the same subnet. */
-					return FALSE;
-				}
-			}
-		}
-	}
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	return TRUE;
-}
+	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
+		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
 
-/******************************************************************/
+	klass = nmp_class_from_type (obj_type);
 
-static gboolean
-_route_match (struct rtnl_route *rtnlroute, int family, int ifindex, gboolean include_proto_kernel)
-{
-	struct rtnl_nexthop *nexthop;
+	nmp_cache_id_init_routes_visible (&cache_id,
+	                                  obj_type,
+	                                  NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT),
+	                                  NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT),
+	                                  ifindex);
 
-	g_return_val_if_fail (rtnlroute, FALSE);
+	routes = (const NMPlatformIPRoute *const*) nmp_cache_lookup_multi (priv->cache, &cache_id, &len);
 
-	if (rtnl_route_get_type (rtnlroute) != RTN_UNICAST ||
-	    rtnl_route_get_table (rtnlroute) != RT_TABLE_MAIN ||
-	    rtnl_route_get_tos (rtnlroute) != 0 ||
-	    (!include_proto_kernel && rtnl_route_get_protocol (rtnlroute) == RTPROT_KERNEL) ||
-	    rtnl_route_get_family (rtnlroute) != family ||
-	    rtnl_route_get_nnexthops (rtnlroute) != 1 ||
-	    rtnl_route_get_flags (rtnlroute) & RTM_F_CLONED)
-		return FALSE;
+	array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
 
-	if (ifindex == 0)
-		return TRUE;
+	with_rtprot_kernel = NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL);
+	for (i = 0; i < len; i++) {
+		nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (routes[i])) == klass);
 
-	nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
-	return rtnl_route_nh_get_ifindex (nexthop) == ifindex;
+		if (   with_rtprot_kernel
+		    || routes[i]->source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
+			g_array_append_vals (array, routes[i], 1);
+	}
+	return array;
 }
 
 static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mode)
+ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *routes;
-	NMPlatformIP4Route route;
-	struct nl_object *object;
-
-	g_return_val_if_fail (NM_IN_SET (mode, NM_PLATFORM_GET_ROUTE_MODE_ALL, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT), NULL);
-
-	routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-
-	for (object = nl_cache_get_first (priv->route_cache); object; object = nl_cache_get_next (object)) {
-		if (_route_match ((struct rtnl_route *) object, AF_INET, ifindex, FALSE)) {
-			if (_rtnl_route_is_default ((struct rtnl_route *) object)) {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT)
-					continue;
-			} else {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT)
-					continue;
-			}
-			if (init_ip4_route (&route, (struct rtnl_route *) object))
-				g_array_append_val (routes, route);
-		}
-	}
-
-	return routes;
+	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ROUTE, flags);
 }
 
 static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mode)
+ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *routes;
-	NMPlatformIP6Route route;
-	struct nl_object *object;
-
-	g_return_val_if_fail (NM_IN_SET (mode, NM_PLATFORM_GET_ROUTE_MODE_ALL, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT), NULL);
-
-	routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
-
-	for (object = nl_cache_get_first (priv->route_cache); object; object = nl_cache_get_next (object)) {
-		if (_route_match ((struct rtnl_route *) object, AF_INET6, ifindex, FALSE)) {
-			if (_rtnl_route_is_default ((struct rtnl_route *) object)) {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT)
-					continue;
-			} else {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT)
-					continue;
-			}
-			if (init_ip6_route (&route, (struct rtnl_route *) object))
-				g_array_append_val (routes, route);
-		}
-	}
-
-	return routes;
+	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags);
 }
 
 static void
@@ -3846,26 +4273,26 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
 	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 ? _nm_nl_addr_build (family, gateway, addrlen) : NULL;
-	auto_nl_addr struct nl_addr *pref_src_nl = pref_src ? _nm_nl_addr_build (family, pref_src, addrlen) : 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 = _nm_nl_addr_build (family, network_clean, plen ? addrlen : 0);
+	dst = _nl_addr_build (family, network_clean, plen ? addrlen : 0);
 	nl_addr_set_prefixlen (dst, plen);
 
-	rtnlroute = _nm_rtnl_route_alloc ();
+	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, source_to_rtprot (source));
+	rtnl_route_set_protocol (rtnlroute, _nm_ip_config_source_to_rtprot (source));
 
-	nexthop = _nm_rtnl_route_nh_alloc ();
+	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);
@@ -3879,111 +4306,113 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
 	return (struct nl_object *) rtnlroute;
 }
 
-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)
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
 {
-	return add_object (platform, build_rtnl_route (AF_INET, ifindex, source, &network, plen, &gateway, pref_src ? &pref_src : NULL, metric, mss));
+	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);
 }
 
-static gboolean
-ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
-               struct in6_addr network, int plen, struct in6_addr gateway,
-               guint32 metric, guint32 mss)
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
 {
-	metric = nm_utils_ip6_route_metric_normalize (metric);
+	const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj;
 
-	return add_object (platform, build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss));
+	return build_rtnl_route (AF_INET6,
+	                         obj->ifindex,
+	                         obj->source,
+	                         &obj->network,
+	                         obj->plen,
+	                         &obj->gateway,
+	                         NULL,
+	                         obj->metric,
+	                         obj->mss);
 }
 
-static struct rtnl_route *
-route_search_cache (struct nl_cache *cache, int family, int ifindex, const void *network, int plen, guint32 metric)
+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)
 {
-	guint32 network_clean[4], dst_clean[4];
-	struct nl_object *object;
-
-	clear_host_address (family, network, plen, network_clean);
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = NULL;
 
-	for (object = nl_cache_get_first (cache); object; object = nl_cache_get_next (object)) {
-		struct nl_addr *dst;
-		struct rtnl_route *rtnlroute = (struct rtnl_route *) object;
-
-		if (!_route_match (rtnlroute, family, ifindex, FALSE))
-			continue;
-
-		if (metric != rtnl_route_get_priority (rtnlroute))
-			continue;
-
-		dst = rtnl_route_get_dst (rtnlroute);
-		if (   !dst
-		    || nl_addr_get_family (dst) != family
-		    || nl_addr_get_prefixlen (dst) != plen)
-			continue;
-
-		/* plen = 0 means all host bits, so all bits should be cleared.
-		 * Likewise if the binary address is not present or all zeros.
-		 */
-		if (plen == 0 || nl_addr_iszero (dst))
-			memset (dst_clean, 0, sizeof (dst_clean));
-		else
-			clear_host_address (family, nl_addr_get_binary_addr (dst), plen, dst_clean);
-
-		if (memcmp (dst_clean, network_clean,
-		            family == AF_INET ? sizeof (guint32) : sizeof (struct in6_addr)) != 0)
-			continue;
-
-		rtnl_route_get (rtnlroute);
-		return rtnlroute;
-	}
-	return 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);
 }
 
 static gboolean
-refresh_route (NMPlatform *platform, int family, int ifindex, const void *network, int plen, guint32 metric)
+ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
+               struct in6_addr network, int plen, struct in6_addr gateway,
+               guint32 metric, guint32 mss)
 {
-	struct nl_cache *cache;
-	auto_nl_object struct rtnl_route *cached_object = NULL;
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = NULL;
 
-	cache = choose_cache_by_type (platform, family == AF_INET ? OBJECT_TYPE_IP4_ROUTE : OBJECT_TYPE_IP6_ROUTE);
-	cached_object = route_search_cache (cache, family, ifindex, network, plen, metric);
+	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	if (cached_object)
-		return refresh_object (platform, (struct nl_object *) cached_object, TRUE, NM_PLATFORM_REASON_INTERNAL);
-	return TRUE;
+	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);
 }
 
 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;
-	struct rtnl_route *cached_object;
-	struct nl_object *route = build_rtnl_route (AF_INET, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN, &network, plen, &gateway, NULL, metric, 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;
-	struct nl_cache *cache;
+	const NMPObject *obj;
+	NMPObject obj_needle;
 
-	g_return_val_if_fail (route, FALSE);
+	g_return_val_if_fail (nlo, FALSE);
 
-	cache = choose_cache_by_type (platform, OBJECT_TYPE_IP4_ROUTE);
+	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
 
 	if (metric == 0) {
 		/* Deleting an IPv4 route with metric 0 does not only delete an exectly matching route.
 		 * If no route with metric 0 exists, it might delete another route to the same destination.
 		 * For nm_platform_ip4_route_delete() we don't want this semantic.
 		 *
-		 * Instead, re-fetch the route from kernel, and if that fails, there is nothing to do.
-		 * On success, there is still a race that we might end up deleting the wrong route. */
-		if (!refresh_object (platform, (struct nl_object *) route, FALSE, _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)) {
-			rtnl_route_put ((struct rtnl_route *) route);
-			return TRUE;
-		}
+		 * 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);
 	}
 
-	/* when deleting an IPv4 route, several fields of the provided route must match.
-	 * Lookup in the cache so that we hopefully get the right values. */
-	cached_object = (struct rtnl_route *) nl_cache_search (cache, route);
-	if (!cached_object)
-		cached_object = route_search_cache (cache, AF_INET, ifindex, &network, plen, metric);
+	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
@@ -4000,8 +4429,8 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 		 * So, this workaround is only needed unless we have NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE.
 		 **/
 
-		if (cached_object)
-			scope = rtnl_route_get_scope (cached_object);
+		if (obj)
+			scope = nm_platform_route_scope_inv (obj->ip4_route.scope_inv);
 
 		if (scope == RT_SCOPE_NOWHERE) {
 			/* If we would set the scope to RT_SCOPE_NOWHERE, libnl would guess the scope.
@@ -4010,7 +4439,7 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 			scope = RT_SCOPE_UNIVERSE;
 		}
 	}
-	rtnl_route_set_scope ((struct rtnl_route *) route, scope);
+	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. */
@@ -4022,179 +4451,51 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 	 * pref_src: NULL
 	 */
 
-	rtnl_route_put (cached_object);
-	return delete_object (platform, route, FALSE) && refresh_route (platform, AF_INET, ifindex, &network, plen, metric);
+	return do_delete_object (platform, &obj_needle, nlo);
 }
 
 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;
 
 	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	return delete_object (platform, build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0), FALSE) &&
-	    refresh_route (platform, AF_INET6, ifindex, &network, plen, metric);
-}
-
-static gboolean
-ip_route_exists (NMPlatform *platform, int family, int ifindex, gpointer network, int plen, guint32 metric)
-{
-	auto_nl_object struct nl_object *object = build_rtnl_route (family, ifindex,
-	                                                            NM_IP_CONFIG_SOURCE_UNKNOWN,
-	                                                            network, plen, NULL, NULL, metric, 0);
-	struct nl_cache *cache = choose_cache (platform, object);
-	auto_nl_object struct nl_object *cached_object = nl_cache_search (cache, object);
-
-	if (!cached_object)
-		cached_object = (struct nl_object *) route_search_cache (cache, family, ifindex, network, plen, metric);
-	return !!cached_object;
-}
-
-static gboolean
-ip4_route_exists (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
-{
-	return ip_route_exists (platform, AF_INET, ifindex, &network, plen, metric);
-}
-
-static gboolean
-ip6_route_exists (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
-{
-	metric = nm_utils_ip6_route_metric_normalize (metric);
-
-	return ip_route_exists (platform, AF_INET6, ifindex, &network, plen, metric);
-}
-
-/******************************************************************/
+	nlo = build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0);
 
-/* Initialize the link cache while ensuring all links are of AF_UNSPEC,
- * family (even though the kernel might set AF_BRIDGE for bridges).
- * See also: _nl_link_family_unset() */
-static void
-init_link_cache (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_object *object = NULL;
+	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
 
-	rtnl_link_alloc_cache (priv->nlh, AF_UNSPEC, &priv->link_cache);
-
-	do {
-		for (object = nl_cache_get_first (priv->link_cache); object; object = nl_cache_get_next (object)) {
-			if (rtnl_link_get_family ((struct rtnl_link *)object) != AF_UNSPEC)
-				break;
-		}
-
-		if (object) {
-			/* A non-AF_UNSPEC object encoutnered */
-			struct nl_object *existing;
-
-			nl_object_get (object);
-			nl_cache_remove (object);
-			rtnl_link_set_family ((struct rtnl_link *)object, AF_UNSPEC);
-			existing = nl_cache_search (priv->link_cache, object);
-			if (existing)
-				nl_object_put (existing);
-			else
-				nl_cache_add (priv->link_cache, object);
-			nl_object_put (object);
-		}
-	} while (object);
+	return do_delete_object (platform, &obj_needle, nlo);
 }
 
-/* Calls announce_object with appropriate arguments for all objects
- * which are not coherent between old and new caches and deallocates
- * the old cache. */
-static void
-cache_announce_changes (NMPlatform *platform, struct nl_cache *new, struct nl_cache *old)
+static const NMPlatformIP4Route *
+ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
-	struct nl_object *object;
-
-	if (!old)
-		return;
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-	for (object = nl_cache_get_first (new); object; object = nl_cache_get_next (object)) {
-		struct nl_object *cached_object = nm_nl_cache_search (old, object);
-
-		if (cached_object) {
-			ObjectType type = object_type_from_nl_object (object);
-			if (nm_nl_object_diff (type, object, cached_object))
-				announce_object (platform, object, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_EXTERNAL);
-			nl_object_put (cached_object);
-		} else
-			announce_object (platform, object, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL);
-	}
-	for (object = nl_cache_get_first (old); object; object = nl_cache_get_next (object)) {
-		struct nl_object *cached_object = nm_nl_cache_search (new, object);
-		if (cached_object)
-			nl_object_put (cached_object);
-		else
-			announce_object (platform, object, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL);
-	}
-
-	nl_cache_free (old);
+	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);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip4_route;
+	return NULL;
 }
 
-/* The cache should always avoid containing objects not handled by NM, like
- * e.g. addresses of the AF_PHONET family. */
-static void
-cache_remove_unknown (struct nl_cache *cache)
+static const NMPlatformIP6Route *
+ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	GPtrArray *objects_to_remove = NULL;
-	struct nl_object *object;
-
-	for (object = nl_cache_get_first (cache); object; object = nl_cache_get_next (object)) {
-		if (object_type_from_nl_object (object) == OBJECT_TYPE_UNKNOWN) {
-			if (!objects_to_remove)
-				objects_to_remove = g_ptr_array_new_with_free_func ((GDestroyNotify) nl_object_put);
-			nl_object_get (object);
-			g_ptr_array_add (objects_to_remove, object);
-		}
-	}
-
-	if (objects_to_remove) {
-		guint i;
-
-		for (i = 0; i < objects_to_remove->len; i++)
-			nl_cache_remove (g_ptr_array_index (objects_to_remove, i));
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-		g_ptr_array_free (objects_to_remove, TRUE);
-	}
-}
-
-/* Creates and populates the netlink object caches. Called upon platform init and
- * when we run out of sync (out of buffer space, netlink congestion control). In case
- * the caches already exist, it finds changed, added and removed objects, announces
- * them and destroys the old caches. */
-static void
-cache_repopulate_all (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_cache *old_link_cache = priv->link_cache;
-	struct nl_cache *old_address_cache = priv->address_cache;
-	struct nl_cache *old_route_cache = priv->route_cache;
-	struct nl_object *object;
-
-	debug ("platform: %spopulate platform cache", old_link_cache ? "re" : "");
-
-	/* Allocate new netlink caches */
-	init_link_cache (platform);
-	rtnl_addr_alloc_cache (priv->nlh, &priv->address_cache);
-	rtnl_route_alloc_cache (priv->nlh, AF_UNSPEC, 0, &priv->route_cache);
-	g_assert (priv->link_cache && priv->address_cache && priv->route_cache);
-
-	/* Remove all unknown objects from the caches */
-	cache_remove_unknown (priv->link_cache);
-	cache_remove_unknown (priv->address_cache);
-	cache_remove_unknown (priv->route_cache);
-
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		_rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object);
-	}
+	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	/* Make sure all changes we've missed are announced. */
-	cache_announce_changes (platform, priv->link_cache, old_link_cache);
-	cache_announce_changes (platform, priv->address_cache, old_address_cache);
-	cache_announce_changes (platform, priv->route_cache, old_route_cache);
+	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);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip6_route;
+	return NULL;
 }
 
 /******************************************************************/
@@ -4208,10 +4509,9 @@ verify_source (struct nl_msg *msg, gpointer user_data)
 {
 	struct ucred *creds = nlmsg_get_creds (msg);
 
-	if (!creds || creds->pid || creds->uid || creds->gid) {
+	if (!creds || creds->pid) {
 		if (creds)
-			warning ("netlink: received non-kernel message (pid %d uid %d gid %d)",
-					creds->pid, creds->uid, creds->gid);
+			warning ("netlink: received non-kernel message (pid %d)", creds->pid);
 		else
 			warning ("netlink: received message without credentials");
 		return NL_STOP;
@@ -4225,35 +4525,42 @@ event_handler (GIOChannel *channel,
                GIOCondition io_condition,
                gpointer user_data)
 {
-	NMPlatform *platform = NM_PLATFORM (user_data);
+	delayed_action_handle_all (NM_PLATFORM (user_data), TRUE);
+	return TRUE;
+}
+
+static gboolean
+event_handler_read_netlink_one (NMPlatform *platform)
+{
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int nle;
 
 	nle = nl_recvmsgs_default (priv->nlh_event);
+
+	/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
+	if (nle == 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+		nle = -NLE_AGAIN;
+
 	if (nle < 0)
 		switch (nle) {
+		case -NLE_AGAIN:
+			return FALSE;
 		case -NLE_DUMP_INTR:
-			/* this most likely happens due to our request (RTM_GETADDR, AF_INET6, NLM_F_DUMP)
-			 * to detect support for support_kernel_extended_ifa_flags. This is not critical
-			 * and can happen easily. */
 			debug ("Uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 			break;
 		case -NLE_NOMEM:
 			warning ("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_socket_modify_cb (priv->nlh_event, NL_CB_VALID, NL_CB_DEFAULT, NULL, NULL);
-			do {
-				errno = 0;
-
-				nle = nl_recvmsgs_default (priv->nlh_event);
-
-				/* 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_socket_modify_cb (priv->nlh_event, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data);
-			cache_repopulate_all (platform);
+			_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);
@@ -4262,6 +4569,71 @@ event_handler (GIOChannel *channel,
 	return TRUE;
 }
 
+static gboolean
+event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	int r;
+	struct pollfd pfd;
+	gboolean any = FALSE;
+	gint64 timestamp = 0, now;
+	const int TIMEOUT = 250;
+	int timeout = 0;
+	guint32 wait_for_seq = 0;
+
+	while (TRUE) {
+		while (event_handler_read_netlink_one (platform))
+			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);
+			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;
+			}
+
+			/* readjust the wait-time. */
+			timeout = TIMEOUT - (now - timestamp);
+		}
+
+		memset (&pfd, 0, sizeof (pfd));
+		pfd.fd = nl_socket_get_fd (priv->nlh_event);
+		pfd.events = POLLIN;
+		r = poll (&pfd, 1, timeout);
+
+		if (r == 0) {
+			/* timeout. */
+			break;
+		}
+		if (r < 0) {
+			int errsv = errno;
+
+			if (errsv != EINTR) {
+				_LOGE ("read-netlink-all: poll failed with %s", strerror (errsv));
+				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)
 {
@@ -4278,7 +4650,8 @@ setup_socket (gboolean event, gpointer user_data)
 	/* 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_disable_seq_check (sock);
+		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);
@@ -4298,14 +4671,23 @@ setup_socket (gboolean event, gpointer user_data)
 /******************************************************************/
 
 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;
+	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);
+}
+
+static void
 udev_device_added (NMPlatform *platform,
                    GUdevDevice *udev_device)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
 	const char *ifname;
 	int ifindex;
-	gboolean was_announceable = FALSE;
 
 	ifname = g_udev_device_get_name (udev_device);
 	if (!ifname) {
@@ -4329,59 +4711,37 @@ udev_device_added (NMPlatform *platform,
 		return;
 	}
 
-	rtnllink = rtnl_link_get (priv->link_cache, ifindex);
-	if (rtnllink)
-		was_announceable = link_is_announceable (platform, rtnllink);
-
-	g_hash_table_insert (priv->udev_devices, GINT_TO_POINTER (ifindex),
-	                     g_object_ref (udev_device));
+	cache_update_link_udev (platform, ifindex, udev_device);
+}
 
-	/* Announce devices only if they also have been discovered via Netlink. */
-	if (rtnllink && link_is_announceable (platform, rtnllink))
-		announce_object (platform, (struct nl_object *) rtnllink, was_announceable ? NM_PLATFORM_SIGNAL_CHANGED : NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL);
+static gboolean
+_udev_device_removed_match_link (const NMPObject *obj, gpointer udev_device)
+{
+	return obj->_link.udev.device == udev_device;
 }
 
 static void
 udev_device_removed (NMPlatform *platform,
                      GUdevDevice *udev_device)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
 	int ifindex = 0;
-	gboolean was_announceable = FALSE;
 
 	if (g_udev_device_get_property (udev_device, "IFINDEX"))
 		ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
 	else {
-		GHashTableIter iter;
-		gpointer key, value;
+		const NMPObject *obj;
 
-		/* This should not happen, but just to be sure.
-		 * If we can't get IFINDEX, go through the devices and
-		 * compare the pointers.
-		 */
-		g_hash_table_iter_init (&iter, priv->udev_devices);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			if ((GUdevDevice *)value == udev_device) {
-				ifindex = GPOINTER_TO_INT (key);
-				break;
-			}
-		}
+		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+		                                  0, NULL, FALSE, NM_LINK_TYPE_NONE, _udev_device_removed_match_link, udev_device);
+		if (obj)
+			ifindex = obj->link.ifindex;
 	}
 
 	debug ("udev-remove: IFINDEX=%d", ifindex);
 	if (ifindex <= 0)
 		return;
 
-	rtnllink = rtnl_link_get (priv->link_cache, ifindex);
-	if (rtnllink)
-		was_announceable = link_is_announceable (platform, rtnllink);
-
-	g_hash_table_remove (priv->udev_devices, GINT_TO_POINTER (ifindex));
-
-	/* Announce device removal if it is no longer announceable. */
-	if (was_announceable && !link_is_announceable (platform, rtnllink))
-		announce_object (platform, (struct nl_object *) rtnllink, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL);
+	cache_update_link_udev (platform, ifindex, NULL);
 }
 
 static void
@@ -4416,28 +4776,40 @@ handle_udev_event (GUdevClient *client,
 /******************************************************************/
 
 static void
-nm_linux_platform_init (NMLinuxPlatform *platform)
+nm_linux_platform_init (NMLinuxPlatform *self)
 {
+	NMLinuxPlatformPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate);
+
+	self->priv = priv;
+
+	priv->delayed_deletion = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+	                                                (GEqualFunc) nmp_object_id_equal,
+	                                                (GDestroyNotify) nmp_object_unref,
+	                                                (GDestroyNotify) nmp_object_unref);
+	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->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
 }
 
-static gboolean
-setup (NMPlatform *platform)
+static void
+constructed (GObject *_object)
 {
+	NMPlatform *platform = NM_PLATFORM (_object);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const char *udev_subsys[] = { "net", NULL };
-	GUdevEnumerator *enumerator;
-	GList *devices, *iter;
 	int channel_flags;
 	gboolean status;
 	int nle;
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	struct nl_object *object;
-#endif
+	GUdevEnumerator *enumerator;
+	GList *devices, *iter;
+
+	_LOGD ("create");
 
 	/* Initialize netlink socket for requests */
 	priv->nlh = setup_socket (FALSE, platform);
 	g_assert (priv->nlh);
-	debug ("Netlink socket for requests established: %d", nl_socket_get_local_port (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);
@@ -4454,7 +4826,7 @@ setup (NMPlatform *platform)
 	                                 RTNLGRP_IPV4_ROUTE,  RTNLGRP_IPV6_ROUTE,
 	                                 0);
 	g_assert (!nle);
-	debug ("Netlink socket for events established: %d", nl_socket_get_local_port (priv->nlh_event));
+	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));
 
 	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh_event));
 	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
@@ -4465,37 +4837,31 @@ setup (NMPlatform *platform)
 		channel_flags | G_IO_FLAG_NONBLOCK, NULL);
 	g_assert (status);
 	priv->event_id = g_io_add_watch (priv->event_channel,
-		(EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
-		event_handler, platform);
-
-	cache_repopulate_all (platform);
-
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	/* Initial check for user IPv6LL support once the link cache is allocated
-	 * and filled.  If there are no links in the cache yet then we'll check
-	 * when a new link shows up in announce_object().
-	 */
-	object = nl_cache_get_first (priv->link_cache);
-	if (object) {
-		uint8_t mode;
-
-		if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) object, &mode) == 0)
-			priv->support_user_ipv6ll = 1;
-		else
-			priv->support_user_ipv6ll = -1;
-	}
-#else
-	priv->support_user_ipv6ll = -1;
-#endif
+	                                (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
+	                                 event_handler, platform);
 
 	/* Set up udev monitoring */
 	priv->udev_client = g_udev_client_new (udev_subsys);
 	g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (handle_udev_event), platform);
-	priv->udev_devices = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+
+	/* complete construction of the GObject instance before populating the cache. */
+	G_OBJECT_CLASS (nm_linux_platform_parent_class)->constructed (_object);
+
+	_LOGD ("populate platform cache");
+	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);
+
+	delayed_action_handle_all (platform, FALSE);
 
 	/* And read initial device list */
 	enumerator = g_udev_enumerator_new (priv->udev_client);
 	g_udev_enumerator_add_match_subsystem (enumerator, "net");
+
 	g_udev_enumerator_add_match_is_initialized (enumerator);
 
 	devices = g_udev_enumerator_execute (enumerator);
@@ -4505,16 +4871,26 @@ setup (NMPlatform *platform)
 	}
 	g_list_free (devices);
 	g_object_unref (enumerator);
+}
 
-	/* request all IPv6 addresses (hopeing that there is at least one), to check for
-	 * the IFA_FLAGS attribute. */
-	nle = nl_rtgen_request (priv->nlh_event, RTM_GETADDR, AF_INET6, NLM_F_DUMP);
-	if (nle < 0)
-		nm_log_warn (LOGD_PLATFORM, "Netlink error: requesting RTM_GETADDR failed with %s", nl_geterror (nle));
+static void
+dispose (GObject *object)
+{
+	NMPlatform *platform = NM_PLATFORM (object);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
+	_LOGD ("dispose");
 
-	return TRUE;
+	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_clear_pointer (&priv->delayed_deletion, g_hash_table_unref);
+
+	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
 }
 
 static void
@@ -4522,17 +4898,18 @@ nm_linux_platform_finalize (GObject *object)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (object);
 
+	nmp_cache_free (priv->cache);
+
+	g_ptr_array_unref (priv->delayed_action.list_master_connected);
+	g_ptr_array_unref (priv->delayed_action.list_refresh_link);
+
 	/* 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);
-	nl_cache_free (priv->link_cache);
-	nl_cache_free (priv->address_cache);
-	nl_cache_free (priv->route_cache);
 
 	g_object_unref (priv->udev_client);
-	g_hash_table_unref (priv->udev_devices);
 	g_hash_table_unref (priv->wifi_data);
 
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->finalize (object);
@@ -4549,21 +4926,21 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	g_type_class_add_private (klass, sizeof (NMLinuxPlatformPrivate));
 
 	/* virtual methods */
+	object_class->constructed = constructed;
+	object_class->dispose = dispose;
 	object_class->finalize = nm_linux_platform_finalize;
 
-	platform_class->setup = setup;
-
 	platform_class->sysctl_set = sysctl_set;
 	platform_class->sysctl_get = sysctl_get;
 
 	platform_class->link_get = _nm_platform_link_get;
+	platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname;
+	platform_class->link_get_by_address = _nm_platform_link_get_by_address;
 	platform_class->link_get_all = link_get_all;
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
-	platform_class->link_get_ifindex = link_get_ifindex;
-	platform_class->link_get_name = link_get_name;
-	platform_class->link_get_type = link_get_type;
 	platform_class->link_get_type_name = link_get_type_name;
+	platform_class->link_get_unmanaged = link_get_unmanaged;
 
 	platform_class->link_refresh = link_refresh;
 
@@ -4571,28 +4948,26 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_down = link_set_down;
 	platform_class->link_set_arp = link_set_arp;
 	platform_class->link_set_noarp = link_set_noarp;
-	platform_class->link_is_up = link_is_up;
-	platform_class->link_is_connected = link_is_connected;
-	platform_class->link_uses_arp = link_uses_arp;
 
-	platform_class->link_get_user_ipv6ll_enabled = link_get_user_ipv6ll_enabled;
+	platform_class->link_get_udi = link_get_udi;
+	platform_class->link_get_udev_device = link_get_udev_device;
+
 	platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled;
 
-	platform_class->link_get_address = link_get_address;
 	platform_class->link_set_address = link_set_address;
-	platform_class->link_get_mtu = link_get_mtu;
+	platform_class->link_get_permanent_address = link_get_permanent_address;
 	platform_class->link_set_mtu = link_set_mtu;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
 	platform_class->link_get_dev_id = link_get_dev_id;
 	platform_class->link_get_wake_on_lan = link_get_wake_on_lan;
+	platform_class->link_get_driver_info = link_get_driver_info;
 
 	platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
 	platform_class->link_supports_vlans = link_supports_vlans;
 
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
-	platform_class->link_get_master = link_get_master;
 	platform_class->master_set_option = master_set_option;
 	platform_class->master_get_option = master_get_option;
 	platform_class->slave_set_option = slave_set_option;
@@ -4604,6 +4979,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->vlan_set_egress_map = vlan_set_egress_map;
 
 	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;
@@ -4626,26 +5002,27 @@ 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->ip4_address_get = ip4_address_get;
+	platform_class->ip6_address_get = ip6_address_get;
 	platform_class->ip4_address_get_all = ip4_address_get_all;
 	platform_class->ip6_address_get_all = ip6_address_get_all;
 	platform_class->ip4_address_add = ip4_address_add;
 	platform_class->ip6_address_add = ip6_address_add;
 	platform_class->ip4_address_delete = ip4_address_delete;
 	platform_class->ip6_address_delete = ip6_address_delete;
-	platform_class->ip4_address_exists = ip4_address_exists;
-	platform_class->ip6_address_exists = ip6_address_exists;
-
-	platform_class->ip4_check_reinstall_device_route = ip4_check_reinstall_device_route;
 
+	platform_class->ip4_route_get = ip4_route_get;
+	platform_class->ip6_route_get = ip6_route_get;
 	platform_class->ip4_route_get_all = ip4_route_get_all;
 	platform_class->ip6_route_get_all = ip6_route_get_all;
 	platform_class->ip4_route_add = ip4_route_add;
 	platform_class->ip6_route_add = ip6_route_add;
 	platform_class->ip4_route_delete = ip4_route_delete;
 	platform_class->ip6_route_delete = ip6_route_delete;
-	platform_class->ip4_route_exists = ip4_route_exists;
-	platform_class->ip6_route_exists = ip6_route_exists;
 
 	platform_class->check_support_kernel_extended_ifa_flags = check_support_kernel_extended_ifa_flags;
 	platform_class->check_support_user_ipv6ll = check_support_user_ipv6ll;
+
+	platform_class->process_events = process_events;
 }
+
diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h
index 6f2c1993..a9e2cd82 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/platform/nm-linux-platform.h
@@ -32,8 +32,12 @@
 
 /******************************************************************/
 
+struct _NMLinuxPlatformPrivate;
+
 typedef struct {
 	NMPlatform parent;
+
+	struct _NMLinuxPlatformPrivate *priv;
 } NMLinuxPlatform;
 
 typedef struct {
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
new file mode 100644
index 00000000..d3c62bdd
--- /dev/null
+++ b/src/platform/nm-platform-utils.c
@@ -0,0 +1,436 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "nm-platform-utils.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
+#include <linux/mii.h>
+#include <linux/version.h>
+
+#include "gsystem-local-alloc.h"
+#include "nm-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-logging.h"
+
+
+/******************************************************************
+ * ethtool
+ ******************************************************************/
+
+static gboolean
+ethtool_get (const char *name, gpointer edata)
+{
+	struct ifreq ifr;
+	int fd;
+
+	if (!name || !*name)
+		return FALSE;
+
+	memset (&ifr, 0, sizeof (ifr));
+	strncpy (ifr.ifr_name, name, IFNAMSIZ);
+	ifr.ifr_data = edata;
+
+	fd = socket (PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		nm_log_err (LOGD_PLATFORM, "ethtool: Could not open socket.");
+		return FALSE;
+	}
+
+	if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool: Request failed: %s", strerror (errno));
+		close (fd);
+		return FALSE;
+	}
+
+	close (fd);
+	return TRUE;
+}
+
+static int
+ethtool_get_stringset_index (const char *ifname, int stringset_id, const char *string)
+{
+	gs_free struct ethtool_sset_info *info = NULL;
+	gs_free struct ethtool_gstrings *strings = NULL;
+	guint32 len, i;
+
+	info = g_malloc0 (sizeof (*info) + sizeof (guint32));
+	info->cmd = ETHTOOL_GSSET_INFO;
+	info->reserved = 0;
+	info->sset_mask = 1ULL << stringset_id;
+
+	if (!ethtool_get (ifname, info))
+		return -1;
+	if (!info->sset_mask)
+		return -1;
+
+	len = info->data[0];
+
+	strings = g_malloc0 (sizeof (*strings) + len * ETH_GSTRING_LEN);
+	strings->cmd = ETHTOOL_GSTRINGS;
+	strings->string_set = stringset_id;
+	strings->len = len;
+	if (!ethtool_get (ifname, strings))
+		return -1;
+
+	for (i = 0; i < len; i++) {
+		if (!strcmp ((char *) &strings->data[i * ETH_GSTRING_LEN], string))
+			return i;
+	}
+
+	return -1;
+}
+
+gboolean
+nmp_utils_ethtool_get_driver_info (const char *ifname,
+                                   char **out_driver_name,
+                                   char **out_driver_version,
+                                   char **out_fw_version)
+{
+	struct ethtool_drvinfo drvinfo = { 0 };
+
+	if (!ifname)
+		return FALSE;
+
+	drvinfo.cmd = ETHTOOL_GDRVINFO;
+	if (!ethtool_get (ifname, &drvinfo))
+		return FALSE;
+
+	if (out_driver_name)
+		*out_driver_name = g_strdup (drvinfo.driver);
+	if (out_driver_version)
+		*out_driver_version = g_strdup (drvinfo.version);
+	if (out_fw_version)
+		*out_fw_version = g_strdup (drvinfo.fw_version);
+
+	return TRUE;
+}
+
+gboolean
+nmp_utils_ethtool_get_permanent_address (const char *ifname,
+                                         guint8 *buf,
+                                         size_t *length)
+{
+	gs_free struct ethtool_perm_addr *epaddr = NULL;
+
+	if (!ifname)
+		return FALSE;
+
+	epaddr = g_malloc0 (sizeof (*epaddr) + NM_UTILS_HWADDR_LEN_MAX);
+	epaddr->cmd = ETHTOOL_GPERMADDR;
+	epaddr->size = NM_UTILS_HWADDR_LEN_MAX;
+
+	if (!ethtool_get (ifname, epaddr))
+		return FALSE;
+
+	g_assert (epaddr->size <= NM_UTILS_HWADDR_LEN_MAX);
+	memcpy (buf, epaddr->data, epaddr->size);
+	*length = epaddr->size;
+	return TRUE;
+}
+
+gboolean
+nmp_utils_ethtool_supports_carrier_detect (const char *ifname)
+{
+	struct ethtool_cmd edata = { .cmd = ETHTOOL_GLINK };
+
+	/* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we
+	 * assume the device supports carrier-detect, otherwise we assume it
+	 * doesn't.
+	 */
+	return ethtool_get (ifname, &edata);
+}
+
+gboolean
+nmp_utils_ethtool_supports_vlans (const char *ifname)
+{
+	gs_free struct ethtool_gfeatures *features = NULL;
+	int idx, block, bit, size;
+
+	if (!ifname)
+		return FALSE;
+
+	idx = ethtool_get_stringset_index (ifname, ETH_SS_FEATURES, "vlan-challenged");
+	if (idx == -1) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool: vlan-challenged ethtool feature does not exist for %s?", ifname);
+		return FALSE;
+	}
+
+	block = idx /  32;
+	bit = idx % 32;
+	size = block + 1;
+
+	features = g_malloc0 (sizeof (*features) + size * sizeof (struct ethtool_get_features_block));
+	features->cmd = ETHTOOL_GFEATURES;
+	features->size = size;
+
+	if (!ethtool_get (ifname, features))
+		return FALSE;
+
+	return !(features->features[block].active & (1 << bit));
+}
+
+int
+nmp_utils_ethtool_get_peer_ifindex (const char *ifname)
+{
+	gs_free struct ethtool_stats *stats = NULL;
+	int peer_ifindex_stat;
+
+	if (!ifname)
+		return 0;
+
+	peer_ifindex_stat = ethtool_get_stringset_index (ifname, ETH_SS_STATS, "peer_ifindex");
+	if (peer_ifindex_stat == -1) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool: peer_ifindex stat for %s does not exist?", ifname);
+		return FALSE;
+	}
+
+	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
+	stats->cmd = ETHTOOL_GSTATS;
+	stats->n_stats = peer_ifindex_stat + 1;
+	if (!ethtool_get (ifname, stats))
+		return 0;
+
+	return stats->data[peer_ifindex_stat];
+}
+
+gboolean
+nmp_utils_ethtool_get_wake_on_lan (const char *ifname)
+{
+	struct ethtool_wolinfo wol;
+
+	if (!ifname)
+		return FALSE;
+
+	memset (&wol, 0, sizeof (wol));
+	wol.cmd = ETHTOOL_GWOL;
+	if (!ethtool_get (ifname, &wol))
+		return FALSE;
+
+	return wol.wolopts != 0;
+}
+
+gboolean
+nmp_utils_ethtool_get_link_speed (const char *ifname, guint32 *out_speed)
+{
+	struct ethtool_cmd edata = {
+		.cmd = ETHTOOL_GSET,
+	};
+	guint32 speed;
+
+	if (!ethtool_get (ifname, &edata))
+		return FALSE;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+	speed = edata.speed;
+#else
+	speed = ethtool_cmd_speed (&edata);
+#endif
+	if (speed == G_MAXUINT16 || speed == G_MAXUINT32)
+		speed = 0;
+
+	if (out_speed)
+		*out_speed = speed;
+	return TRUE;
+}
+
+/******************************************************************
+ * mii
+ ******************************************************************/
+
+gboolean
+nmp_utils_mii_supports_carrier_detect (const char *ifname)
+{
+	int fd, errsv;
+	struct ifreq ifr;
+	struct mii_ioctl_data *mii;
+	gboolean supports_mii = FALSE;
+
+	if (!ifname)
+		return FALSE;
+
+	fd = socket (PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		nm_log_err (LOGD_PLATFORM, "mii: couldn't open control socket (%s)", ifname);
+		return FALSE;
+	}
+
+	memset (&ifr, 0, sizeof (struct ifreq));
+	strncpy (ifr.ifr_name, ifname, IFNAMSIZ);
+
+	errno = 0;
+	if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) {
+		errsv = errno;
+		nm_log_dbg (LOGD_PLATFORM, "mii: SIOCGMIIPHY failed: %s (%d) (%s)", strerror (errsv), errsv, ifname);
+		goto out;
+	}
+
+	/* If we can read the BMSR register, we assume that the card supports MII link detection */
+	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
+	mii->reg_num = MII_BMSR;
+
+	if (ioctl (fd, SIOCGMIIREG, &ifr) == 0) {
+		nm_log_dbg (LOGD_PLATFORM, "mii: SIOCGMIIREG result 0x%X (%s)", mii->val_out, ifname);
+		supports_mii = TRUE;
+	} else {
+		errsv = errno;
+		nm_log_dbg (LOGD_PLATFORM, "mii: SIOCGMIIREG failed: %s (%d) (%s)", strerror (errsv), errsv, ifname);
+	}
+
+out:
+	close (fd);
+	nm_log_dbg (LOGD_PLATFORM, "mii: MII %s supported (%s)", supports_mii ? "is" : "not", ifname);
+	return supports_mii;
+}
+
+/******************************************************************
+ * udev
+ ******************************************************************/
+
+const char *
+nmp_utils_udev_get_driver (GUdevDevice *device)
+{
+	GUdevDevice *parent = NULL, *grandparent = NULL;
+	const char *driver, *subsys;
+
+	driver = g_udev_device_get_driver (device);
+	if (driver)
+		goto out;
+
+	/* Try the parent */
+	parent = g_udev_device_get_parent (device);
+	if (parent) {
+		driver = g_udev_device_get_driver (parent);
+		if (!driver) {
+			/* Try the grandparent if it's an ibmebus device or if the
+			 * subsys is NULL which usually indicates some sort of
+			 * platform device like a 'gadget' net interface.
+			 */
+			subsys = g_udev_device_get_subsystem (parent);
+			if (   (g_strcmp0 (subsys, "ibmebus") == 0)
+			    || (subsys == NULL)) {
+				grandparent = g_udev_device_get_parent (parent);
+				if (grandparent)
+					driver = g_udev_device_get_driver (grandparent);
+			}
+		}
+	}
+	g_clear_object (&parent);
+	g_clear_object (&grandparent);
+
+out:
+	/* Intern the string so we don't have to worry about memory
+	 * management in NMPlatformLink. */
+	return g_intern_string (driver);
+}
+
+/******************************************************************
+ * utils
+ ******************************************************************/
+
+/**
+ * Takes a pair @timestamp and @duration, and returns the remaining duration based
+ * on the new timestamp @now.
+ */
+guint32
+nmp_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
+                                                guint32 duration,
+                                                guint32 now,
+                                                guint32 padding)
+{
+	gint64 t;
+
+	if (duration == NM_PLATFORM_LIFETIME_PERMANENT)
+		return NM_PLATFORM_LIFETIME_PERMANENT;
+
+	if (timestamp == 0) {
+		/* if the @timestamp is zero, assume it was just left unset and that the relative
+		 * @duration starts counting from @now. This is convenient to construct an address
+		 * and print it in nm_platform_ip4_address_to_string().
+		 *
+		 * In general it does not make sense to set the @duration without anchoring at
+		 * @timestamp because you don't know the absolute expiration time when looking
+		 * at the address at a later moment. */
+		timestamp = now;
+	}
+
+	/* For timestamp > now, just accept it and calculate the expected(?) result. */
+	t = (gint64) timestamp + (gint64) duration - (gint64) now;
+
+	/* Optional padding to avoid potential races. */
+	t += (gint64) padding;
+
+	if (t <= 0)
+		return 0;
+	if (t >= NM_PLATFORM_LIFETIME_PERMANENT)
+		return NM_PLATFORM_LIFETIME_PERMANENT - 1;
+	return t;
+}
+
+gboolean
+nmp_utils_lifetime_get (guint32 timestamp,
+                        guint32 lifetime,
+                        guint32 preferred,
+                        guint32 now,
+                        guint32 padding,
+                        guint32 *out_lifetime,
+                        guint32 *out_preferred)
+{
+	guint32 t_lifetime, t_preferred;
+
+	if (lifetime == 0) {
+		*out_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
+		*out_preferred = NM_PLATFORM_LIFETIME_PERMANENT;
+
+		/* We treat lifetime==0 as permanent addresses to allow easy creation of such addresses
+		 * (without requiring to set the lifetime fields to NM_PLATFORM_LIFETIME_PERMANENT).
+		 * In that case we also expect that the other fields (timestamp and preferred) are left unset. */
+		g_return_val_if_fail (timestamp == 0 && preferred == 0, TRUE);
+	} else {
+		if (!now)
+			now = nm_utils_get_monotonic_timestamp_s ();
+		t_lifetime = nmp_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now, padding);
+		if (!t_lifetime) {
+			*out_lifetime = 0;
+			*out_preferred = 0;
+			return FALSE;
+		}
+		t_preferred = nmp_utils_lifetime_rebase_relative_time_on_now (timestamp, preferred, now, padding);
+
+		*out_lifetime = t_lifetime;
+		*out_preferred = MIN (t_preferred, t_lifetime);
+
+		/* Assert that non-permanent addresses have a (positive) @timestamp. nmp_utils_lifetime_rebase_relative_time_on_now()
+		 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
+		 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
+		 */
+		g_return_val_if_fail (   timestamp != 0
+		                      || (   lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
+		                          && preferred == NM_PLATFORM_LIFETIME_PERMANENT), TRUE);
+		g_return_val_if_fail (t_preferred <= t_lifetime, TRUE);
+	}
+	return TRUE;
+}
+
+
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
new file mode 100644
index 00000000..557de844
--- /dev/null
+++ b/src/platform/nm-platform-utils.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_PLATFORM_UTILS_H__
+#define __NM_PLATFORM_UTILS_H__
+
+#include "config.h"
+
+#include <gudev/gudev.h>
+
+#include "nm-platform.h"
+
+
+const char *nmp_utils_ethtool_get_driver (const char *ifname);
+gboolean nmp_utils_ethtool_supports_carrier_detect (const char *ifname);
+gboolean nmp_utils_ethtool_supports_vlans (const char *ifname);
+int nmp_utils_ethtool_get_peer_ifindex (const char *ifname);
+gboolean nmp_utils_ethtool_get_wake_on_lan (const char *ifname);
+gboolean nmp_utils_ethtool_get_link_speed (const char *ifname, guint32 *out_speed);
+
+gboolean nmp_utils_ethtool_get_driver_info (const char *ifname,
+                                            char **out_driver_name,
+                                            char **out_driver_version,
+                                            char **out_fw_version);
+
+gboolean  nmp_utils_ethtool_get_permanent_address (const char *ifname,
+                                                   guint8 *buf,
+                                                   size_t *length);
+
+
+gboolean nmp_utils_mii_supports_carrier_detect (const char *ifname);
+
+
+const char *nmp_utils_udev_get_driver (GUdevDevice *device);
+
+guint32 nmp_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
+                                                        guint32 duration,
+                                                        guint32 now,
+                                                        guint32 padding);
+
+gboolean nmp_utils_lifetime_get (guint32 timestamp,
+                                 guint32 lifetime,
+                                 guint32 preferred,
+                                 guint32 now,
+                                 guint32 padding,
+                                 guint32 *out_lifetime,
+                                 guint32 *out_preferred);
+
+#endif /* __NM_PLATFORM_UTILS_H__ */
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index ba7708d6..b6d87ff9 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -27,16 +27,59 @@
 #include <arpa/inet.h>
 #include <string.h>
 #include <netlink/route/addr.h>
+#include <netlink/route/rtnl.h>
 
 #include "gsystem-local-alloc.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-platform.h"
+#include "nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-logging.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);
+
+#define _LOG_DOMAIN LOGD_PLATFORM
+#define _LOG_PREFIX_NAME "platform"
+
+#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 = _LOG_PREFIX_NAME; \
+            const void *const __self = (self); \
+            \
+            if (__self && __self != nm_platform_try_get ()) { \
+                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _LOG_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
+#define _LOG_LEVEL_ENABLED(level, domain) \
+    ( nm_logging_enabled ((level), (domain)) )
+
+#ifdef NM_MORE_LOGGING
+#define _LOGT_ENABLED()     _LOG_LEVEL_ENABLED (LOGL_TRACE, _LOG_DOMAIN)
+#define _LOGT(...)          _LOG (LOGL_TRACE, _LOG_DOMAIN, self, __VA_ARGS__)
+#else
+#define _LOGT_ENABLED()     FALSE
+#define _LOGT(...)          G_STMT_START { if (FALSE) { _LOG (LOGL_TRACE, _LOG_DOMAIN, self, __VA_ARGS__); } } G_STMT_END
+#endif
 
-#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
+#define _LOGD(...)      _LOG (LOGL_DEBUG, _LOG_DOMAIN, self, __VA_ARGS__)
+#define _LOGI(...)      _LOG (LOGL_INFO , _LOG_DOMAIN, self, __VA_ARGS__)
+#define _LOGW(...)      _LOG (LOGL_WARN , _LOG_DOMAIN, self, __VA_ARGS__)
+#define _LOGE(...)      _LOG (LOGL_ERR  , _LOG_DOMAIN, self, __VA_ARGS__)
 
 #define NM_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_PLATFORM, NMPlatformPrivate))
 
@@ -54,19 +97,46 @@ enum {
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
+enum {
+	PROP_0,
+	PROP_REGISTER_SINGLETON,
+	LAST_PROP,
+};
+
+typedef struct {
+	gboolean register_singleton;
+} NMPlatformPrivate;
+
 /******************************************************************/
 
 /* Singleton NMPlatform subclass instance and cached class object */
-static NMPlatform *platform = NULL;
-static NMPlatformClass *klass = NULL;
+NM_DEFINE_SINGLETON_INSTANCE (NMPlatform);
+
+NM_DEFINE_SINGLETON_WEAK_REF (NMPlatform);
+
+/* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS()
+ * is only a plain read on the self instance, which the compiler
+ * like can optimize out.
+ */
+#define _CHECK_SELF_VOID(self, klass) \
+	NMPlatformClass *klass; \
+	do { \
+		g_return_if_fail (NM_IS_PLATFORM (self)); \
+		klass = NM_PLATFORM_GET_CLASS (self); \
+		(void) klass; \
+	} while (0)
+
+#define _CHECK_SELF(self, klass, err_val) \
+	NMPlatformClass *klass; \
+	do { \
+		g_return_val_if_fail (NM_IS_PLATFORM (self), err_val); \
+		klass = NM_PLATFORM_GET_CLASS (self); \
+		(void) klass; \
+	} while (0)
 
 /**
  * nm_platform_setup:
- * @type: The #GType for a subclass of #NMPlatform
- *
- * Do not use this function directly, it is intended to be called by
- * NMPlatform subclasses. For the linux platform initialization use
- * nm_linux_platform_setup() instead.
+ * @instance: the #NMPlatform instance
  *
  * Failing to set up #NMPlatform singleton results in a fatal error,
  * as well as trying to initialize it multiple times without freeing
@@ -74,122 +144,82 @@ static NMPlatformClass *klass = NULL;
  *
  * NetworkManager will typically use only one platform object during
  * its run. Test programs might want to switch platform implementations,
- * though. This is done with a combination of nm_platform_free() and
- * nm_*_platform_setup().
+ * though.
  */
 void
-nm_platform_setup (GType type)
+nm_platform_setup (NMPlatform *instance)
 {
-	gboolean status;
+	g_return_if_fail (NM_IS_PLATFORM (instance));
+	g_return_if_fail (!singleton_instance);
 
-	g_assert (platform == NULL);
+	singleton_instance = instance;
 
-	platform = g_object_new (type, NULL);
-	g_assert (NM_IS_PLATFORM (platform));
+	nm_singleton_instance_weak_ref_register ();
 
-	klass = NM_PLATFORM_GET_CLASS (platform);
-	g_assert (klass->setup);
-
-	status = klass->setup (platform);
-	g_assert (status);
-}
-
-/**
- * nm_platform_free:
- *
- * Free #NMPlatform singleton created by nm_*_platform_setup().
- */
-void
-nm_platform_free (void)
-{
-	g_assert (platform);
-
-	g_object_unref (platform);
-	platform = NULL;
+	nm_log_dbg (LOGD_CORE, "setup NMPlatform singleton (%p, %s)", instance,  G_OBJECT_TYPE_NAME (instance));
 }
 
 /**
  * nm_platform_get:
+ * @self: platform instance
  *
  * Retrieve #NMPlatform singleton. Use this whenever you want to connect to
- * #NMPlatform signals. It is an error to call it before nm_*_platform_setup()
- * or after nm_platform_free().
+ * #NMPlatform signals. It is an error to call it before nm_platform_setup().
  *
  * Returns: (transfer none): The #NMPlatform singleton reference.
  */
 NMPlatform *
-nm_platform_get (void)
+nm_platform_get ()
 {
-	g_assert (platform);
+	g_assert (singleton_instance);
 
-	return platform;
+	return singleton_instance;
 }
 
-/******************************************************************/
-
-/**
- * nm_platform_set_error:
- * @error: The error code
- *
- * Convenience function to falsify platform->error. It can be used for example
- * by functions that want to save the error, execute some operations and
- * restore it.
- */
-void nm_platform_set_error (NMPlatformError error)
+NMPlatform *
+nm_platform_try_get (void)
 {
-	platform->error = error;
+	return singleton_instance;
 }
 
-/**
- * nm_platform_get_error:
- *
- * Convenience function to quickly retrieve the error code of the last
- * operation.
- *
- * Returns: Integer error code.
- */
-NMPlatformError
-nm_platform_get_error (void)
-{
-	g_assert (platform);
-
-	return platform->error;
-}
+/******************************************************************/
 
 /**
- * nm_platform_get_error_message:
+ * nm_platform_error_to_string:
+ * @error_code: the error code to stringify.
  *
- * Returns: Static human-readable string for the error. Don't free.
+ * Returns: A string representation of the error.
+ * For negative numbers, this function interprets
+ * the code as -errno.
  */
 const char *
-nm_platform_get_error_msg (void)
-{
-	g_assert (platform);
-
-	switch (platform->error) {
-	case NM_PLATFORM_ERROR_NONE:
-		return "unknown error";
+nm_platform_error_to_string (NMPlatformError error)
+{
+	switch (error) {
+	case NM_PLATFORM_ERROR_SUCCESS:
+		return "success";
+	case NM_PLATFORM_ERROR_BUG:
+		return "bug";
+	case NM_PLATFORM_ERROR_UNSPECIFIED:
+		return "unspecified";
 	case NM_PLATFORM_ERROR_NOT_FOUND:
-		return "object not found";
+		return "not-found";
 	case NM_PLATFORM_ERROR_EXISTS:
-		return "object already exists";
+		return "exists";
 	case NM_PLATFORM_ERROR_WRONG_TYPE:
-		return "object is wrong type";
+		return "wrong-type";
 	case NM_PLATFORM_ERROR_NOT_SLAVE:
-		return "link not a slave";
+		return "not-slave";
 	case NM_PLATFORM_ERROR_NO_FIRMWARE:
-		return "firmware not found";
+		return "no-firmware";
 	default:
-		return "invalid error number";
+		if (error < 0)
+			return g_strerror (- ((int) error));
+		return "unknown";
 	}
 }
 
-static void
-reset_error (void)
-{
-	g_assert (platform);
-	platform->error = NM_PLATFORM_ERROR_NONE;
-}
+/******************************************************************/
 
 #define IFA_F_MANAGETEMPADDR_STR "mngtmpaddr"
 #define IFA_F_NOPREFIXROUTE_STR "noprefixroute"
@@ -208,35 +238,53 @@ nm_platform_check_support_libnl_extended_ifa_flags ()
 }
 
 gboolean
-nm_platform_check_support_kernel_extended_ifa_flags ()
+nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self)
 {
-	g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
+	_CHECK_SELF (self, klass, FALSE);
 
 	if (!klass->check_support_kernel_extended_ifa_flags)
 		return FALSE;
 
-	return klass->check_support_kernel_extended_ifa_flags (platform);
+	return klass->check_support_kernel_extended_ifa_flags (self);
 }
 
 gboolean
-nm_platform_check_support_user_ipv6ll (void)
+nm_platform_check_support_user_ipv6ll (NMPlatform *self)
 {
 	static int supported = -1;
 
-	g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
+	_CHECK_SELF (self, klass, FALSE);
 
 	if (!klass->check_support_user_ipv6ll)
 		return FALSE;
 
 	if (supported < 0)
-		supported = klass->check_support_user_ipv6ll (platform) ? 1 : 0;
+		supported = klass->check_support_user_ipv6ll (self) ? 1 : 0;
 	return !!supported;
 }
 
+/**
+ * nm_platform_process_events:
+ * @self: platform instance
+ *
+ * Process pending events or handle pending delayed-actions.
+ * Effectively, this reads the netlink socket and processes
+ * new netlink messages. Possibly it will raise change signals.
+ */
+void
+nm_platform_process_events (NMPlatform *self)
+{
+	_CHECK_SELF_VOID (self, klass);
+
+	if (klass->process_events)
+		klass->process_events (self);
+}
+
 /******************************************************************/
 
 /**
  * nm_platform_sysctl_set:
+ * @self: platform instance
  * @path: Absolute option path
  * @value: Value to write
  *
@@ -247,23 +295,25 @@ nm_platform_check_support_user_ipv6ll (void)
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_sysctl_set (const char *path, const char *value)
+nm_platform_sysctl_set (NMPlatform *self, const char *path, const char *value)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	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 (platform, path, value);
+	return klass->sysctl_set (self, path, value);
 }
 
 gboolean
-nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value)
+nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value)
 {
 	const char *path;
 	gint64 cur;
 
+	_CHECK_SELF (self, klass, FALSE);
+
 	/* the hop-limit provided via RA is uint8. */
 	if (value > 0xFF)
 		return FALSE;
@@ -273,7 +323,7 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value)
 		return FALSE;
 
 	path = nm_utils_ip6_property_path (iface, "hop_limit");
-	cur = nm_platform_sysctl_get_int_checked (path, 10, 1, G_MAXINT32, -1);
+	cur = nm_platform_sysctl_get_int_checked (self, path, 10, 1, G_MAXINT32, -1);
 
 	/* only allow increasing the hop-limit to avoid DOS by an attacker
 	 * setting a low hop-limit (CVE-2015-2924, rh#1209902) */
@@ -284,7 +334,7 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value)
 		char svalue[20];
 
 		sprintf (svalue, "%d", value);
-		nm_platform_sysctl_set (path, svalue);
+		nm_platform_sysctl_set (self, path, svalue);
 	}
 
 	return TRUE;
@@ -292,23 +342,25 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value)
 
 /**
  * nm_platform_sysctl_get:
+ * @self: platform instance
  * @path: Absolute path to sysctl
  *
  * Returns: (transfer full): Contents of the virtual sysctl file.
  */
 char *
-nm_platform_sysctl_get (const char *path)
+nm_platform_sysctl_get (NMPlatform *self, const char *path)
 {
-	reset_error ();
+	_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 (platform, path);
+	return klass->sysctl_get (self, path);
 }
 
 /**
  * nm_platform_sysctl_get_int32:
+ * @self: platform instance
  * @path: Absolute path to sysctl
  * @fallback: default value, if the content of path could not be read
  * as decimal integer.
@@ -318,13 +370,14 @@ nm_platform_sysctl_get (const char *path)
  * value, on success %errno will be set to zero.
  */
 gint32
-nm_platform_sysctl_get_int32 (const char *path, gint32 fallback)
+nm_platform_sysctl_get_int32 (NMPlatform *self, const char *path, gint32 fallback)
 {
-	return nm_platform_sysctl_get_int_checked (path, 10, G_MININT32, G_MAXINT32, fallback);
+	return nm_platform_sysctl_get_int_checked (self, path, 10, G_MININT32, G_MAXINT32, fallback);
 }
 
 /**
  * nm_platform_sysctl_get_int_checked:
+ * @self: platform instance
  * @path: Absolute path to sysctl
  * @base: base of numeric conversion
  * @min: minimal value that is still valid
@@ -339,22 +392,24 @@ nm_platform_sysctl_get_int32 (const char *path, gint32 fallback)
  * (inclusive) or @fallback.
  */
 gint64
-nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
+nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
 {
 	char *value = NULL;
 	gint32 ret;
 
+	_CHECK_SELF (self, klass, fallback);
+
 	g_return_val_if_fail (path, fallback);
 
 	if (path)
-		value = nm_platform_sysctl_get (path);
+		value = nm_platform_sysctl_get (self, path);
 
 	if (!value) {
 		errno = EINVAL;
 		return fallback;
 	}
 
-	ret = nm_utils_ascii_str_to_int64 (value, base, min, max, fallback);
+	ret = _nm_utils_ascii_str_to_int64 (value, base, min, max, fallback);
 	g_free (value);
 	return ret;
 }
@@ -362,47 +417,25 @@ nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gi
 /******************************************************************/
 
 /**
- * nm_platform_query_devices:
- *
- * Emit #NMPlatform:link-changed ADDED signals for all currently-known links.
- * Should only be called at startup.
- */
-void
-nm_platform_query_devices (void)
-{
-	GArray *links_array;
-	NMPlatformLink *links;
-	int i;
-
-	links_array = nm_platform_link_get_all ();
-	links = (NMPlatformLink *) links_array->data;
-	for (i = 0; i < links_array->len; i++) {
-		g_signal_emit (platform, signals[SIGNAL_LINK_CHANGED], 0,
-		               links[i].ifindex, &links[i], NM_PLATFORM_SIGNAL_ADDED,
-		               NM_PLATFORM_REASON_INTERNAL);
-	}
-	g_array_unref (links_array);
-}
-
-/**
  * nm_platform_link_get_all:
+ * self: platform instance
  *
  * Retrieve a snapshot of configuration for all links at once. The result is
  * owned by the caller and should be freed with g_array_unref().
  */
 GArray *
-nm_platform_link_get_all (void)
+nm_platform_link_get_all (NMPlatform *self)
 {
 	GArray *links, *result;
 	guint i, j, nresult;
 	GHashTable *unseen;
 	NMPlatformLink *item;
 
-	reset_error ();
+	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (klass->link_get_all, NULL);
 
-	links = klass->link_get_all (platform);
+	links = klass->link_get_all (self);
 
 	if (!links || links->len == 0)
 		return links;
@@ -411,11 +444,9 @@ nm_platform_link_get_all (void)
 	for (i = 0; i < links->len; i++) {
 		item = &g_array_index (links, NMPlatformLink, i);
 
-		if (item->ifindex <= 0 || g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex))) {
-			g_warn_if_reached ();
-			item->ifindex = 0;
-			continue;
-		}
+		_LOGT ("link-get: %3d: %s", i, nm_platform_link_to_string (item));
+
+		nm_assert (item->ifindex > 0 && !g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));
 
 		g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL);
 	}
@@ -466,6 +497,8 @@ nm_platform_link_get_all (void)
 			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));
+
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
 			item->ifindex = 0;
@@ -473,10 +506,12 @@ nm_platform_link_get_all (void)
 		}
 
 		if (!found_something) {
-			/* there is a circle, pop the first (remaining) element from the list */
-			g_warn_if_reached ();
+			/* There is a loop, pop the first (remaining) element from the list.
+			 * 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));
+
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
 			item->ifindex = 0;
@@ -491,182 +526,229 @@ nm_platform_link_get_all (void)
 
 /**
  * nm_platform_link_get:
+ * @self: platform instance
  * @ifindex: ifindex of the link
- * @link: (out): output NMPlatformLink structure.
  *
- * If a link with given @ifindex exists, fill the given NMPlatformLink
- * structure.
+ * Lookup the internal NMPlatformLink object.
  *
- * Returns: %TRUE, if such a link exists, %FALSE otherwise.
- * If the link does not exist, the content of @link is undefined.
+ * Returns: %NULL, if such a link exists or the internal
+ * platform link object. Do not modify the returned value.
+ * Also, be aware that any subsequent platform call might
+ * invalidated/modify the returned instance.
  **/
-gboolean
-nm_platform_link_get (int ifindex, NMPlatformLink *link)
+const NMPlatformLink *
+nm_platform_link_get (NMPlatform *self, int ifindex)
 {
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (link, FALSE);
+	_CHECK_SELF (self, klass, NULL);
+
+	if (ifindex > 0)
+		return klass->link_get (self, ifindex);
+	return NULL;
+}
+
+/**
+ * nm_platform_link_get_by_ifname:
+ * @self: platform instance
+ * @ifname: the ifname
+ *
+ * Returns: the first #NMPlatformLink instance with the given name.
+ **/
+const NMPlatformLink *
+nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname)
+{
+	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (klass->link_get, FALSE);
-	return !!klass->link_get (platform, ifindex, link);
+	if (ifname && *ifname)
+		return klass->link_get_by_ifname (self, ifname);
+	return NULL;
+}
+
+/**
+ * nm_platform_link_get_by_address:
+ * @self: platform instance
+ * @address: a pointer to the binary hardware address
+ * @length: the size of @address in bytes
+ *
+ * Returns: the first #NMPlatformLink object with a matching
+ * address.
+ **/
+const NMPlatformLink *
+nm_platform_link_get_by_address (NMPlatform *self,
+                                 gconstpointer address,
+                                 size_t length)
+{
+	_CHECK_SELF (self, klass, NULL);
+
+	g_return_val_if_fail (length == 0 || address, NULL);
+	if (length > 0) {
+		if (length > NM_UTILS_HWADDR_LEN_MAX)
+			g_return_val_if_reached (NULL);
+		return klass->link_get_by_address (self, address, length);
+	}
+	return NULL;
+}
+
+static NMPlatformError
+_link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, NMPlatformLink *out_link)
+{
+	const NMPlatformLink *pllink;
+
+	pllink = nm_platform_link_get_by_ifname (self, name);
+	if (pllink) {
+		gboolean wrong_type;
+
+		wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type;
+		_LOGD ("link: skip adding link due to existing interface '%s' of type %s%s%s",
+		       name,
+		       nm_link_type_to_string (pllink->type),
+		       wrong_type ? ", expected " : "",
+		       wrong_type ? nm_link_type_to_string (type) : "");
+		if (out_link)
+			*out_link = *pllink;
+		if (wrong_type)
+			return NM_PLATFORM_ERROR_WRONG_TYPE;
+		return NM_PLATFORM_ERROR_EXISTS;
+	}
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 /**
  * nm_platform_link_add:
+ * @self: platform instance
  * @name: Interface name
  * @type: Interface type
  * @address: (allow-none): set the mac address of the link
  * @address_len: the length of the @address
+ * @out_link: on success, the link object
  *
- * Add a software interface. Sets platform->error to NM_PLATFORM_ERROR_EXISTS
- * if interface is already already exists.  Any link-changed ADDED signal will be
- * emitted directly, before this function finishes.
+ * Add a software interface.  If the interface already exists and is of type
+ * @type, return NM_PLATFORM_ERROR_EXISTS and returns the link
+ * in @out_link.  If the interface already exists and is not of type @type,
+ * return NM_PLATFORM_ERROR_WRONG_TYPE.
+ *
+ * Any link-changed ADDED signal will be emitted directly, before this
+ * function finishes.
+ *
+ * Returns: the error reason or NM_PLATFORM_ERROR_SUCCESS.
  */
-static gboolean
-nm_platform_link_add (const char *name, NMLinkType type, const void *address, size_t address_len)
+static NMPlatformError
+nm_platform_link_add (NMPlatform *self,
+                      const char *name,
+                      NMLinkType type,
+                      const void *address,
+                      size_t address_len,
+                      NMPlatformLink *out_link)
 {
-	reset_error ();
+	NMPlatformError plerr;
 
-	g_return_val_if_fail (name, FALSE);
-	g_return_val_if_fail (klass->link_add, FALSE);
-	g_return_val_if_fail ( (address != NULL) ^ (address_len == 0) , FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	if (nm_platform_link_exists (name)) {
-		debug ("link: already exists");
-		platform->error = NM_PLATFORM_ERROR_EXISTS;
-		return FALSE;
-	}
+	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);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
 
-	return klass->link_add (platform, name, type, address, address_len);
+	_LOGD ("link: adding %s '%s'", nm_link_type_to_string (type), name);
+	if (!klass->link_add (self, name, type, address, address_len, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 /**
  * nm_platform_dummy_add:
+ * @self: platform instance
  * @name: New interface name
+ * @out_link: on success, the link object
  *
  * Create a software ethernet-like interface
  */
-gboolean
-nm_platform_dummy_add (const char *name)
-{
-	g_return_val_if_fail (name, FALSE);
-
-	debug ("link: adding dummy '%s'", name);
-	return nm_platform_link_add (name, NM_LINK_TYPE_DUMMY, NULL, 0);
-}
-
-/**
- * nm_platform_link_exists:
- * @name: Interface name
- *
- * Returns: %TRUE if an interface of this name exists, %FALSE otherwise.
- */
-gboolean
-nm_platform_link_exists (const char *name)
+NMPlatformError
+nm_platform_dummy_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
 {
-	int ifindex = nm_platform_link_get_ifindex (name);
-
-	reset_error();
-	return ifindex > 0;
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, 0, out_link);
 }
 
 /**
  * nm_platform_link_delete:
+ * @self: platform instance
  * @ifindex: Interface index
- *
- * Delete a software interface. Sets platform->error to
- * NM_PLATFORM_ERROR_NOT_FOUND if ifindex not available.
  */
 gboolean
-nm_platform_link_delete (int ifindex)
+nm_platform_link_delete (NMPlatform *self, int ifindex)
 {
-	const char *name;
+	const NMPlatformLink *pllink;
 
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->link_delete, FALSE);
-
-	name = nm_platform_link_get_name (ifindex);
-
-	if (!name)
+	pllink = nm_platform_link_get (self, ifindex);
+	if (!pllink)
 		return FALSE;
 
-	debug ("link: deleting '%s' (%d)", name, ifindex);
-	return klass->link_delete (platform, ifindex);
+	_LOGD ("link: deleting '%s' (%d)", pllink->name, ifindex);
+	return klass->link_delete (self, ifindex);
 }
 
 /**
  * nm_platform_link_get_index:
+ * @self: platform instance
  * @name: Interface name
  *
  * Returns: The interface index corresponding to the given interface name
  * or 0. Inteface name is owned by #NMPlatform, don't free it.
  */
 int
-nm_platform_link_get_ifindex (const char *name)
+nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
 {
-	int ifindex;
-
-	reset_error ();
+	const NMPlatformLink *pllink;
 
-	g_return_val_if_fail (name, 0);
-	g_return_val_if_fail (klass->link_get_ifindex, 0);
-
-	ifindex = klass->link_get_ifindex (platform, name);
-
-	if (!ifindex) {
-		debug ("link not found: %s", name);
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-	}
-
-	return ifindex;
+	pllink = nm_platform_link_get_by_ifname (self, name);
+	return pllink ? pllink->ifindex : 0;
 }
 
 /**
  * nm_platform_link_get_name:
+ * @self: platform instance
  * @name: Interface name
  *
  * Returns: The interface name corresponding to the given interface index
  * or %NULL.
  */
 const char *
-nm_platform_link_get_name (int ifindex)
+nm_platform_link_get_name (NMPlatform *self, int ifindex)
 {
-	const char *name;
-
-	reset_error ();
-
-	g_return_val_if_fail (klass->link_get_name, NULL);
+	const NMPlatformLink *pllink;
 
-	name = klass->link_get_name (platform, ifindex);
-
-	if (!name) {
-		debug ("link not found: %d", ifindex);
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		return FALSE;
-	}
+	_CHECK_SELF (self, klass, NULL);
 
-	return name;
+	pllink = nm_platform_link_get (self, ifindex);
+	return pllink ? pllink->name : NULL;
 }
 
 /**
  * nm_platform_link_get_type:
+ * @self: platform instance
  * @ifindex: Interface index.
  *
  * Returns: Link type constant as defined in nm-platform.h. On error,
  * NM_LINK_TYPE_NONE is returned.
  */
 NMLinkType
-nm_platform_link_get_type (int ifindex)
+nm_platform_link_get_type (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	const NMPlatformLink *pllink;
 
-	g_return_val_if_fail (klass->link_get_type, NM_LINK_TYPE_NONE);
+	_CHECK_SELF (self, klass, NM_LINK_TYPE_NONE);
 
-	return klass->link_get_type (platform, ifindex);
+	pllink = nm_platform_link_get (self, ifindex);
+	return pllink ? pllink->type : NM_LINK_TYPE_NONE;
 }
 
 /**
  * nm_platform_link_get_type_name:
+ * @self: platform instance
  * @ifindex: Interface index.
  *
  * Returns: A string describing the type of link. In some cases this
@@ -674,113 +756,200 @@ nm_platform_link_get_type (int ifindex)
  * other cases it may not. On error, %NULL is returned.
  */
 const char *
-nm_platform_link_get_type_name (int ifindex)
+nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (klass->link_get_type_name, NULL);
 
-	return klass->link_get_type_name (platform, ifindex);
+	return klass->link_get_type_name (self, ifindex);
+}
+
+/**
+ * nm_platform_link_get_unmanaged:
+ * @self: platform instance
+ * @ifindex: interface index
+ * @unmanaged: management status (in case %TRUE is returned)
+ *
+ * Returns: %TRUE if platform overrides NM default-unmanaged status,
+ * %FALSE otherwise (with @unmanaged unmodified).
+ */
+gboolean
+nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanaged)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	if (klass->link_get_unmanaged)
+		return klass->link_get_unmanaged (self, ifindex, unmanaged);
+	return FALSE;
 }
 
 /**
  * nm_platform_link_is_software:
+ * @self: platform instance
  * @ifindex: Interface index.
  *
  * Returns: %TRUE if ifindex belongs to a software interface, not backed by
  * a physical device.
  */
 gboolean
-nm_platform_link_is_software (int ifindex)
+nm_platform_link_is_software (NMPlatform *self, int ifindex)
 {
-	return (nm_platform_link_get_type (ifindex) & 0x10000);
+	return (nm_platform_link_get_type (self, ifindex) & 0x10000);
 }
 
 /**
  * nm_platform_link_supports_slaves:
+ * @self: platform instance
  * @ifindex: Interface index.
  *
  * Returns: %TRUE if ifindex belongs to an interface capable of enslaving
  * other interfaces.
  */
 gboolean
-nm_platform_link_supports_slaves (int ifindex)
+nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
 {
-	return (nm_platform_link_get_type (ifindex) & 0x20000);
+	return (nm_platform_link_get_type (self, ifindex) & 0x20000);
 }
 
 /**
  * nm_platform_link_refresh:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Reload the cache for ifindex synchronously.
  */
 gboolean
-nm_platform_link_refresh (int ifindex)
+nm_platform_link_refresh (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
 	if (klass->link_refresh)
-		return klass->link_refresh (platform, ifindex);
+		return klass->link_refresh (self, ifindex);
 
 	return TRUE;
 }
 
+static guint32
+_link_get_flags (NMPlatform *self, int ifindex)
+{
+	const NMPlatformLink *pllink;
+
+	pllink = nm_platform_link_get (self, ifindex);
+	return pllink ? pllink->flags : IFF_NOARP;
+}
+
 /**
  * nm_platform_link_is_up:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Check if the interface is up.
  */
 gboolean
-nm_platform_link_is_up (int ifindex)
+nm_platform_link_is_up (NMPlatform *self, int ifindex)
 {
-	reset_error ();
-
-	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_is_up, FALSE);
+	_CHECK_SELF (self, klass, FALSE);
 
-	return klass->link_is_up (platform, ifindex);
+	return NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_UP);
 }
 
 /**
  * nm_platform_link_is_connected:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Check if the interface is connected.
  */
 gboolean
-nm_platform_link_is_connected (int ifindex)
+nm_platform_link_is_connected (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	const NMPlatformLink *pllink;
 
-	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_is_connected, FALSE);
+	_CHECK_SELF (self, klass, FALSE);
 
-	return klass->link_is_connected (platform, ifindex);
+	pllink = nm_platform_link_get (self, ifindex);
+	return pllink ? pllink->connected : FALSE;
 }
 
 /**
  * nm_platform_link_uses_arp:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Check if the interface is configured to use ARP.
  */
 gboolean
-nm_platform_link_uses_arp (int ifindex)
+nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
+
+	return !NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_NOARP);
+}
+
+/**
+ * nm_platform_link_get_ipv6_token:
+ * @self: platform instance
+ * @ifindex: Interface index
+ * @iid: Tokenized interface identifier
+ *
+ * Returns IPv6 tokenized interface identifier. If the platform or OS doesn't
+ * support IPv6 tokenized interface identifiers, or the token is not set
+ * this call will fail and return %FALSE.
+ *
+ * Returns: %TRUE a tokenized identifier was available
+ */
+gboolean
+nm_platform_link_get_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId *iid)
+{
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_uses_arp, FALSE);
+	g_return_val_if_fail (iid, FALSE);
+
+#if HAVE_LIBNL_INET6_TOKEN
+	{
+		const NMPlatformLink *pllink;
 
-	return klass->link_uses_arp (platform, ifindex);
+		pllink = nm_platform_link_get (self, ifindex);
+		if (pllink && pllink->inet6_token.is_valid) {
+			*iid = pllink->inet6_token.iid;
+			return TRUE;
+		}
+	}
+#endif
+	return FALSE;
+}
+
+const char *
+nm_platform_link_get_udi (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex >= 0, NULL);
+
+	if (klass->link_get_udi)
+		return klass->link_get_udi (self, ifindex);
+	return NULL;
+}
+
+GObject *
+nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex >= 0, NULL);
+
+	if (klass->link_get_udev_device)
+		return klass->link_get_udev_device (self, ifindex);
+	return NULL;
 }
 
 /**
  * nm_platform_link_get_user_ip6vll_enabled:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Check whether NM handles IPv6LL address creation for the link.  If the
@@ -790,20 +959,28 @@ nm_platform_link_uses_arp (int ifindex)
  * Returns: %TRUE if NM handles the IPv6LL address for @ifindex
  */
 gboolean
-nm_platform_link_get_user_ipv6ll_enabled (int ifindex)
+nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_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_get_user_ipv6ll_enabled)
-		return klass->link_get_user_ipv6ll_enabled (platform, ifindex);
+#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
 	return FALSE;
 }
 
+
 /**
  * nm_platform_link_set_user_ip6vll_enabled:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Set whether NM handles IPv6LL address creation for the link.  If the
@@ -813,189 +990,248 @@ nm_platform_link_get_user_ipv6ll_enabled (int ifindex)
  * Returns: %TRUE if the operation was successful, %FALSE if it failed.
  */
 gboolean
-nm_platform_link_set_user_ipv6ll_enabled (int ifindex, gboolean enabled)
+nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled)
 {
-	reset_error ();
+	_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 (platform, ifindex, enabled);
+		return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
 	return FALSE;
 }
 
 /**
  * nm_platform_link_set_address:
+ * @self: platform instance
  * @ifindex: Interface index
  * @address: The new MAC address
  *
  * Set interface MAC address.
  */
 gboolean
-nm_platform_link_set_address (int ifindex, gconstpointer address, size_t length)
+nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	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);
 
-	debug ("link: setting '%s' (%d) hardware address", nm_platform_link_get_name (ifindex), ifindex);
-	return klass->link_set_address (platform, ifindex, address, length);
+	_LOGD ("link: setting '%s' (%d) hardware address", nm_platform_link_get_name (self, ifindex), ifindex);
+	return klass->link_set_address (self, ifindex, address, length);
 }
 
 /**
  * nm_platform_link_get_address:
+ * @self: platform instance
  * @ifindex: Interface index
  * @length: Pointer to a variable to store address length
  *
- * Saves interface hardware address to @address.
+ * Returns: the interface hardware address as an array of bytes of
+ * length @length.
  */
 gconstpointer
-nm_platform_link_get_address (int ifindex, size_t *length)
+nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 {
-	reset_error ();
+	const NMPlatformLink *pllink;
+	gconstpointer a = NULL;
+	guint8 l = 0;
+
+	_CHECK_SELF (self, klass, NULL);
 
 	if (length)
 		*length = 0;
 
 	g_return_val_if_fail (ifindex > 0, NULL);
-	g_return_val_if_fail (klass->link_get_address, NULL);
 
-	return klass->link_get_address (platform, ifindex, length);
+	pllink = nm_platform_link_get (self, ifindex);
+	if (pllink && pllink->addr.len > 0) {
+		if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) {
+			if (length)
+				*length = 0;
+			g_return_val_if_reached (NULL);
+		}
+		a = pllink->addr.data;
+		l = pllink->addr.len;
+	}
+
+	if (length)
+		*length = l;
+	return a;
+}
+
+/**
+ * nm_platform_link_get_permanent_address:
+ * @self: platform instance
+ * @ifindex: Interface index
+ * @buf: buffer of at least %NM_UTILS_HWADDR_LEN_MAX bytes, on success
+ * the permanent hardware address
+ * @length: Pointer to a variable to store address length
+ *
+ * Returns: %TRUE on success, %FALSE on failure to read the permanent hardware
+ * address.
+ */
+gboolean
+nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	if (length)
+		*length = 0;
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (buf, FALSE);
+	g_return_val_if_fail (length, FALSE);
+
+	if (klass->link_get_permanent_address)
+		return klass->link_get_permanent_address (self, ifindex, buf, length);
+	return FALSE;
 }
 
 gboolean
-nm_platform_link_supports_carrier_detect (int ifindex)
+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 (platform, ifindex);
+	return klass->link_supports_carrier_detect (self, ifindex);
 }
 
 gboolean
-nm_platform_link_supports_vlans (int ifindex)
+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 (platform, ifindex);
+	return klass->link_supports_vlans (self, ifindex);
 }
 
 /**
  * nm_platform_link_set_up:
+ * @self: platform instance
  * @ifindex: Interface index
+ * @out_no_firmware: (allow-none): if the failure reason is due to missing firmware.
  *
  * Bring the interface up.
  */
 gboolean
-nm_platform_link_set_up (int ifindex)
+nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmware)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (klass->link_set_up, FALSE);
 
-	debug ("link: setting up '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
-	return klass->link_set_up (platform, ifindex);
+	_LOGD ("link: setting up '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
+	return klass->link_set_up (self, ifindex, out_no_firmware);
 }
 
 /**
  * nm_platform_link_set_down:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Take the interface down.
  */
 gboolean
-nm_platform_link_set_down (int ifindex)
+nm_platform_link_set_down (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (klass->link_set_down, FALSE);
 
-	debug ("link: setting down '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
-	return klass->link_set_down (platform, ifindex);
+	_LOGD ("link: setting down '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
+	return klass->link_set_down (self, ifindex);
 }
 
 /**
  * nm_platform_link_set_arp:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Enable ARP on the interface.
  */
 gboolean
-nm_platform_link_set_arp (int ifindex)
+nm_platform_link_set_arp (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (klass->link_set_arp, FALSE);
 
-	debug ("link: setting arp '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
-	return klass->link_set_arp (platform, ifindex);
+	_LOGD ("link: setting arp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
+	return klass->link_set_arp (self, ifindex);
 }
 
 /**
  * nm_platform_link_set_noarp:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Disable ARP on the interface.
  */
 gboolean
-nm_platform_link_set_noarp (int ifindex)
+nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (klass->link_set_noarp, FALSE);
 
-	debug ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
-	return klass->link_set_noarp (platform, ifindex);
+	_LOGD ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
+	return klass->link_set_noarp (self, ifindex);
 }
 
 /**
  * nm_platform_link_set_mtu:
+ * @self: platform instance
  * @ifindex: Interface index
  * @mtu: The new MTU value
  *
  * Set interface MTU.
  */
 gboolean
-nm_platform_link_set_mtu (int ifindex, guint32 mtu)
+nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	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);
 
-	debug ("link: setting '%s' (%d) mtu %"G_GUINT32_FORMAT, nm_platform_link_get_name (ifindex), ifindex, mtu);
-	return klass->link_set_mtu (platform, ifindex, mtu);
+	_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);
 }
 
 /**
  * nm_platform_link_get_mtu:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Returns: MTU value for the interface or 0 on error.
  */
 guint32
-nm_platform_link_get_mtu (int ifindex)
+nm_platform_link_get_mtu (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	const NMPlatformLink *pllink;
 
-	g_return_val_if_fail (ifindex >= 0, 0);
-	g_return_val_if_fail (klass->link_get_mtu, 0);
+	_CHECK_SELF (self, klass, 0);
 
-	return klass->link_get_mtu (platform, ifindex);
+	pllink = nm_platform_link_get (self, ifindex);
+	return pllink ? pllink->mtu : 0;
 }
 
 /**
  * nm_platform_link_get_physical_port_id:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * The physical port ID, if present, indicates some unique identifier of
@@ -1008,18 +1244,20 @@ nm_platform_link_get_mtu (int ifindex)
  * or if the interface has no physical port ID.
  */
 char *
-nm_platform_link_get_physical_port_id (int ifindex)
+nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex >= 0, NULL);
-	g_return_val_if_fail (klass->link_get_physical_port_id, NULL);
 
-	return klass->link_get_physical_port_id (platform, ifindex);
+	if (klass->link_get_physical_port_id)
+		return klass->link_get_physical_port_id (self, ifindex);
+	return NULL;
 }
 
 /**
  * nm_platform_link_get_dev_id:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * In contrast to the physical device ID (which indicates which parent a
@@ -1030,233 +1268,277 @@ nm_platform_link_get_physical_port_id (int ifindex)
  * interface has no device ID.
  */
 guint
-nm_platform_link_get_dev_id (int ifindex)
+nm_platform_link_get_dev_id (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, 0);
 
 	g_return_val_if_fail (ifindex >= 0, 0);
-	g_return_val_if_fail (klass->link_get_dev_id, 0);
 
-	return klass->link_get_dev_id (platform, ifindex);
+	if (klass->link_get_dev_id)
+		return klass->link_get_dev_id (self, ifindex);
+	return 0;
 }
 
 /**
  * nm_platform_link_get_wake_onlan:
+ * @self: platform instance
  * @ifindex: Interface index
  *
  * Returns: the "Wake-on-LAN" status for @ifindex.
  */
 gboolean
-nm_platform_link_get_wake_on_lan (int ifindex)
+nm_platform_link_get_wake_on_lan (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+
+	if (klass->link_get_wake_on_lan)
+		return klass->link_get_wake_on_lan (self, ifindex);
+	return FALSE;
+}
+
+/**
+ * nm_platform_link_get_driver_info:
+ * @self: platform instance
+ * @ifindex: Interface index
+ * @out_driver_name: (transfer full): on success, the driver name if available
+ * @out_driver_version: (transfer full): on success, the driver version if available
+ * @out_fw_version: (transfer full): on success, the firmware version if available
+ *
+ * Returns: %TRUE on success (though @out_driver_name, @out_driver_version and
+ * @out_fw_version can be %NULL if no information was available), %FALSE on
+ * failure.
+ */
+gboolean
+nm_platform_link_get_driver_info (NMPlatform *self,
+                                  int ifindex,
+                                  char **out_driver_name,
+                                  char **out_driver_version,
+                                  char **out_fw_version)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_get_wake_on_lan, FALSE);
+	g_return_val_if_fail (klass->link_get_driver_info, FALSE);
 
-	return klass->link_get_wake_on_lan (platform, ifindex);
+	return klass->link_get_driver_info (self,
+	                                    ifindex,
+	                                    out_driver_name,
+	                                    out_driver_version,
+	                                    out_fw_version);
 }
 
 /**
  * nm_platform_link_enslave:
+ * @self: platform instance
  * @master: Interface index of the master
  * @slave: Interface index of the slave
  *
  * Enslave @slave to @master.
  */
 gboolean
-nm_platform_link_enslave (int master, int slave)
+nm_platform_link_enslave (NMPlatform *self, int master, int slave)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_assert (platform);
 	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);
 
-	debug ("link: enslaving '%s' (%d) to master '%s' (%d)",
-		nm_platform_link_get_name (slave), slave,
-		nm_platform_link_get_name (master), master);
-	return klass->link_enslave (platform, master, slave);
+	_LOGD ("link: enslaving '%s' (%d) to master '%s' (%d)",
+	       nm_platform_link_get_name (self, slave), slave,
+	       nm_platform_link_get_name (self, master), master);
+	return klass->link_enslave (self, master, slave);
 }
 
 /**
  * nm_platform_link_release:
+ * @self: platform instance
  * @master: Interface index of the master
  * @slave: Interface index of the slave
  *
  * Release @slave from @master.
  */
 gboolean
-nm_platform_link_release (int master, int slave)
+nm_platform_link_release (NMPlatform *self, int master, int slave)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_assert (platform);
 	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 (slave) != master) {
-		platform->error = NM_PLATFORM_ERROR_NOT_SLAVE;
+	if (nm_platform_link_get_master (self, slave) != master)
 		return FALSE;
-	}
 
-	debug ("link: releasing '%s' (%d) from master '%s' (%d)",
-		nm_platform_link_get_name (slave), slave,
-		nm_platform_link_get_name (master), master);
-	return klass->link_release (platform, master, slave);
+	_LOGD ("link: releasing '%s' (%d) from master '%s' (%d)",
+	       nm_platform_link_get_name (self, slave), slave,
+	       nm_platform_link_get_name (self, master), master);
+	return klass->link_release (self, master, slave);
 }
 
 /**
  * nm_platform_link_get_master:
+ * @self: platform instance
  * @slave: Interface index of the slave.
  *
  * Returns: Interfase index of the slave's master.
  */
 int
-nm_platform_link_get_master (int slave)
+nm_platform_link_get_master (NMPlatform *self, int slave)
 {
-	reset_error ();
+	const NMPlatformLink *pllink;
+
+	_CHECK_SELF (self, klass, 0);
 
-	g_assert (platform);
 	g_return_val_if_fail (slave >= 0, FALSE);
-	g_return_val_if_fail (klass->link_get_master, FALSE);
 
-	if (!nm_platform_link_get_name (slave)) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		return 0;
-	}
-	return klass->link_get_master (platform, slave);
+	pllink = nm_platform_link_get (self, slave);
+	return pllink ? pllink->master : 0;
 }
 
 /**
  * nm_platform_bridge_add:
+ * @self: platform instance
  * @name: New interface name
  * @address: (allow-none): set the mac address of the new bridge
  * @address_len: the length of the @address
+ * @out_link: on success, the link object
  *
  * Create a software bridge.
  */
-gboolean
-nm_platform_bridge_add (const char *name, const void *address, size_t address_len)
+NMPlatformError
+nm_platform_bridge_add (NMPlatform *self,
+                        const char *name,
+                        const void *address,
+                        size_t address_len,
+                        NMPlatformLink *out_link)
 {
-	debug ("link: adding bridge '%s'", name);
-	return nm_platform_link_add (name, NM_LINK_TYPE_BRIDGE, address, address_len);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, address, address_len, out_link);
 }
 
 /**
  * nm_platform_bond_add:
+ * @self: platform instance
  * @name: New interface name
+ * @out_link: on success, the link object
  *
  * Create a software bonding device.
  */
-gboolean
-nm_platform_bond_add (const char *name)
+NMPlatformError
+nm_platform_bond_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
 {
-	debug ("link: adding bond '%s'", name);
-	return nm_platform_link_add (name, NM_LINK_TYPE_BOND, NULL, 0);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, 0, out_link);
 }
 
 /**
  * nm_platform_team_add:
+ * @self: platform instance
  * @name: New interface name
+ * @out_link: on success, the link object
  *
  * Create a software teaming device.
  */
-gboolean
-nm_platform_team_add (const char *name)
+NMPlatformError
+nm_platform_team_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
 {
-	debug ("link: adding team '%s'", name);
-	return nm_platform_link_add (name, NM_LINK_TYPE_TEAM, NULL, 0);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, 0, out_link);
 }
 
 /**
  * nm_platform_vlan_add:
+ * @self: platform instance
  * @name: New interface name
  * @vlanid: VLAN identifier
  * @vlanflags: VLAN flags from libnm
+ * @out_link: on success, the link object
  *
  * Create a software VLAN device.
  */
-gboolean
-nm_platform_vlan_add (const char *name, int parent, int vlanid, guint32 vlanflags)
+NMPlatformError
+nm_platform_vlan_add (NMPlatform *self,
+                      const char *name,
+                      int parent,
+                      int vlanid,
+                      guint32 vlanflags,
+                      NMPlatformLink *out_link)
 {
-	reset_error ();
+	NMPlatformError plerr;
 
-	g_assert (platform);
-	g_return_val_if_fail (parent >= 0, FALSE);
-	g_return_val_if_fail (vlanid >= 0, FALSE);
-	g_return_val_if_fail (name, FALSE);
-	g_return_val_if_fail (klass->vlan_add, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	if (nm_platform_link_exists (name)) {
-		debug ("link already exists: %s", name);
-		platform->error = NM_PLATFORM_ERROR_EXISTS;
-		return FALSE;
-	}
+	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)
+		return plerr;
 
-	debug ("link: adding vlan '%s' parent %d vlanid %d vlanflags %x",
-		name, parent, vlanid, vlanflags);
-	return klass->vlan_add (platform, name, parent, vlanid, vlanflags);
+	_LOGD ("link: adding vlan '%s' parent %d vlanid %d vlanflags %x",
+	       name, parent, vlanid, vlanflags);
+	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 gboolean
-nm_platform_master_set_option (int ifindex, const char *option, const char *value)
+nm_platform_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
 {
-	reset_error ();
+	_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 (platform, ifindex, option, value);
+	return klass->master_set_option (self, ifindex, option, value);
 }
 
 char *
-nm_platform_master_get_option (int ifindex, const char *option)
+nm_platform_master_get_option (NMPlatform *self, int ifindex, const char *option)
 {
-	reset_error ();
+	_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 (platform, ifindex, option);
+	return klass->master_get_option (self, ifindex, option);
 }
 
 gboolean
-nm_platform_slave_set_option (int ifindex, const char *option, const char *value)
+nm_platform_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
 {
-	reset_error ();
+	_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 (platform, ifindex, option, value);
+	return klass->slave_set_option (self, ifindex, option, value);
 }
 
 char *
-nm_platform_slave_get_option (int ifindex, const char *option)
+nm_platform_slave_get_option (NMPlatform *self, int ifindex, const char *option)
 {
-	reset_error ();
+	_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 (platform, ifindex, option);
+	return klass->slave_get_option (self, ifindex, option);
 }
 
 gboolean
-nm_platform_vlan_get_info (int ifindex, int *parent, int *vlanid)
+nm_platform_vlan_get_info (NMPlatform *self, int ifindex, int *parent, int *vlanid)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_assert (platform);
 	g_return_val_if_fail (klass->vlan_get_info, FALSE);
 
 	if (parent)
@@ -1264,261 +1546,274 @@ nm_platform_vlan_get_info (int ifindex, int *parent, int *vlanid)
 	if (vlanid)
 		*vlanid = 0;
 
-	if (nm_platform_link_get_type (ifindex) != NM_LINK_TYPE_VLAN)
+	if (nm_platform_link_get_type (self, ifindex) != NM_LINK_TYPE_VLAN)
 		return FALSE;
 
-	return klass->vlan_get_info (platform, ifindex, parent, vlanid);
+	return klass->vlan_get_info (self, ifindex, parent, vlanid);
 }
 
 gboolean
-nm_platform_vlan_set_ingress_map (int ifindex, int from, int to)
+nm_platform_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_assert (platform);
 	g_return_val_if_fail (klass->vlan_set_ingress_map, FALSE);
 
-	debug ("link: setting vlan ingress map for %d from %d to %d", ifindex, from, to);
-	return klass->vlan_set_ingress_map (platform, ifindex, from, 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);
 }
 
 gboolean
-nm_platform_vlan_set_egress_map (int ifindex, int from, int to)
+nm_platform_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_assert (platform);
 	g_return_val_if_fail (klass->vlan_set_egress_map, FALSE);
 
-	debug ("link: setting vlan egress map for %d from %d to %d", ifindex, from, to);
-	return klass->vlan_set_egress_map (platform, ifindex, from, to);
+	_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);
 }
 
-gboolean
-nm_platform_infiniband_partition_add (int parent, int p_key)
+NMPlatformError
+nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, NMPlatformLink *out_link)
 {
-	const char *parent_name;
-	char *name;
+	gs_free char *parent_name = NULL;
+	gs_free char *name = NULL;
+	NMPlatformError plerr;
 
-	reset_error ();
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	g_return_val_if_fail (parent >= 0, FALSE);
-	g_return_val_if_fail (p_key >= 0, FALSE);
-	g_return_val_if_fail (klass->infiniband_partition_add, FALSE);
+	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);
 
-	if (nm_platform_link_get_type (parent) != NM_LINK_TYPE_INFINIBAND) {
-		platform->error = NM_PLATFORM_ERROR_WRONG_TYPE;
-		return FALSE;
-	}
+	parent_name = g_strdup (nm_platform_link_get_name (self, parent));
+	if (   !parent_name
+	    || nm_platform_link_get_type (self, parent) != NM_LINK_TYPE_INFINIBAND)
+		return NM_PLATFORM_ERROR_WRONG_TYPE;
 
-	parent_name = nm_platform_link_get_name (parent);
 	name = g_strdup_printf ("%s.%04x", parent_name, p_key);
-	if (nm_platform_link_exists (name)) {
-		debug ("infiniband: already exists");
-		platform->error = NM_PLATFORM_ERROR_EXISTS;
-		g_free (name);
-		return FALSE;
-	}
-	g_free (name);
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_INFINIBAND, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
 
-	return klass->infiniband_partition_add (platform, parent, p_key);
+	_LOGD ("link: adding infiniband partition %s for parent '%s' (%d), key %d",
+	       name, parent_name, parent, p_key);
+	if (!klass->infiniband_partition_add (self, parent, p_key, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 gboolean
-nm_platform_veth_get_properties (int ifindex, NMPlatformVethProperties *props)
+nm_platform_infiniband_get_info (NMPlatform *self,
+                                 int ifindex,
+                                 int *parent,
+                                 int *p_key,
+                                 const char **mode)
 {
-	reset_error ();
+	_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);
+}
+
+gboolean
+nm_platform_veth_get_properties (NMPlatform *self, int ifindex, NMPlatformVethProperties *props)
+{
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props != NULL, FALSE);
 
-	return klass->veth_get_properties (platform, ifindex, props);
+	return klass->veth_get_properties (self, ifindex, props);
 }
 
 gboolean
-nm_platform_tun_get_properties (int ifindex, NMPlatformTunProperties *props)
+nm_platform_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props != NULL, FALSE);
 
-	return klass->tun_get_properties (platform, ifindex, props);
+	return klass->tun_get_properties (self, ifindex, props);
 }
 
 gboolean
-nm_platform_macvlan_get_properties (int ifindex, NMPlatformMacvlanProperties *props)
+nm_platform_macvlan_get_properties (NMPlatform *self, int ifindex, NMPlatformMacvlanProperties *props)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props != NULL, FALSE);
 
-	return klass->macvlan_get_properties (platform, ifindex, props);
+	return klass->macvlan_get_properties (self, ifindex, props);
 }
 
 gboolean
-nm_platform_vxlan_get_properties (int ifindex, NMPlatformVxlanProperties *props)
+nm_platform_vxlan_get_properties (NMPlatform *self, int ifindex, NMPlatformVxlanProperties *props)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props != NULL, FALSE);
 
-	return klass->vxlan_get_properties (platform, ifindex, props);
+	return klass->vxlan_get_properties (self, ifindex, props);
 }
 
 gboolean
-nm_platform_gre_get_properties (int ifindex, NMPlatformGreProperties *props)
+nm_platform_gre_get_properties (NMPlatform *self, int ifindex, NMPlatformGreProperties *props)
 {
-	reset_error ();
+	_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 (platform, ifindex, props);
+	return klass->gre_get_properties (self, ifindex, props);
 }
 
 gboolean
-nm_platform_wifi_get_capabilities (int ifindex, NMDeviceWifiCapabilities *caps)
+nm_platform_wifi_get_capabilities (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	return klass->wifi_get_capabilities (platform, ifindex, caps);
+	return klass->wifi_get_capabilities (self, ifindex, caps);
 }
 
 gboolean
-nm_platform_wifi_get_bssid (int ifindex, guint8 *bssid)
+nm_platform_wifi_get_bssid (NMPlatform *self, int ifindex, guint8 *bssid)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	return klass->wifi_get_bssid (platform, ifindex, bssid);
+	return klass->wifi_get_bssid (self, ifindex, bssid);
 }
 
 GByteArray *
-nm_platform_wifi_get_ssid (int ifindex)
+nm_platform_wifi_get_ssid (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex > 0, NULL);
 
-	return klass->wifi_get_ssid (platform, ifindex);
+	return klass->wifi_get_ssid (self, ifindex);
 }
 
 guint32
-nm_platform_wifi_get_frequency (int ifindex)
+nm_platform_wifi_get_frequency (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, 0);
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	return klass->wifi_get_frequency (platform, ifindex);
+	return klass->wifi_get_frequency (self, ifindex);
 }
 
 int
-nm_platform_wifi_get_quality (int ifindex)
+nm_platform_wifi_get_quality (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, 0);
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	return klass->wifi_get_quality (platform, ifindex);
+	return klass->wifi_get_quality (self, ifindex);
 }
 
 guint32
-nm_platform_wifi_get_rate (int ifindex)
+nm_platform_wifi_get_rate (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, 0);
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	return klass->wifi_get_rate (platform, ifindex);
+	return klass->wifi_get_rate (self, ifindex);
 }
 
 NM80211Mode
-nm_platform_wifi_get_mode (int ifindex)
+nm_platform_wifi_get_mode (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, NM_802_11_MODE_UNKNOWN);
 
 	g_return_val_if_fail (ifindex > 0, NM_802_11_MODE_UNKNOWN);
 
-	return klass->wifi_get_mode (platform, ifindex);
+	return klass->wifi_get_mode (self, ifindex);
 }
 
 void
-nm_platform_wifi_set_mode (int ifindex, NM80211Mode mode)
+nm_platform_wifi_set_mode (NMPlatform *self, int ifindex, NM80211Mode mode)
 {
-	reset_error ();
+	_CHECK_SELF_VOID (self, klass);
 
 	g_return_if_fail (ifindex > 0);
 
-	klass->wifi_set_mode (platform, ifindex, mode);
+	klass->wifi_set_mode (self, ifindex, mode);
 }
 
 guint32
-nm_platform_wifi_find_frequency (int ifindex, const guint32 *freqs)
+nm_platform_wifi_find_frequency (NMPlatform *self, int ifindex, const guint32 *freqs)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, 0);
 
 	g_return_val_if_fail (ifindex > 0, 0);
 	g_return_val_if_fail (freqs != NULL, 0);
 
-	return klass->wifi_find_frequency (platform, ifindex, freqs);
+	return klass->wifi_find_frequency (self, ifindex, freqs);
 }
 
 void
-nm_platform_wifi_indicate_addressing_running (int ifindex, gboolean running)
+nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gboolean running)
 {
-	reset_error ();
+	_CHECK_SELF_VOID (self, klass);
 
 	g_return_if_fail (ifindex > 0);
 
-	klass->wifi_indicate_addressing_running (platform, ifindex, running);
+	klass->wifi_indicate_addressing_running (self, ifindex, running);
 }
 
 guint32
-nm_platform_mesh_get_channel (int ifindex)
+nm_platform_mesh_get_channel (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, 0);
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	return klass->mesh_get_channel (platform, ifindex);
+	return klass->mesh_get_channel (self, ifindex);
 }
 
 gboolean
-nm_platform_mesh_set_channel (int ifindex, guint32 channel)
+nm_platform_mesh_set_channel (NMPlatform *self, int ifindex, guint32 channel)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	return klass->mesh_set_channel (platform, ifindex, channel);
+	return klass->mesh_set_channel (self, ifindex, channel);
 }
 
 gboolean
-nm_platform_mesh_set_ssid (int ifindex, const guint8 *ssid, gsize len)
+nm_platform_mesh_set_ssid (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (ssid != NULL, FALSE);
 
-	return klass->mesh_set_ssid (platform, ifindex, ssid, len);
+	return klass->mesh_set_ssid (self, ifindex, ssid, len);
 }
 
 #define TO_STRING_DEV_BUF_SIZE (5+15+1)
 static const char *
-_to_string_dev (int ifindex, char *buf, size_t size)
+_to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
 {
 	g_assert (buf && size >= TO_STRING_DEV_BUF_SIZE);
 
 	if (ifindex) {
-		const char *name = ifindex > 0 ? nm_platform_link_get_name (ifindex) : NULL;
+		const char *name = ifindex > 0 && self ? nm_platform_link_get_name (self, ifindex) : NULL;
 		char *buf2;
 
 		strcpy (buf, " dev ");
@@ -1538,29 +1833,30 @@ _to_string_dev (int ifindex, char *buf, size_t size)
 /******************************************************************/
 
 GArray *
-nm_platform_ip4_address_get_all (int ifindex)
+nm_platform_ip4_address_get_all (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_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 (platform, ifindex);
+	return klass->ip4_address_get_all (self, ifindex);
 }
 
 GArray *
-nm_platform_ip6_address_get_all (int ifindex)
+nm_platform_ip6_address_get_all (NMPlatform *self, int ifindex)
 {
-	reset_error ();
+	_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 (platform, ifindex);
+	return klass->ip6_address_get_all (self, ifindex);
 }
 
 gboolean
-nm_platform_ip4_address_add (int ifindex,
+nm_platform_ip4_address_add (NMPlatform *self,
+	                         int ifindex,
                              in_addr_t address,
                              in_addr_t peer_address,
                              int plen,
@@ -1568,7 +1864,7 @@ nm_platform_ip4_address_add (int ifindex,
                              guint32 preferred,
                              const char *label)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen > 0, FALSE);
@@ -1590,13 +1886,14 @@ nm_platform_ip4_address_add (int ifindex,
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
-		debug ("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));
 	}
-	return klass->ip4_address_add (platform, ifindex, address, peer_address, plen, lifetime, preferred, label);
+	return klass->ip4_address_add (self, ifindex, address, peer_address, plen, lifetime, preferred, label);
 }
 
 gboolean
-nm_platform_ip6_address_add (int ifindex,
+nm_platform_ip6_address_add (NMPlatform *self,
+                             int ifindex,
                              struct in6_addr address,
                              struct in6_addr peer_address,
                              int plen,
@@ -1604,7 +1901,7 @@ nm_platform_ip6_address_add (int ifindex,
                              guint32 preferred,
                              guint flags)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen > 0, FALSE);
@@ -1624,74 +1921,72 @@ nm_platform_ip6_address_add (int ifindex,
 		addr.preferred = preferred;
 		addr.flags = flags;
 
-		debug ("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));
 	}
-	return klass->ip6_address_add (platform, ifindex, address, peer_address, plen, lifetime, preferred, flags);
+	return klass->ip6_address_add (self, ifindex, address, peer_address, plen, lifetime, preferred, flags);
 }
 
 gboolean
-nm_platform_ip4_address_delete (int ifindex, in_addr_t address, int plen, in_addr_t peer_address)
+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];
 
-	reset_error ();
+	_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);
 
-	debug ("address: deleting IPv4 address %s/%d, %s%s%sifindex %d%s",
+	_LOGD ("address: deleting IPv4 address %s/%d, %s%s%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 ? ", " : "",
 	       ifindex,
-	       _to_string_dev (ifindex, str_dev, sizeof (str_dev)));
-	return klass->ip4_address_delete (platform, ifindex, address, plen, peer_address);
+	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
 }
 
 gboolean
-nm_platform_ip6_address_delete (int ifindex, struct in6_addr address, int plen)
+nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, int plen)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 
-	reset_error ();
+	_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->ip6_address_delete, FALSE);
 
-	debug ("address: deleting IPv6 address %s/%d, ifindex %d%s",
+	_LOGD ("address: deleting IPv6 address %s/%d, ifindex %d%s",
 	       nm_utils_inet6_ntop (&address, NULL), plen, ifindex,
-	       _to_string_dev (ifindex, str_dev, sizeof (str_dev)));
-	return klass->ip6_address_delete (platform, ifindex, address, plen);
+	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	return klass->ip6_address_delete (self, ifindex, address, plen);
 }
 
-gboolean
-nm_platform_ip4_address_exists (int ifindex, in_addr_t address, int plen)
+const NMPlatformIP4Address *
+nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, int plen)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (plen > 0, FALSE);
-	g_return_val_if_fail (klass->ip4_address_exists, FALSE);
 
-	return klass->ip4_address_exists (platform, ifindex, address, plen);
+	return klass->ip4_address_get (self, ifindex, address, plen);
 }
 
-gboolean
-nm_platform_ip6_address_exists (int ifindex, struct in6_addr address, int plen)
+const NMPlatformIP6Address *
+nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address, int plen)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (plen > 0, FALSE);
-	g_return_val_if_fail (klass->ip6_address_exists, FALSE);
 
-	return klass->ip6_address_exists (platform, ifindex, address, plen);
+	return klass->ip6_address_get (self, ifindex, address, plen);
 }
 
 static gboolean
-array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address)
+array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address, gint64 now, guint32 padding)
 {
 	guint len = addresses ? addresses->len : 0;
 	guint i;
@@ -1699,15 +1994,20 @@ 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)
-			return TRUE;
+		if (candidate->address == address->address && candidate->plen == address->plen) {
+			guint32 lifetime, preferred;
+
+			if (nmp_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
+			                            now, padding, &lifetime, &preferred))
+				return TRUE;
+		}
 	}
 
 	return FALSE;
 }
 
 static gboolean
-array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address *address)
+array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address *address, gint64 now, guint32 padding)
 {
 	guint len = addresses ? addresses->len : 0;
 	guint i;
@@ -1715,108 +2015,27 @@ array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address
 	for (i = 0; i < len; i++) {
 		NMPlatformIP6Address *candidate = &g_array_index (addresses, NMPlatformIP6Address, i);
 
-		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen)
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
-/**
- * Takes a pair @timestamp and @duration, and returns the remaining duration based
- * on the new timestamp @now.
- */
-static guint32
-_rebase_relative_time_on_now (guint32 timestamp, guint32 duration, guint32 now, guint32 padding)
-{
-	gint64 t;
-
-	if (duration == NM_PLATFORM_LIFETIME_PERMANENT)
-		return NM_PLATFORM_LIFETIME_PERMANENT;
-
-	if (timestamp == 0) {
-		/* if the @timestamp is zero, assume it was just left unset and that the relative
-		 * @duration starts counting from @now. This is convenient to construct an address
-		 * and print it in nm_platform_ip4_address_to_string().
-		 *
-		 * In general it does not make sense to set the @duration without anchoring at
-		 * @timestamp because you don't know the absolute expiration time when looking
-		 * at the address at a later moment. */
-		timestamp = now;
-	}
+		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen) {
+			guint32 lifetime, preferred;
 
-	/* For timestamp > now, just accept it and calculate the expected(?) result. */
-	t = (gint64) timestamp + (gint64) duration - (gint64) now;
-
-	/* Optional padding to avoid potential races. */
-	t += (gint64) padding;
-
-	if (t <= 0)
-		return 0;
-	if (t >= NM_PLATFORM_LIFETIME_PERMANENT)
-		return NM_PLATFORM_LIFETIME_PERMANENT - 1;
-	return t;
-}
-
-static gboolean
-_address_get_lifetime (const NMPlatformIPAddress *address, guint32 now, guint32 padding, guint32 *out_lifetime, guint32 *out_preferred)
-{
-	guint32 lifetime, preferred;
-
-	if (address->lifetime == 0) {
-		*out_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		*out_preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-
-		/* We treat lifetime==0 as permanent addresses to allow easy creation of such addresses
-		 * (without requiring to set the lifetime fields to NM_PLATFORM_LIFETIME_PERMANENT).
-		 * In that case we also expect that the other fields (timestamp and preferred) are left unset. */
-		g_return_val_if_fail (address->timestamp == 0 && address->preferred == 0, TRUE);
-	} else {
-		lifetime = _rebase_relative_time_on_now (address->timestamp, address->lifetime, now, padding);
-		if (!lifetime)
-			return FALSE;
-		preferred = _rebase_relative_time_on_now (address->timestamp, address->preferred, now, padding);
-
-		*out_lifetime = lifetime;
-		*out_preferred = MIN (preferred, lifetime);
-
-		/* Assert that non-permanent addresses have a (positive) @timestamp. _rebase_relative_time_on_now()
-		 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
-		 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
-		 */
-		g_return_val_if_fail (   address->timestamp != 0
-		                      || (   address->lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
-		                          && address->preferred == NM_PLATFORM_LIFETIME_PERMANENT), TRUE);
-		g_return_val_if_fail (preferred <= lifetime, TRUE);
-	}
-	return TRUE;
-}
-
-gboolean
-nm_platform_ip4_check_reinstall_device_route (int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric)
-{
-	g_return_val_if_fail (address, FALSE);
-
-	if (   ifindex <= 0
-	    || address->plen <= 0
-	    || address->plen >= 32)
-		return FALSE;
-
-	if (device_route_metric == NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE) {
-		/* The automatically added route would be already our desired priority.
-		 * Nothing to do. */
-		return FALSE;
+			if (nmp_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
+			                            now, padding, &lifetime, &preferred))
+				return TRUE;
+		}
 	}
 
-	return klass->ip4_check_reinstall_device_route (platform, ifindex, address, device_route_metric);
+	return FALSE;
 }
 
 /**
  * nm_platform_ip4_address_sync:
+ * @self: platform instance
  * @ifindex: Interface index
  * @known_addresses: List of addresses
- * @device_route_metric: the route metric for adding subnet routes (replaces
- *   the kernel added routes).
+ * @out_added_addresses: (out): (allow-none): if not %NULL, return a #GPtrArray
+ *   with the addresses added. The pointers point into @known_addresses.
+ *   It possibly does not contain all addresses from @known_address because
+ *   some addresses might be expired.
  *
  * A convenience function to synchronize addresses for a specific interface
  * with the least possible disturbance. It simply removes addresses that are
@@ -1825,23 +2044,28 @@ nm_platform_ip4_check_reinstall_device_route (int ifindex, const NMPlatformIP4Ad
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint32 device_route_metric)
+nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, GPtrArray **out_added_addresses)
 {
 	GArray *addresses;
 	NMPlatformIP4Address *address;
 	guint32 now = nm_utils_get_monotonic_timestamp_s ();
 	int i;
 
+	_CHECK_SELF (self, klass, FALSE);
+
 	/* Delete unknown addresses */
-	addresses = nm_platform_ip4_address_get_all (ifindex);
+	addresses = nm_platform_ip4_address_get_all (self, ifindex);
 	for (i = 0; i < addresses->len; i++) {
 		address = &g_array_index (addresses, NMPlatformIP4Address, i);
 
-		if (!array_contains_ip4_address (known_addresses, address))
-			nm_platform_ip4_address_delete (ifindex, address->address, address->plen, address->peer_address);
+		if (!array_contains_ip4_address (known_addresses, address, now, ADDRESS_LIFETIME_PADDING))
+			nm_platform_ip4_address_delete (self, ifindex, address->address, address->plen, address->peer_address);
 	}
 	g_array_free (addresses, TRUE);
 
+	if (out_added_addresses)
+		*out_added_addresses = NULL;
+
 	if (!known_addresses)
 		return TRUE;
 
@@ -1849,33 +2073,18 @@ nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint3
 	for (i = 0; i < known_addresses->len; i++) {
 		const NMPlatformIP4Address *known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
 		guint32 lifetime, preferred;
-		guint32 network;
-		gboolean reinstall_device_route = FALSE;
 
-		/* add a padding of 5 seconds to avoid potential races. */
-		if (!_address_get_lifetime ((NMPlatformIPAddress *) known_address, now, 5, &lifetime, &preferred))
+		if (!nmp_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                             now, ADDRESS_LIFETIME_PADDING, &lifetime, &preferred))
 			continue;
 
-		if (nm_platform_ip4_check_reinstall_device_route (ifindex, known_address, device_route_metric))
-			reinstall_device_route = TRUE;
-
-		if (!nm_platform_ip4_address_add (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->peer_address, known_address->plen, lifetime, preferred, known_address->label))
 			return FALSE;
 
-		if (reinstall_device_route) {
-			/* Kernel automatically adds a device route for us with metric 0. That is not what we want.
-			 * Remove it, and re-add it.
-			 *
-			 * In face of having the same subnets on two different interfaces with the same metric,
-			 * this is a problem. Surprisingly, kernel is able to add two routes for the same subnet/prefix,metric
-			 * to different interfaces. We cannot. Adding one, would replace the other. This is avoided
-			 * by the above nm_platform_ip4_check_reinstall_device_route() check.
-			 */
-			network = nm_utils_ip4_address_clear_host_address (known_address->address, known_address->plen);
-			(void) nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_KERNEL, network, known_address->plen,
-			                                  0, known_address->address, device_route_metric, 0);
-			(void) nm_platform_ip4_route_delete (ifindex, network, known_address->plen,
-			                                     NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE);
+		if (out_added_addresses) {
+			if (!*out_added_addresses)
+				*out_added_addresses = g_ptr_array_new ();
+			g_ptr_array_add (*out_added_addresses, (gpointer) known_address);
 		}
 	}
 
@@ -1884,6 +2093,7 @@ nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint3
 
 /**
  * nm_platform_ip6_address_sync:
+ * @self: platform instance
  * @ifindex: Interface index
  * @known_addresses: List of addresses
  * @keep_link_local: Don't remove link-local address
@@ -1895,7 +2105,7 @@ nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint3
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboolean keep_link_local)
+nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, gboolean keep_link_local)
 {
 	GArray *addresses;
 	NMPlatformIP6Address *address;
@@ -1903,7 +2113,7 @@ nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboole
 	int i;
 
 	/* Delete unknown addresses */
-	addresses = nm_platform_ip6_address_get_all (ifindex);
+	addresses = nm_platform_ip6_address_get_all (self, ifindex);
 	for (i = 0; i < addresses->len; i++) {
 		address = &g_array_index (addresses, NMPlatformIP6Address, i);
 
@@ -1911,8 +2121,8 @@ nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboole
 		if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
 			continue;
 
-		if (!array_contains_ip6_address (known_addresses, address))
-			nm_platform_ip6_address_delete (ifindex, address->address, address->plen);
+		if (!array_contains_ip6_address (known_addresses, address, now, ADDRESS_LIFETIME_PADDING))
+			nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen);
 	}
 	g_array_free (addresses, TRUE);
 
@@ -1924,11 +2134,11 @@ nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboole
 		const NMPlatformIP6Address *known_address = &g_array_index (known_addresses, NMPlatformIP6Address, i);
 		guint32 lifetime, preferred;
 
-		/* add a padding of 5 seconds to avoid potential races. */
-		if (!_address_get_lifetime ((NMPlatformIPAddress *) known_address, now, 5, &lifetime, &preferred))
+		if (!nmp_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                             now, ADDRESS_LIFETIME_PADDING, &lifetime, &preferred))
 			continue;
 
-		if (!nm_platform_ip6_address_add (ifindex, known_address->address,
+		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
 		                                  known_address->peer_address, known_address->plen,
 		                                  lifetime, preferred, known_address->flags))
 			return FALSE;
@@ -1938,51 +2148,50 @@ nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboole
 }
 
 gboolean
-nm_platform_address_flush (int ifindex)
+nm_platform_address_flush (NMPlatform *self, int ifindex)
 {
-	return nm_platform_ip4_address_sync (ifindex, NULL, 0)
-			&& nm_platform_ip6_address_sync (ifindex, NULL, FALSE);
+	_CHECK_SELF (self, klass, FALSE);
+
+	return    nm_platform_ip4_address_sync (self, ifindex, NULL, NULL)
+	       && nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE);
 }
 
 /******************************************************************/
 
 GArray *
-nm_platform_ip4_route_get_all (int ifindex, NMPlatformGetRouteMode mode)
+nm_platform_ip4_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex >= 0, NULL);
-	g_return_val_if_fail (klass->ip4_route_get_all, NULL);
 
-	return klass->ip4_route_get_all (platform, ifindex, mode);
+	return klass->ip4_route_get_all (self, ifindex, flags);
 }
 
 GArray *
-nm_platform_ip6_route_get_all (int ifindex, NMPlatformGetRouteMode mode)
+nm_platform_ip6_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (ifindex >= 0, NULL);
-	g_return_val_if_fail (klass->ip6_route_get_all, NULL);
 
-	return klass->ip6_route_get_all (platform, ifindex, mode);
+	return klass->ip6_route_get_all (self, ifindex, flags);
 }
 
 gboolean
-nm_platform_ip4_route_add (int ifindex, NMIPConfigSource source,
+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)
 {
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (platform, FALSE);
 	g_return_val_if_fail (0 <= plen && plen <= 32, FALSE);
 	g_return_val_if_fail (klass->ip4_route_add, FALSE);
 
 	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
 		NMPlatformIP4Route route = { 0 };
-		char pref_src_buf[NM_UTILS_INET_ADDRSTRLEN];
 
 		route.ifindex = ifindex;
 		route.source = source;
@@ -1991,21 +2200,21 @@ nm_platform_ip4_route_add (int ifindex, NMIPConfigSource source,
 		route.gateway = gateway;
 		route.metric = metric;
 		route.mss = mss;
+		route.pref_src = pref_src;
 
-		debug ("route: adding or updating IPv4 route: %s%s%s%s", nm_platform_ip4_route_to_string (&route),
-		       pref_src ? " (src: " : "",
-		       pref_src ? nm_utils_inet4_ntop (pref_src, pref_src_buf) : "",
-		       pref_src ? ")" : "");
+		_LOGD ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (&route));
 	}
-	return klass->ip4_route_add (platform, ifindex, source, network, plen, gateway, pref_src, metric, mss);
+	return klass->ip4_route_add (self, ifindex, source, network, plen, gateway, pref_src, metric, mss);
 }
 
 gboolean
-nm_platform_ip6_route_add (int ifindex, NMIPConfigSource source,
+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)
 {
-	g_return_val_if_fail (platform, FALSE);
+	_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);
 
@@ -2020,256 +2229,55 @@ nm_platform_ip6_route_add (int ifindex, NMIPConfigSource source,
 		route.metric = metric;
 		route.mss = mss;
 
-		debug ("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));
 	}
-	return klass->ip6_route_add (platform, ifindex, source, network, plen, gateway, metric, mss);
+	return klass->ip6_route_add (self, ifindex, source, network, plen, gateway, metric, mss);
 }
 
 gboolean
-nm_platform_ip4_route_delete (int ifindex, in_addr_t network, int plen, guint32 metric)
+nm_platform_ip4_route_delete (NMPlatform *self, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (platform, FALSE);
 	g_return_val_if_fail (klass->ip4_route_delete, FALSE);
 
-	debug ("route: deleting IPv4 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
+	_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 (ifindex, str_dev, sizeof (str_dev)));
-	return klass->ip4_route_delete (platform, ifindex, network, plen, metric);
+	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	return klass->ip4_route_delete (self, ifindex, network, plen, metric);
 }
 
 gboolean
-nm_platform_ip6_route_delete (int ifindex, struct in6_addr network, int plen, guint32 metric)
+nm_platform_ip6_route_delete (NMPlatform *self, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 
-	reset_error ();
+	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (platform, FALSE);
 	g_return_val_if_fail (klass->ip6_route_delete, FALSE);
 
-	debug ("route: deleting IPv6 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
+	_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 (ifindex, str_dev, sizeof (str_dev)));
-	return klass->ip6_route_delete (platform, ifindex, network, plen, metric);
-}
-
-gboolean
-nm_platform_ip4_route_exists (int ifindex, in_addr_t network, int plen, guint32 metric)
-{
-	reset_error ();
-
-	g_return_val_if_fail (platform, FALSE);
-	g_return_val_if_fail (klass->ip4_route_exists, FALSE);
-
-	return klass->ip4_route_exists (platform,ifindex, network, plen, metric);
-}
-
-gboolean
-nm_platform_ip6_route_exists (int ifindex, struct in6_addr network, int plen, guint32 metric)
-{
-	reset_error ();
-
-	g_return_val_if_fail (platform, FALSE);
-	g_return_val_if_fail (klass->ip6_route_exists, FALSE);
-
-	return klass->ip6_route_exists (platform, ifindex, network, plen, metric);
-}
-
-static gboolean
-array_contains_ip4_route (const GArray *routes, const NMPlatformIP4Route *route)
-{
-	guint len = routes ? routes->len : 0;
-	guint i;
-
-	for (i = 0; i < len; i++) {
-		NMPlatformIP4Route *c = &g_array_index (routes, NMPlatformIP4Route, i);
-
-		if (route->network == c->network &&
-		    route->plen == c->plen &&
-		    route->gateway == c->gateway &&
-		    route->metric == c->metric)
-			return TRUE;
-	}
-
-	return FALSE;
+	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	return klass->ip6_route_delete (self, ifindex, network, plen, metric);
 }
 
-static gboolean
-array_contains_ip6_route (const GArray *routes, const NMPlatformIP6Route *route)
+const NMPlatformIP4Route *
+nm_platform_ip4_route_get (NMPlatform *self, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
-	guint len = routes ? routes->len : 0;
-	guint i;
+	_CHECK_SELF (self, klass, FALSE);
 
-	for (i = 0; i < len; i++) {
-		NMPlatformIP6Route *c = &g_array_index (routes, NMPlatformIP6Route, i);
-
-		if (IN6_ARE_ADDR_EQUAL (&route->network, &c->network) &&
-		    route->plen == c->plen &&
-		    IN6_ARE_ADDR_EQUAL (&route->gateway, &c->gateway) &&
-		    route->metric == c->metric)
-			return TRUE;
-	}
-
-	return FALSE;
+	return klass->ip4_route_get (self ,ifindex, network, plen, metric);
 }
 
-/**
- * nm_platform_ip4_route_sync:
- * @ifindex: Interface index
- * @known_routes: List of routes
- *
- * A convenience function to synchronize routes for a specific interface
- * with the least possible disturbance. It simply removes routes that are
- * not listed and adds routes that are.
- * Default routes are ignored (both in @known_routes and those already
- * configured on the device).
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_ip4_route_sync (int ifindex, const GArray *known_routes)
+const NMPlatformIP6Route *
+nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	GArray *routes;
-	NMPlatformIP4Route *route;
-	const NMPlatformIP4Route *known_route;
-	gboolean success;
-	int i, i_type;
-
-	/* Delete unknown routes */
-	routes = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT);
-	for (i = 0; i < routes->len; i++) {
-		route = &g_array_index (routes, NMPlatformIP4Route, i);
-
-		if (!array_contains_ip4_route (known_routes, route))
-			(void) nm_platform_ip4_route_delete (ifindex, route->network, route->plen, route->metric);
-	}
-
-	if (!known_routes) {
-		g_array_free (routes, TRUE);
-		return TRUE;
-	}
-
-	/* Add missing routes */
-	for (i_type = 0, success = TRUE; i_type < 2 && success; i_type++) {
-		for (i = 0; i < known_routes->len && success; i++) {
-			known_route = &g_array_index (known_routes, NMPlatformIP4Route, i);
-
-			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (known_route))
-				continue;
+	_CHECK_SELF (self, klass, FALSE);
 
-			if (   (i_type == 0 && known_route->gateway != 0)
-			    || (i_type == 1 && known_route->gateway == 0)) {
-				/* Make two runs over the list of routes. On the first, only add
-				 * device routes, on the second the others (gateway routes). */
-				continue;
-			}
-
-			/* Ignore routes that already exist */
-			if (!array_contains_ip4_route (routes, known_route)) {
-				success = nm_platform_ip4_route_add (ifindex,
-				                                     known_route->source,
-				                                     known_route->network,
-				                                     known_route->plen,
-				                                     known_route->gateway,
-				                                     0,
-				                                     known_route->metric,
-				                                     known_route->mss);
-				if (!success && known_route->source < NM_IP_CONFIG_SOURCE_USER) {
-					nm_log_dbg (LOGD_PLATFORM, "ignore error adding IPv4 route to kernel: %s",
-					                           nm_platform_ip4_route_to_string (known_route));
-					success = TRUE;
-				}
-			}
-		}
-	}
-
-	g_array_free (routes, TRUE);
-	return success;
-}
-
-/**
- * nm_platform_ip6_route_sync:
- * @ifindex: Interface index
- * @known_routes: List of routes
- *
- * A convenience function to synchronize routes for a specific interface
- * with the least possible disturbance. It simply removes routes that are
- * not listed and adds routes that are.
- * Default routes are ignored (both in @known_routes and those already
- * configured on the device).
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_ip6_route_sync (int ifindex, const GArray *known_routes)
-{
-	GArray *routes;
-	NMPlatformIP6Route *route;
-	const NMPlatformIP6Route *known_route;
-	gboolean success;
-	int i, i_type;
-
-	/* Delete unknown routes */
-	routes = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT);
-	for (i = 0; i < routes->len; i++) {
-		route = &g_array_index (routes, NMPlatformIP6Route, i);
-		route->ifindex = 0;
-
-		if (!array_contains_ip6_route (known_routes, route))
-			nm_platform_ip6_route_delete (ifindex, route->network, route->plen, route->metric);
-	}
-
-	if (!known_routes) {
-		g_array_free (routes, TRUE);
-		return TRUE;
-	}
-
-	/* Add missing routes */
-	for (i_type = 0, success = TRUE; i_type < 2 && success; i_type++) {
-		for (i = 0; i < known_routes->len && success; i++) {
-			known_route = &g_array_index (known_routes, NMPlatformIP6Route, i);
-
-			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (known_route))
-				continue;
-
-			if (   (i_type == 0 && !IN6_IS_ADDR_UNSPECIFIED (&known_route->gateway))
-			    || (i_type == 1 && IN6_IS_ADDR_UNSPECIFIED (&known_route->gateway))) {
-				/* Make two runs over the list of routes. On the first, only add
-				 * device routes, on the second the others (gateway routes). */
-				continue;
-			}
-
-			/* Ignore routes that already exist */
-			if (!array_contains_ip6_route (routes, known_route)) {
-				success = nm_platform_ip6_route_add (ifindex,
-				                                     known_route->source,
-				                                     known_route->network,
-				                                     known_route->plen,
-				                                     known_route->gateway,
-				                                     known_route->metric,
-				                                     known_route->mss);
-				if (!success && known_route->source < NM_IP_CONFIG_SOURCE_USER) {
-					nm_log_dbg (LOGD_PLATFORM, "ignore error adding IPv6 route to kernel: %s",
-					                           nm_platform_ip6_route_to_string (known_route));
-					success = TRUE;
-				}
-			}
-		}
-	}
-
-	g_array_free (routes, TRUE);
-	return success;
-}
-
-gboolean
-nm_platform_route_flush (int ifindex)
-{
-	return nm_platform_ip4_route_sync (ifindex, NULL)
-			&& nm_platform_ip6_route_sync (ifindex, NULL);
+	return klass->ip6_route_get (self, ifindex, network, plen, metric);
 }
 
 /******************************************************************/
@@ -2278,6 +2286,10 @@ static const char *
 source_to_string (NMIPConfigSource source)
 {
 	switch (source) {
+	case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
+		return "rtprot-kernel";
+	case _NM_IP_CONFIG_SOURCE_RTM_F_CLONED:
+		return "rtm-f-cloned";
 	case NM_IP_CONFIG_SOURCE_KERNEL:
 		return "kernel";
 	case NM_IP_CONFIG_SOURCE_SHARED:
@@ -2309,7 +2321,7 @@ _lifetime_to_string (guint32 timestamp, guint32 lifetime, gint32 now, char *buf,
 		return "forever";
 
 	g_snprintf (buf, buf_size, "%usec",
-	            _rebase_relative_time_on_now (timestamp, lifetime, now, 0));
+	            nmp_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now, 0));
 	return buf;
 }
 
@@ -2322,28 +2334,38 @@ _lifetime_summary_to_string (gint32 now, guint32 timestamp, guint32 preferred, g
 	return buf;
 }
 
-static char to_string_buffer[256];
+char _nm_platform_to_string_buffer[256];
 
 const char *
 nm_platform_link_to_string (const NMPlatformLink *link)
 {
 	char master[20];
 	char parent[20];
-	char *driver, *udi, *type;
-	GString *str;
+	char str_vlan[16];
+	GString *str_flags;
+	char str_addrmode[30];
+	gs_free char *str_addr = NULL;
+	gs_free char *str_inet6_token = NULL;
 
 	if (!link)
 		return "(unknown link)";
 
-	str = g_string_new (NULL);
-	if (!link->arp)
-		g_string_append (str, "NOARP,");
-	if (link->up)
-		g_string_append (str, "UP");
+	str_flags = g_string_new (NULL);
+	if (NM_FLAGS_HAS (link->flags, IFF_NOARP))
+		g_string_append (str_flags, "NOARP,");
+	if (NM_FLAGS_HAS (link->flags, IFF_UP))
+		g_string_append (str_flags, "UP");
 	else
-		g_string_append (str, "DOWN");
+		g_string_append (str_flags, "DOWN");
 	if (link->connected)
-		g_string_append (str, ",LOWER_UP");
+		g_string_append (str_flags, ",LOWER_UP");
+
+	if (link->flags) {
+		char str_flags_buf[64];
+
+		rtnl_link_flags2str (link->flags, str_flags_buf, sizeof (str_flags_buf));
+		g_string_append_printf (str_flags, ";%s", str_flags_buf);
+	}
 
 	if (link->master)
 		g_snprintf (master, sizeof (master), " master %d", link->master);
@@ -2355,19 +2377,69 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	else
 		parent[0] = 0;
 
-	driver = link->driver ? g_strdup_printf (" driver '%s'", link->driver) : NULL;
-	udi = link->udi ? g_strdup_printf (" udi '%s'", link->udi) : NULL;
-	type = link->type_name ? NULL : g_strdup_printf ("(%d)", link->type);
-
-	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%d: %s%s <%s> mtu %d%s %s%s%s",
-	            link->ifindex, link->name, parent, str->str,
-	            link->mtu, master, link->type_name ? link->type_name : type,
-	            driver ? driver : "", udi ? udi : "");
-	g_string_free (str, TRUE);
-	g_free (driver);
-	g_free (udi);
-	g_free (type);
-	return to_string_buffer;
+	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:
+				g_snprintf (str_addrmode, sizeof (str_addrmode), " addrgenmode eui64");
+				break;
+			case 1:
+				g_snprintf (str_addrmode, sizeof (str_addrmode), " addrgenmode none");
+				break;
+			default:
+				g_snprintf (str_addrmode, sizeof (str_addrmode), " addrgenmode %d", _nm_platform_uint8_inv (link->inet6_addr_gen_mode_inv));
+				break;
+		}
+	} else
+		str_addrmode[0] = '\0';
+
+	if (link->addr.len)
+		str_addr = nm_utils_hwaddr_ntoa (link->addr.data, MIN (link->addr.len, sizeof (link->addr.data)));
+	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),
+	            "%d: " /* ifindex */
+	            "%s" /* name */
+	            "%s" /* parent */
+	            " <%s>" /* flags */
+	            " mtu %d"
+	            "%s" /* master */
+	            "%s" /* vlan */
+	            " arp %u" /* arptype */
+	            "%s%s" /* link->type */
+	            "%s%s" /* kind */
+	            "%s" /* is-in-udev */
+	            "%s" /* addr-gen-mode */
+	            "%s%s" /* addr */
+	            "%s%s" /* inet6_token */
+	            "%s%s" /* driver */
+	            ,
+	            link->ifindex,
+	            link->name,
+	            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 : "",
+	            link->initialized ? " init" : " not-init",
+	            str_addrmode,
+	            str_addr ? " addr " : "",
+	            str_addr ? str_addr : "",
+	            str_inet6_token ? " inet6token " : "",
+	            str_inet6_token ? str_inet6_token : "",
+	            link->driver ? " driver " : "",
+	            link->driver ? link->driver : "");
+	g_string_free (str_flags, TRUE);
+	return _nm_platform_to_string_buffer;
 }
 
 /**
@@ -2403,7 +2475,7 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
 		str_peer = g_strconcat (" ptp ", s_peer, NULL);
 	}
 
-	_to_string_dev (address->ifindex, str_dev, sizeof (str_dev));
+	_to_string_dev (NULL, address->ifindex, str_dev, sizeof (str_dev));
 
 	if (*address->label)
 		g_snprintf (str_label, sizeof (str_label), " label %s", address->label);
@@ -2420,14 +2492,14 @@ 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 (to_string_buffer, sizeof (to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	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",
 	            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 to_string_buffer;
+	return _nm_platform_to_string_buffer;
 }
 
 /**
@@ -2488,7 +2560,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
 		str_peer = g_strconcat (" ptp ", s_peer, NULL);
 	}
 
-	_to_string_dev (address->ifindex, str_dev, sizeof (str_dev));
+	_to_string_dev (NULL, address->ifindex, str_dev, sizeof (str_dev));
 
 	nm_platform_addr_flags2str (address->flags, s_flags, sizeof (s_flags));
 
@@ -2504,7 +2576,7 @@ 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 (to_string_buffer, sizeof (to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	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",
 	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
 	            str_peer ? str_peer : "",
 	            str_dev,
@@ -2512,7 +2584,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
 	            source_to_string (address->source));
 	g_free (str_flags);
 	g_free (str_peer);
-	return to_string_buffer;
+	return _nm_platform_to_string_buffer;
 }
 
 /**
@@ -2531,21 +2603,38 @@ const char *
 nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route)
 {
 	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)");
 
 	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 (route->ifindex, str_dev, sizeof (str_dev));
-
-	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%s/%d via %s%s metric %"G_GUINT32_FORMAT" mss %"G_GUINT32_FORMAT" src %s",
-	            s_network, route->plen, 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),
+	            "%s/%d"
+	            " via %s"
+	            "%s"
+	            " metric %"G_GUINT32_FORMAT
+	            " mss %"G_GUINT32_FORMAT
+	            " src %s" /* source */
+	            "%s%s" /* scope */
+	            "%s%s" /* pref-src */
+	            "",
+	            s_network, route->plen,
+	            s_gateway,
 	            str_dev,
-	            route->metric, route->mss,
-	            source_to_string (route->source));
-	return to_string_buffer;
+	            route->metric,
+	            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->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;
 }
 
 /**
@@ -2571,14 +2660,23 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
 	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 (route->ifindex, str_dev, sizeof (str_dev));
-
-	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%s/%d via %s%s metric %"G_GUINT32_FORMAT" mss %"G_GUINT32_FORMAT" src %s",
-	            s_network, route->plen, 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),
+	            "%s/%d"
+	            " via %s"
+	            "%s"
+	            " metric %"G_GUINT32_FORMAT
+	            " mss %"G_GUINT32_FORMAT
+	            " src %s" /* source */
+	            "",
+	            s_network, route->plen,
+	            s_gateway,
 	            str_dev,
-	            route->metric, route->mss,
+	            route->metric,
+	            route->mss,
 	            source_to_string (route->source));
-	return to_string_buffer;
+	return _nm_platform_to_string_buffer;
 }
 
 #define _CMP_POINTER(a, b)                                  \
@@ -2597,6 +2695,12 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
             return (((a)->field) < ((b)->field)) ? -1 : 1;  \
     } G_STMT_END
 
+#define _CMP_FIELD_BOOL(a, b, field)                        \
+    G_STMT_START {                                          \
+        if ((!((a)->field)) != (!((b)->field)))                 \
+            return ((!((a)->field)) < (!((b)->field))) ? -1 : 1; \
+    } G_STMT_END
+
 #define _CMP_FIELD_STR(a, b, field)                         \
     G_STMT_START {                                          \
         int c = strcmp ((a)->field, (b)->field);            \
@@ -2604,6 +2708,16 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
             return c < 0 ? -1 : 1;                          \
     } G_STMT_END
 
+#define _CMP_FIELD_STR_INTERNED(a, b, field)                \
+    G_STMT_START {                                          \
+        if (((a)->field) != ((b)->field)) {                 \
+            /* just to be sure, also do a strcmp() if the pointers don't match */ \
+            int c = g_strcmp0 ((a)->field, (b)->field);     \
+            if (c != 0)                                     \
+                return c < 0 ? -1 : 1;                      \
+        } \
+    } G_STMT_END
+
 #define _CMP_FIELD_STR0(a, b, field)                        \
     G_STMT_START {                                          \
         int c = g_strcmp0 ((a)->field, (b)->field);         \
@@ -2611,6 +2725,14 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
             return c < 0 ? -1 : 1;                          \
     } G_STMT_END
 
+#define _CMP_FIELD_MEMCMP_LEN(a, b, field, len)             \
+    G_STMT_START {                                          \
+        int c = memcmp (&((a)->field), &((b)->field),       \
+                        MIN (len, sizeof ((a)->field)));    \
+        if (c != 0)                                         \
+            return c < 0 ? -1 : 1;                          \
+    } G_STMT_END
+
 #define _CMP_FIELD_MEMCMP(a, b, field)                      \
     G_STMT_START {                                          \
         int c = memcmp (&((a)->field), &((b)->field),       \
@@ -2623,17 +2745,26 @@ int
 nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 {
 	_CMP_POINTER (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, up);
+	_CMP_FIELD (a, b, vlan_id);
+	_CMP_FIELD (a, b, flags);
 	_CMP_FIELD (a, b, connected);
-	_CMP_FIELD (a, b, arp);
 	_CMP_FIELD (a, b, mtu);
-	_CMP_FIELD_STR0 (a, b, type_name);
-	_CMP_FIELD_STR0 (a, b, udi);
-	_CMP_FIELD_STR0 (a, b, driver);
+	_CMP_FIELD_BOOL (a, b, initialized);
+	_CMP_FIELD (a, b, arptype);
+	_CMP_FIELD (a, b, addr.len);
+	_CMP_FIELD (a, b, inet6_addr_gen_mode_inv);
+	_CMP_FIELD (a, b, inet6_token.is_valid);
+	_CMP_FIELD_STR_INTERNED (a, b, kind);
+	_CMP_FIELD_STR_INTERNED (a, b, driver);
+	if (a->addr.len)
+		_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
+	if (a->inet6_token.is_valid)
+		_CMP_FIELD_MEMCMP (a, b, inet6_token.iid);
 	return 0;
 }
 
@@ -2680,6 +2811,8 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 	_CMP_FIELD (a, b, gateway);
 	_CMP_FIELD (a, b, metric);
 	_CMP_FIELD (a, b, mss);
+	_CMP_FIELD (a, b, scope_inv);
+	_CMP_FIELD (a, b, pref_src);
 	return 0;
 }
 
@@ -2753,8 +2886,8 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 
 #undef _CMP_POINTER
 
-static const char *
-_change_type_to_string (NMPlatformSignalChangeType change_type)
+const char *
+nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type)
 {
 	switch (change_type) {
 	case NM_PLATFORM_SIGNAL_ADDED:
@@ -2769,76 +2902,80 @@ _change_type_to_string (NMPlatformSignalChangeType change_type)
 }
 
 static void
-log_link (NMPlatform *p, int ifindex, NMPlatformLink *device, NMPlatformSignalChangeType change_type, gpointer user_data)
+log_link (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformLink *device, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
 
-	debug ("signal: link %7s: %s", _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));
 }
 
 static void
-log_ip4_address (NMPlatform *p, int ifindex, NMPlatformIP4Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
+log_ip4_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	debug ("signal: address 4 %7s: %s", _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));
 }
 
 static void
-log_ip6_address (NMPlatform *p, int ifindex, NMPlatformIP6Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
+log_ip6_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	debug ("signal: address 6 %7s: %s", _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));
 }
 
 static void
-log_ip4_route (NMPlatform *p, int ifindex, NMPlatformIP4Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
+log_ip4_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	debug ("signal: route   4 %7s: %s", _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));
 }
 
 static void
-log_ip6_route (NMPlatform *p, int ifindex, NMPlatformIP6Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
+log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	debug ("signal: route   6 %7s: %s", _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));
 }
 
 /******************************************************************/
 
 static gboolean
-_vtr_v4_route_add (int ifindex, const NMPlatformIPXRoute *route, guint32 v4_pref_src)
+_vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
 {
-	return nm_platform_ip4_route_add (ifindex > 0 ? ifindex : route->rx.ifindex,
+	return nm_platform_ip4_route_add (self,
+	                                  ifindex > 0 ? ifindex : route->rx.ifindex,
 	                                  route->rx.source,
 	                                  route->r4.network,
 	                                  route->rx.plen,
 	                                  route->r4.gateway,
-	                                  v4_pref_src,
-	                                  route->rx.metric,
+	                                  route->r4.pref_src,
+	                                  metric >= 0 ? (guint32) metric : route->rx.metric,
 	                                  route->rx.mss);
 }
 
 static gboolean
-_vtr_v6_route_add (int ifindex, const NMPlatformIPXRoute *route, guint32 v4_pref_src)
+_vtr_v6_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
 {
-	return nm_platform_ip6_route_add (ifindex > 0 ? ifindex : route->rx.ifindex,
+	return nm_platform_ip6_route_add (self,
+	                                  ifindex > 0 ? ifindex : route->rx.ifindex,
 	                                  route->rx.source,
 	                                  route->r6.network,
 	                                  route->rx.plen,
 	                                  route->r6.gateway,
-	                                  route->rx.metric,
+	                                  metric >= 0 ? (guint32) metric : route->rx.metric,
 	                                  route->rx.mss);
 }
 
 static gboolean
-_vtr_v4_route_delete (int ifindex, const NMPlatformIPXRoute *route)
+_vtr_v4_route_delete (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route)
 {
-	return nm_platform_ip4_route_delete (ifindex > 0 ? ifindex : route->rx.ifindex,
+	return nm_platform_ip4_route_delete (self,
+	                                     ifindex > 0 ? ifindex : route->rx.ifindex,
 	                                     route->r4.network,
 	                                     route->rx.plen,
 	                                     route->rx.metric);
 }
 
 static gboolean
-_vtr_v6_route_delete (int ifindex, const NMPlatformIPXRoute *route)
+_vtr_v6_route_delete (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route)
 {
-	return nm_platform_ip6_route_delete (ifindex > 0 ? ifindex : route->rx.ifindex,
+	return nm_platform_ip6_route_delete (self,
+	                                     ifindex > 0 ? ifindex : route->rx.ifindex,
 	                                     route->r6.network,
 	                                     route->rx.plen,
 	                                     route->rx.metric);
@@ -2851,15 +2988,15 @@ _vtr_v4_metric_normalize (guint32 metric)
 }
 
 static gboolean
-_vtr_v4_route_delete_default (int ifindex, guint32 metric)
+_vtr_v4_route_delete_default (NMPlatform *self, int ifindex, guint32 metric)
 {
-	return nm_platform_ip4_route_delete (ifindex, 0, 0, metric);
+	return nm_platform_ip4_route_delete (self, ifindex, 0, 0, metric);
 }
 
 static gboolean
-_vtr_v6_route_delete_default (int ifindex, guint32 metric)
+_vtr_v6_route_delete_default (NMPlatform *self, int ifindex, guint32 metric)
 {
-	return nm_platform_ip6_route_delete (ifindex, in6addr_any, 0, metric);
+	return nm_platform_ip6_route_delete (self, ifindex, in6addr_any, 0, metric);
 }
 
 /******************************************************************/
@@ -2893,6 +3030,35 @@ const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
 /******************************************************************/
 
 static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMPlatformPrivate *priv =  NM_PLATFORM_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_REGISTER_SINGLETON:
+		/* construct-only */
+		priv->register_singleton = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+constructed (GObject *object)
+{
+	NMPlatform *self = NM_PLATFORM (object);
+	NMPlatformPrivate *priv =  NM_PLATFORM_GET_PRIVATE (self);
+
+	G_OBJECT_CLASS (nm_platform_parent_class)->constructed (object);
+
+	if (priv->register_singleton)
+		nm_platform_setup (self);
+}
+
+static void
 nm_platform_init (NMPlatform *object)
 {
 }
@@ -2903,13 +3069,26 @@ nm_platform_init (NMPlatform *object)
 		G_SIGNAL_RUN_FIRST, \
 		G_CALLBACK (method), \
 		NULL, NULL, NULL, \
-		G_TYPE_NONE, 4, G_TYPE_INT, G_TYPE_POINTER, NM_TYPE_PLATFORM_SIGNAL_CHANGE_TYPE, NM_TYPE_PLATFORM_REASON);
+		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)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (platform_class);
 
+	g_type_class_add_private (object_class, sizeof (NMPlatformPrivate));
+
+	object_class->set_property = set_property;
+	object_class->constructed = constructed;
+
+	g_object_class_install_property
+	 (object_class, PROP_REGISTER_SINGLETON,
+	     g_param_spec_boolean (NM_PLATFORM_REGISTER_SINGLETON, "", "",
+	                           FALSE,
+	                           G_PARAM_WRITABLE |
+	                           G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS));
+
 	/* Signals */
 	SIGNAL (SIGNAL_LINK_CHANGED, log_link)
 	SIGNAL (SIGNAL_IP4_ADDRESS_CHANGED, log_ip4_address)
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index a49e0f11..4d254195 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -29,6 +29,7 @@
 
 #include <nm-dbus-interface.h>
 #include "nm-types.h"
+#include "NetworkManagerUtils.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -39,6 +40,12 @@
 
 /******************************************************************/
 
+#define NM_PLATFORM_REGISTER_SINGLETON "register-singleton"
+
+/******************************************************************/
+
+typedef struct _NMPlatform NMPlatform;
+
 /* workaround for older libnl version, that does not define these flags. */
 #ifndef IFA_F_MANAGETEMPADDR
 #define IFA_F_MANAGETEMPADDR 0x100
@@ -47,19 +54,24 @@
 #define IFA_F_NOPREFIXROUTE 0x200
 #endif
 
-typedef enum {
-	/* no error specified, sometimes this means the arguments were wrong */
-	NM_PLATFORM_ERROR_NONE,
-	/* object was not found */
+typedef enum { /*< skip >*/
+
+	/* dummy value, to enforce that the enum type is signed and has a size
+	 * to hold an integer. We want to encode errno from <errno.h> as negative
+	 * values. */
+	_NM_PLATFORM_ERROR_MININT = G_MININT,
+
+	NM_PLATFORM_ERROR_SUCCESS = 0,
+
+	NM_PLATFORM_ERROR_BUG,
+
+	NM_PLATFORM_ERROR_UNSPECIFIED,
+
 	NM_PLATFORM_ERROR_NOT_FOUND,
-	/* object already exists */
 	NM_PLATFORM_ERROR_EXISTS,
-	/* object is wrong type */
 	NM_PLATFORM_ERROR_WRONG_TYPE,
-	/* object is not a slave */
 	NM_PLATFORM_ERROR_NOT_SLAVE,
-	/* firmware is not found */
-	NM_PLATFORM_ERROR_NO_FIRMWARE
+	NM_PLATFORM_ERROR_NO_FIRMWARE,
 } NMPlatformError;
 
 typedef enum {
@@ -83,18 +95,54 @@ struct _NMPlatformLink {
 	__NMPlatformObject_COMMON;
 	char name[IFNAMSIZ];
 	NMLinkType type;
-	const char *type_name;
-	const char *udi;
+
+	/* rtnl_link_get_type(), IFLA_INFO_KIND. */
+	/* NMPlatform initializes this field with a static string. */
+	const char *kind;
+
+	/* NMPlatform initializes this field with a static string. */
 	const char *driver;
+
+	gboolean initialized;
 	int master;
 	int parent;
-	gboolean up;
+
+	/* rtnl_link_get_arptype(), ifinfomsg.ifi_type. */
+	guint32 arptype;
+
+	/* rtnl_link_get_addr() */
+	struct {
+		guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */
+		guint8 len;
+	} addr;
+
+	/* rtnl_link_inet6_get_token() */
+	struct {
+		NMUtilsIPv6IfaceId iid;
+		guint8 is_valid;
+	} inet6_token;
+
+	/* The bitwise inverse of rtnl_link_inet6_get_addr_gen_mode(). It is inverse
+	 * to have a default of 0 -- meaning: unspecified. That way, a struct
+	 * 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;
+
+	/* @connected is mostly identical to (@flags & IFF_UP). Except for bridge/bond masters,
+	 * where we coerce the link as disconnect if it has no slaves. */
 	gboolean connected;
-	gboolean arp;
+
 	guint mtu;
 };
 
 typedef enum {
+	NM_PLATFORM_SIGNAL_NONE,
 	NM_PLATFORM_SIGNAL_ADDED,
 	NM_PLATFORM_SIGNAL_CHANGED,
 	NM_PLATFORM_SIGNAL_REMOVED,
@@ -102,11 +150,17 @@ typedef enum {
 
 #define NM_PLATFORM_LIFETIME_PERMANENT G_MAXUINT32
 
-typedef enum {
-	NM_PLATFORM_GET_ROUTE_MODE_ALL,
-	NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT,
-	NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT,
-} NMPlatformGetRouteMode;
+typedef enum { /*< skip >*/
+	NM_PLATFORM_GET_ROUTE_FLAGS_NONE                            = 0,
+
+	/* Whether to include default-routes/non-default-routes. Omitting
+	 * both WITH_DEFAULT and WITH_NON_DEFAULT, is equal to specifying
+	 * both of them. */
+	NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT                    = (1LL << 0),
+	NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT                = (1LL << 1),
+
+	NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL              = (1LL << 2),
+} NMPlatformGetRouteFlags;
 
 typedef struct {
 	__NMPlatformObject_COMMON;
@@ -224,6 +278,14 @@ struct _NMPlatformIP4Route {
 	__NMPlatformIPRoute_COMMON;
 	in_addr_t network;
 	in_addr_t gateway;
+
+	/* The bitwise inverse of the route scope. It is inverted so that the
+	 * default value (RT_SCOPE_NOWHERE) is nul. */
+	guint8 scope_inv;
+
+	/* RTA_PREFSRC/rtnl_route_get_pref_src(). A value of zero means that
+	 * no pref-src is set.  */
+	guint32 pref_src;
 };
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
 
@@ -252,16 +314,17 @@ typedef struct {
 	gsize sizeof_route;
 	int (*route_cmp) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b);
 	const char *(*route_to_string) (const NMPlatformIPXRoute *route);
-	GArray *(*route_get_all) (int ifindex, NMPlatformGetRouteMode mode);
-	gboolean (*route_add) (int ifindex, const NMPlatformIPXRoute *route, guint32 v4_pref_src);
-	gboolean (*route_delete) (int ifindex, const NMPlatformIPXRoute *route);
-	gboolean (*route_delete_default) (int ifindex, guint32 metric);
+	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);
+	gboolean (*route_delete_default) (NMPlatform *self, int ifindex, guint32 metric);
 	guint32 (*metric_normalize) (guint32 metric);
 } NMPlatformVTableRoute;
 
 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;
@@ -346,68 +409,81 @@ typedef struct {
  * network configuration daemons stopped. Look at the code first.
  */
 
-typedef struct {
+struct _NMPlatform {
 	GObject parent;
-
-	NMPlatformError error;
-} NMPlatform;
+};
 
 typedef struct {
 	GObjectClass parent;
 
-	gboolean (*setup) (NMPlatform *);
-
 	gboolean (*sysctl_set) (NMPlatform *, const char *path, const char *value);
 	char * (*sysctl_get) (NMPlatform *, const char *path);
 
-	gboolean (*link_get) (NMPlatform *platform, int ifindex, NMPlatformLink *link);
+	const NMPlatformLink *(*link_get) (NMPlatform *platform, int ifindex);
+	const NMPlatformLink *(*link_get_by_ifname) (NMPlatform *platform, const char *ifname);
+	const NMPlatformLink *(*link_get_by_address) (NMPlatform *platform, gconstpointer address, size_t length);
+
 	GArray *(*link_get_all) (NMPlatform *);
-	gboolean (*link_add) (NMPlatform *, const char *name, NMLinkType type, const void *address, size_t address_len);
+	gboolean (*link_add) (NMPlatform *,
+	                      const char *name,
+	                      NMLinkType type,
+	                      const void *address,
+	                      size_t address_len,
+	                      NMPlatformLink *out_link);
 	gboolean (*link_delete) (NMPlatform *, int ifindex);
-	int (*link_get_ifindex) (NMPlatform *, const char *name);
-	const char *(*link_get_name) (NMPlatform *, int ifindex);
-	NMLinkType (*link_get_type) (NMPlatform *, int ifindex);
 	const char *(*link_get_type_name) (NMPlatform *, int ifindex);
+	gboolean (*link_get_unmanaged) (NMPlatform *, int ifindex, gboolean *unmanaged);
 
 	gboolean (*link_refresh) (NMPlatform *, int ifindex);
+	void (*process_events) (NMPlatform *self);
 
-	gboolean (*link_set_up) (NMPlatform *, int ifindex);
+	gboolean (*link_set_up) (NMPlatform *, int ifindex, gboolean *out_no_firmware);
 	gboolean (*link_set_down) (NMPlatform *, int ifindex);
 	gboolean (*link_set_arp) (NMPlatform *, int ifindex);
 	gboolean (*link_set_noarp) (NMPlatform *, int ifindex);
-	gboolean (*link_is_up) (NMPlatform *, int ifindex);
-	gboolean (*link_is_connected) (NMPlatform *, int ifindex);
-	gboolean (*link_uses_arp) (NMPlatform *, int ifindex);
 
-	gboolean (*link_get_user_ipv6ll_enabled) (NMPlatform *, int ifindex);
+	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
+	GObject *(*link_get_udev_device) (NMPlatform *self, int ifindex);
+
 	gboolean (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
 
-	gconstpointer (*link_get_address) (NMPlatform *, int ifindex, size_t *length);
+	gboolean (*link_get_permanent_address) (NMPlatform *,
+	                                        int ifindex,
+	                                        guint8 *buf,
+	                                        size_t *length);
 	gboolean (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
-	guint32 (*link_get_mtu) (NMPlatform *, int ifindex);
 	gboolean (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
 
 	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
 	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
 	gboolean (*link_get_wake_on_lan) (NMPlatform *, int ifindex);
+	gboolean (*link_get_driver_info) (NMPlatform *,
+	                                  int ifindex,
+	                                  char **out_driver_name,
+	                                  char **out_driver_version,
+	                                  char **out_fw_version);
 
 	gboolean (*link_supports_carrier_detect) (NMPlatform *, int ifindex);
 	gboolean (*link_supports_vlans) (NMPlatform *, int ifindex);
 
 	gboolean (*link_enslave) (NMPlatform *, int master, int slave);
 	gboolean (*link_release) (NMPlatform *, int master, int slave);
-	gboolean (*link_get_master) (NMPlatform *, 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);
+	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);
+	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);
@@ -441,13 +517,11 @@ typedef struct {
 	                             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);
-	gboolean (*ip4_address_exists) (NMPlatform *, int ifindex, in_addr_t address, int plen);
-	gboolean (*ip6_address_exists) (NMPlatform *, int ifindex, struct in6_addr address, int plen);
+	const NMPlatformIP4Address *(*ip4_address_get) (NMPlatform *, int ifindex, in_addr_t address, int plen);
+	const NMPlatformIP6Address *(*ip6_address_get) (NMPlatform *, int ifindex, struct in6_addr address, int plen);
 
-	gboolean (*ip4_check_reinstall_device_route) (NMPlatform *, int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric);
-
-	GArray * (*ip4_route_get_all) (NMPlatform *, int ifindex, NMPlatformGetRouteMode mode);
-	GArray * (*ip6_route_get_all) (NMPlatform *, int ifindex, NMPlatformGetRouteMode mode);
+	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);
@@ -456,8 +530,8 @@ typedef struct {
 	                           guint32 metric, guint32 mss);
 	gboolean (*ip4_route_delete) (NMPlatform *, int ifindex, in_addr_t network, int plen, guint32 metric);
 	gboolean (*ip6_route_delete) (NMPlatform *, int ifindex, struct in6_addr network, int plen, guint32 metric);
-	gboolean (*ip4_route_exists) (NMPlatform *, int ifindex, in_addr_t network, int plen, guint32 metric);
-	gboolean (*ip6_route_exists) (NMPlatform *, int ifindex, struct in6_addr network, int plen, guint32 metric);
+	const NMPlatformIP4Route *(*ip4_route_get) (NMPlatform *, int ifindex, in_addr_t network, int plen, guint32 metric);
+	const NMPlatformIP6Route *(*ip6_route_get) (NMPlatform *, int ifindex, struct in6_addr network, int plen, guint32 metric);
 
 	gboolean (*check_support_kernel_extended_ifa_flags) (NMPlatform *);
 	gboolean (*check_support_user_ipv6ll) (NMPlatform *);
@@ -480,139 +554,172 @@ typedef struct {
 #define NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED "ip4-route-changed"
 #define NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED "ip6-route-changed"
 
+const char *nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type);
+
 /******************************************************************/
 
 GType nm_platform_get_type (void);
 
-void nm_platform_setup (GType type);
+void nm_platform_setup (NMPlatform *instance);
 NMPlatform *nm_platform_get (void);
-void nm_platform_free (void);
+NMPlatform *nm_platform_try_get (void);
+
+#define NM_PLATFORM_GET (nm_platform_get ())
 
 /******************************************************************/
 
-void nm_platform_set_error (NMPlatformError error);
-NMPlatformError nm_platform_get_error (void);
-const char *nm_platform_get_error_msg (void);
-
-void nm_platform_query_devices (void);
-
-gboolean nm_platform_sysctl_set (const char *path, const char *value);
-char *nm_platform_sysctl_get (const char *path);
-gint32 nm_platform_sysctl_get_int32 (const char *path, gint32 fallback);
-gint64 nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gint64 max, gint64 fallback);
-
-gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value);
-
-gboolean nm_platform_link_get (int ifindex, NMPlatformLink *link);
-GArray *nm_platform_link_get_all (void);
-gboolean nm_platform_dummy_add (const char *name);
-gboolean nm_platform_bridge_add (const char *name, const void *address, size_t address_len);
-gboolean nm_platform_bond_add (const char *name);
-gboolean nm_platform_team_add (const char *name);
-gboolean nm_platform_link_exists (const char *name);
-gboolean nm_platform_link_delete (int ifindex);
-int nm_platform_link_get_ifindex (const char *name);
-const char *nm_platform_link_get_name (int ifindex);
-NMLinkType nm_platform_link_get_type (int ifindex);
-const char *nm_platform_link_get_type_name (int ifindex);
-gboolean nm_platform_link_is_software (int ifindex);
-gboolean nm_platform_link_supports_slaves (int ifindex);
-
-gboolean nm_platform_link_refresh (int ifindex);
-
-gboolean nm_platform_link_set_up (int ifindex);
-gboolean nm_platform_link_set_down (int ifindex);
-gboolean nm_platform_link_set_arp (int ifindex);
-gboolean nm_platform_link_set_noarp (int ifindex);
-gboolean nm_platform_link_is_up (int ifindex);
-gboolean nm_platform_link_is_connected (int ifindex);
-gboolean nm_platform_link_uses_arp (int ifindex);
-
-gboolean nm_platform_link_get_user_ipv6ll_enabled (int ifindex);
-gboolean nm_platform_link_set_user_ipv6ll_enabled (int ifindex, gboolean enabled);
-
-gconstpointer nm_platform_link_get_address (int ifindex, size_t *length);
-gboolean nm_platform_link_set_address (int ifindex, const void *address, size_t length);
-guint32 nm_platform_link_get_mtu (int ifindex);
-gboolean nm_platform_link_set_mtu (int ifindex, guint32 mtu);
-
-char    *nm_platform_link_get_physical_port_id (int ifindex);
-guint    nm_platform_link_get_dev_id (int ifindex);
-gboolean nm_platform_link_get_wake_on_lan (int ifindex);
-
-gboolean nm_platform_link_supports_carrier_detect (int ifindex);
-gboolean nm_platform_link_supports_vlans (int ifindex);
-
-gboolean nm_platform_link_enslave (int master, int slave);
-gboolean nm_platform_link_release (int master, int slave);
-int nm_platform_link_get_master (int slave);
-gboolean nm_platform_master_set_option (int ifindex, const char *option, const char *value);
-char *nm_platform_master_get_option (int ifindex, const char *option);
-gboolean nm_platform_slave_set_option (int ifindex, const char *option, const char *value);
-char *nm_platform_slave_get_option (int ifindex, const char *option);
-
-gboolean nm_platform_vlan_add (const char *name, int parent, int vlanid, guint32 vlanflags);
-gboolean nm_platform_vlan_get_info (int ifindex, int *parent, int *vlanid);
-gboolean nm_platform_vlan_set_ingress_map (int ifindex, int from, int to);
-gboolean nm_platform_vlan_set_egress_map (int ifindex, int from, int to);
-
-gboolean nm_platform_infiniband_partition_add (int parent, int p_key);
-
-gboolean nm_platform_veth_get_properties (int ifindex, NMPlatformVethProperties *properties);
-gboolean nm_platform_tun_get_properties (int ifindex, NMPlatformTunProperties *properties);
-gboolean nm_platform_macvlan_get_properties (int ifindex, NMPlatformMacvlanProperties *props);
-gboolean nm_platform_vxlan_get_properties (int ifindex, NMPlatformVxlanProperties *props);
-gboolean nm_platform_gre_get_properties (int ifindex, NMPlatformGreProperties *props);
-
-gboolean    nm_platform_wifi_get_capabilities (int ifindex, NMDeviceWifiCapabilities *caps);
-gboolean    nm_platform_wifi_get_bssid        (int ifindex, guint8 *bssid);
-GByteArray *nm_platform_wifi_get_ssid         (int ifindex);
-guint32     nm_platform_wifi_get_frequency    (int ifindex);
-int         nm_platform_wifi_get_quality      (int ifindex);
-guint32     nm_platform_wifi_get_rate         (int ifindex);
-NM80211Mode nm_platform_wifi_get_mode         (int ifindex);
-void        nm_platform_wifi_set_mode         (int ifindex, NM80211Mode mode);
-guint32     nm_platform_wifi_find_frequency   (int ifindex, const guint32 *freqs);
-void        nm_platform_wifi_indicate_addressing_running (int ifindex, gboolean running);
-
-guint32     nm_platform_mesh_get_channel      (int ifindex);
-gboolean    nm_platform_mesh_set_channel      (int ifindex, guint32 channel);
-gboolean    nm_platform_mesh_set_ssid         (int ifindex, const guint8 *ssid, gsize len);
-
-GArray *nm_platform_ip4_address_get_all (int ifindex);
-GArray *nm_platform_ip6_address_get_all (int ifindex);
-gboolean nm_platform_ip4_address_add (int ifindex,
+/**
+ * nm_platform_route_scope_inv:
+ * @scope: the route scope, either its original value, or its inverse.
+ *
+ * This function is useful, because the constants such as RT_SCOPE_NOWHERE
+ * are 'int', so ~scope also gives an 'int'. This function gets the type
+ * casts to guint8 right.
+ *
+ * Returns: the bitwise inverse of the route scope.
+ * */
+#define nm_platform_route_scope_inv _nm_platform_uint8_inv
+static inline guint8
+_nm_platform_uint8_inv (guint8 scope)
+{
+	return (guint8) ~scope;
+}
+
+const char *nm_link_type_to_string (NMLinkType link_type);
+
+const char *nm_platform_error_to_string (NMPlatformError error);
+
+gboolean nm_platform_sysctl_set (NMPlatform *self, const char *path, const char *value);
+char *nm_platform_sysctl_get (NMPlatform *self, const char *path);
+gint32 nm_platform_sysctl_get_int32 (NMPlatform *self, const char *path, gint32 fallback);
+gint64 nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *path, guint base, gint64 min, gint64 max, gint64 fallback);
+
+gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value);
+
+const NMPlatformLink *nm_platform_link_get (NMPlatform *self, int ifindex);
+const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname);
+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);
+gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
+
+/* convienience methods to lookup the link and access fields of NMPlatformLink. */
+int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
+const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
+NMLinkType nm_platform_link_get_type (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_is_software (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_is_up (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex);
+guint32 nm_platform_link_get_mtu (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_get_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId *iid);
+gboolean nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex);
+gconstpointer nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length);
+int nm_platform_link_get_master (NMPlatform *self, int slave);
+
+gboolean nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanaged);
+gboolean nm_platform_link_supports_slaves (NMPlatform *self, int ifindex);
+const char *nm_platform_link_get_type_name (NMPlatform *self, int ifindex);
+
+gboolean nm_platform_link_refresh (NMPlatform *self, int ifindex);
+void nm_platform_process_events (NMPlatform *self);
+
+gboolean nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmware);
+gboolean nm_platform_link_set_down (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_set_arp (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_set_noarp (NMPlatform *self, int ifindex);
+
+const char *nm_platform_link_get_udi (NMPlatform *self, int ifindex);
+
+GObject *nm_platform_link_get_udev_device (NMPlatform *self, int ifindex);
+
+gboolean nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
+
+gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length);
+gboolean nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
+gboolean nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
+
+char    *nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex);
+guint    nm_platform_link_get_dev_id (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_get_wake_on_lan (NMPlatform *self, int ifindex);
+gboolean nm_platform_link_get_driver_info (NMPlatform *self,
+                                           int ifindex,
+                                           char **out_driver_name,
+                                           char **out_driver_version,
+                                           char **out_fw_version);
+
+gboolean nm_platform_link_supports_carrier_detect (NMPlatform *self, int ifindex);
+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_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);
+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);
+
+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);
+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,
                                       const char *label);
-gboolean nm_platform_ip6_address_add (int ifindex,
+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_ip4_address_delete (int ifindex, in_addr_t address, int plen, in_addr_t peer_address);
-gboolean nm_platform_ip6_address_delete (int ifindex, struct in6_addr address, int plen);
-gboolean nm_platform_ip4_address_exists (int ifindex, in_addr_t address, int plen);
-gboolean nm_platform_ip6_address_exists (int ifindex, struct in6_addr address, int plen);
-gboolean nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint32 device_route_metric);
-gboolean nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboolean keep_link_local);
-gboolean nm_platform_address_flush (int ifindex);
-
-gboolean nm_platform_ip4_check_reinstall_device_route (int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric);
-
-GArray *nm_platform_ip4_route_get_all (int ifindex, NMPlatformGetRouteMode mode);
-GArray *nm_platform_ip6_route_get_all (int ifindex, NMPlatformGetRouteMode mode);
-gboolean nm_platform_ip4_route_add (int ifindex, NMIPConfigSource source,
+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);
+gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, gboolean keep_link_local);
+gboolean nm_platform_address_flush (NMPlatform *self, int ifindex);
+
+const NMPlatformIP4Route *nm_platform_ip4_route_get (NMPlatform *self, int ifindex, in_addr_t network, int plen, guint32 metric);
+const NMPlatformIP6Route *nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr network, int plen, guint32 metric);
+GArray *nm_platform_ip4_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags);
+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);
-gboolean nm_platform_ip6_route_add (int ifindex, NMIPConfigSource source,
+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 (int ifindex, in_addr_t network, int plen, guint32 metric);
-gboolean nm_platform_ip6_route_delete (int ifindex, struct in6_addr network, int plen, guint32 metric);
-gboolean nm_platform_ip4_route_exists (int ifindex, in_addr_t network, int plen, guint32 metric);
-gboolean nm_platform_ip6_route_exists (int ifindex, struct in6_addr network, int plen, guint32 metric);
-gboolean nm_platform_ip4_route_sync (int ifindex, const GArray *known_routes);
-gboolean nm_platform_ip6_route_sync (int ifindex, const GArray *known_routes);
-gboolean nm_platform_route_flush (int ifindex);
+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);
@@ -627,8 +734,8 @@ int nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4R
 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_kernel_extended_ifa_flags (void);
-gboolean nm_platform_check_support_user_ipv6ll (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);
 
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
new file mode 100644
index 00000000..51684b93
--- /dev/null
+++ b/src/platform/nmp-object.c
@@ -0,0 +1,1924 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "nmp-object.h"
+
+#include <unistd.h>
+
+#include "nm-platform-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-logging.h"
+
+/*********************************************************************************************/
+
+#define _LOG_DOMAIN LOGD_PLATFORM
+
+#define _LOG(level, domain, obj, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            const NMPObject *const __obj = (obj); \
+            \
+            _nm_log (__level, __domain, 0, \
+                     "nmp-object[%p/%s]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     __obj, \
+                     (__obj ? NMP_OBJECT_GET_CLASS (__obj)->obj_type_name : "???") \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+#define _LOG_LEVEL_ENABLED(level, domain) \
+    ( nm_logging_enabled ((level), (domain)) )
+
+#ifdef NM_MORE_LOGGING
+#define _LOGT_ENABLED()     _LOG_LEVEL_ENABLED (LOGL_TRACE, _LOG_DOMAIN)
+#define _LOGT(obj, ...)          _LOG (LOGL_TRACE, _LOG_DOMAIN, obj, __VA_ARGS__)
+#else
+#define _LOGT_ENABLED()     FALSE
+#define _LOGT(obj, ...)          G_STMT_START { if (FALSE) { _LOG (LOGL_TRACE, _LOG_DOMAIN, obj, __VA_ARGS__); } } G_STMT_END
+#endif
+
+#define _LOGD(obj, ...)      _LOG (LOGL_DEBUG, _LOG_DOMAIN, obj, __VA_ARGS__)
+#define _LOGI(obj, ...)      _LOG (LOGL_INFO , _LOG_DOMAIN, obj, __VA_ARGS__)
+#define _LOGW(obj, ...)      _LOG (LOGL_WARN , _LOG_DOMAIN, obj, __VA_ARGS__)
+#define _LOGE(obj, ...)      _LOG (LOGL_ERR  , _LOG_DOMAIN, obj, __VA_ARGS__)
+
+/*********************************************************************************************/
+
+struct _NMPCache {
+	/* the cache contains only one hash table for all object types, and similarly
+	 * it contains only one NMMultiIndex.
+	 * This works, because different object types don't ever compare equal and
+	 * because their index ids also don't overlap.
+	 *
+	 * For routes and addresses, the cache contains an address if (and only if) the
+	 * object was reported via netlink.
+	 * For links, the cache contain a link if it was reported by either netlink
+	 * or udev. That means, a link object can be alive, even if it was already
+	 * removed via netlink.
+	 *
+	 * This effectively merges the udev-device cache into the NMPCache.
+	 */
+
+	GHashTable *idx_main;
+	NMMultiIndex *idx_multi;
+
+	gboolean use_udev;
+};
+
+/******************************************************************/
+
+static inline guint
+_id_hash_ip6_addr (const struct in6_addr *addr)
+{
+	guint hash = (guint) 0x897da53981a13ULL;
+	int i;
+
+	for (i = 0; i < sizeof (*addr); i++)
+		hash = (hash * 33) + ((const guint8 *) addr)[i];
+	return hash;
+}
+
+static const char *
+_link_get_driver (GUdevDevice *udev_device, const char *kind, const char *ifname)
+{
+	const char *driver = NULL;
+
+	nm_assert (kind == g_intern_string (kind));
+
+	if (udev_device) {
+		driver = nmp_utils_udev_get_driver (udev_device);
+		if (driver)
+			return driver;
+	}
+
+	if (kind)
+		return kind;
+
+	if (ifname) {
+		char *d;
+
+		if (nmp_utils_ethtool_get_driver_info (ifname, &d, NULL, NULL)) {
+			driver = d && d[0] ? g_intern_string (d) : NULL;
+			g_free (d);
+			if (driver)
+				return driver;
+		}
+	}
+
+	return "unknown";
+}
+
+void
+_nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev)
+{
+	const char *driver = NULL;
+	gboolean initialized = FALSE;
+
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK);
+
+	/* The link contains internal fields that are combined by
+	 * properties from netlink and udev. Update those properties */
+
+	/* When a link is not in netlink, it's udev fields don't matter. */
+	if (obj->_link.netlink.is_in_netlink) {
+		driver = _link_get_driver (obj->_link.udev.device,
+		                           obj->link.kind,
+		                           obj->link.name);
+		if (obj->_link.udev.device)
+			initialized = TRUE;
+		else if (!use_udev) {
+			/* If we don't use udev, we immediately mark the link as initialized.
+			 *
+			 * For that, we consult @use_udev argument, that is cached via
+			 * nmp_cache_use_udev_get(). It is on purpose not to test
+			 * for a writable /sys on every call. A minor reason for that is
+			 * performance, but the real reason is reproducibility.
+			 *
+			 * If you want to support changing of whether udev is enabled,
+			 * reset the value via nmp_cache_use_udev_set() carefully -- and
+			 * possibly update the links in the cache accordingly.
+			 * */
+			initialized = TRUE;
+		}
+	}
+
+	obj->link.driver = driver;
+	obj->link.initialized = initialized;
+}
+
+static void
+_nmp_object_fixup_link_master_connected (NMPObject *obj, const NMPCache *cache)
+{
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK);
+
+	if (nmp_cache_link_connected_needs_toggle (cache, obj, NULL, NULL))
+		obj->link.connected = !obj->link.connected;
+}
+
+/******************************************************************/
+
+const NMPClass *
+nmp_class_from_type (NMPObjectType obj_type)
+{
+	g_return_val_if_fail (obj_type > NMP_OBJECT_TYPE_UNKNOWN && obj_type <= NMP_OBJECT_TYPE_MAX, NULL);
+
+	return &_nmp_classes[obj_type - 1];
+}
+
+/******************************************************************/
+
+NMPObject *
+nmp_object_ref (NMPObject *obj)
+{
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+	g_return_val_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT, NULL);
+	obj->_ref_count++;
+
+	_LOGT (obj, "ref: %d", obj->_ref_count);
+
+	return obj;
+}
+
+void
+nmp_object_unref (NMPObject *obj)
+{
+	if (obj) {
+		g_return_if_fail (obj->_ref_count > 0);
+		g_return_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT);
+		_LOGT (obj, "%s: %d",
+		       obj->_ref_count <= 1 ? "destroy" : "unref",
+		       obj->_ref_count - 1);
+		if (--obj->_ref_count <= 0) {
+			const NMPClass *klass = obj->_class;
+
+			nm_assert (!obj->is_cached);
+			if (klass->cmd_obj_dispose)
+				klass->cmd_obj_dispose (obj);
+			g_slice_free1 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object), obj);
+		}
+	}
+}
+
+static void
+_vt_cmd_obj_dispose_link (NMPObject *obj)
+{
+	g_clear_object (&obj->_link.udev.device);
+}
+
+static NMPObject *
+_nmp_object_new_from_class (const NMPClass *klass)
+{
+	NMPObject *obj;
+
+	nm_assert (klass);
+	nm_assert (klass->sizeof_data > 0);
+	nm_assert (klass->sizeof_public > 0 && klass->sizeof_public <= klass->sizeof_data);
+
+	obj = g_slice_alloc0 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object));
+	obj->_class = klass;
+	obj->_ref_count = 1;
+	_LOGT (obj, "new");
+	return obj;
+}
+
+NMPObject *
+nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plobj)
+{
+	const NMPClass *klass = nmp_class_from_type (obj_type);
+	NMPObject *obj;
+
+	obj = _nmp_object_new_from_class (klass);
+	if (plobj)
+		memcpy (&obj->object, plobj, klass->sizeof_public);
+	return obj;
+}
+
+NMPObject *
+nmp_object_new_link (int ifindex)
+{
+	NMPObject *obj;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LINK, NULL);
+	obj->link.ifindex = ifindex;
+	return obj;
+}
+
+/******************************************************************/
+
+static const NMPObject *
+_nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass)
+{
+	nm_assert (klass);
+
+	memset (obj, 0, sizeof (NMPObject));
+	obj->_class = klass;
+	obj->_ref_count = NMP_REF_COUNT_STACKINIT;
+	return obj;
+}
+
+const NMPObject *
+nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj)
+{
+	const NMPClass *klass = nmp_class_from_type (obj_type);
+
+	_nmp_object_stackinit_from_class (obj, klass);
+	if (plobj)
+		memcpy (&obj->object, plobj, klass->sizeof_public);
+	return obj;
+}
+
+const NMPObject *
+nmp_object_stackinit_id  (NMPObject *obj, const NMPObject *src)
+{
+	nm_assert (NMP_OBJECT_IS_VALID (src));
+	nm_assert (obj);
+
+	NMP_OBJECT_GET_CLASS (src)->cmd_obj_stackinit_id (obj, src);
+	return obj;
+}
+
+const NMPObject *
+nmp_object_stackinit_id_link (NMPObject *obj, int ifindex)
+{
+	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_LINK, NULL);
+	obj->link.ifindex = ifindex;
+	return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_link (NMPObject *obj, const NMPObject *src)
+{
+	nmp_object_stackinit_id_link (obj, src->link.ifindex);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen)
+{
+	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;
+	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);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, int plen)
+{
+	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP6_ADDRESS, NULL);
+	obj->ip4_address.ifindex = ifindex;
+	if (address)
+		obj->ip6_address.address = *address;
+	obj->ip6_address.plen = plen;
+	return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip6_address (NMPObject *obj, const NMPObject *src)
+{
+	nmp_object_stackinit_id_ip6_address (obj, src->ip_address.ifindex, &src->ip6_address.address, src->ip_address.plen);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, int plen, guint32 metric)
+{
+	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
+	obj->ip4_route.ifindex = ifindex;
+	obj->ip4_route.network = network;
+	obj->ip4_route.plen = plen;
+	obj->ip4_route.metric = metric;
+	return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip4_route (NMPObject *obj, const NMPObject *src)
+{
+	nmp_object_stackinit_id_ip4_route (obj, src->ip_route.ifindex, src->ip4_route.network, src->ip_route.plen, src->ip_route.metric);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, int plen, guint32 metric)
+{
+	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+	obj->ip6_route.ifindex = ifindex;
+	if (network)
+		obj->ip6_route.network = *network;
+	obj->ip6_route.plen = plen;
+	obj->ip6_route.metric = metric;
+	return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip6_route (NMPObject *obj, const NMPObject *src)
+{
+	nmp_object_stackinit_id_ip6_route (obj, src->ip_route.ifindex, &src->ip6_route.network, src->ip_route.plen, src->ip_route.metric);
+}
+
+/******************************************************************/
+
+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);
+	}
+
+	if (!obj) {
+		g_strlcpy (buf, "NULL", buf_size);
+		return buf;
+	}
+
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	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_strlcpy (buf2, NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object), sizeof (buf2));
+
+		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);
+		} else
+			buf3[0] = '\0';
+
+		g_snprintf (buf, buf_size,
+		            "[%s,%p,%d,%ccache,%calive,%cvisible%s; %s]",
+		            klass->obj_type_name, obj, obj->_ref_count,
+		            obj->is_cached ? '+' : '-',
+		            nmp_object_is_alive (obj) ? '+' : '-',
+		            nmp_object_is_visible (obj) ? '+' : '-',
+		            buf3, 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);
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
+
+#define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \
+static const char * \
+_vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
+{ \
+	plat_type *const obj = (plat_type *) _obj; \
+	char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
+	\
+	(void) buf1; \
+	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);
+
+int
+nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
+{
+	if (obj1 == obj2)
+		return 0;
+	if (!obj1)
+		return -1;
+	if (!obj2)
+		return 1;
+
+	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;
+
+	return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object);
+}
+
+gboolean
+nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+	const NMPClass *klass;
+
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
+
+	if (obj1 == obj2)
+		return TRUE;
+
+	klass = NMP_OBJECT_GET_CLASS (obj1);
+
+	if (klass != NMP_OBJECT_GET_CLASS (obj2))
+		return FALSE;
+
+	return klass->cmd_obj_equal (obj1, obj2);
+}
+
+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;
+}
+
+static gboolean
+_vt_cmd_obj_equal_link (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;
+}
+
+/* @src is a const object, which is not entirely correct for link types, where
+ * we increase the ref count for src->_link.udev.device.
+ * Hence, nmp_object_copy() can violate the const promise of @src.
+ * */
+void
+nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only)
+{
+	g_return_if_fail (NMP_OBJECT_IS_VALID (dst));
+	g_return_if_fail (NMP_OBJECT_IS_VALID (src));
+	g_return_if_fail (!NMP_OBJECT_IS_STACKINIT (dst));
+
+	if (src != dst) {
+		const NMPClass *klass = NMP_OBJECT_GET_CLASS (dst);
+
+		g_return_if_fail (klass == NMP_OBJECT_GET_CLASS (src));
+
+		if (id_only)
+			klass->cmd_plobj_id_copy (&dst->object, &src->object);
+		else
+			klass->cmd_obj_copy (dst, src);
+	}
+}
+
+#define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
+static void \
+_vt_cmd_plobj_id_copy_##type (NMPlatformObject *_dst, const NMPlatformObject *_src) \
+{ \
+	plat_type *const dst = (plat_type *) _dst; \
+	const plat_type *const src = (const plat_type *) _src; \
+	{ cmd } \
+}
+_vt_cmd_plobj_id_copy (link, NMPlatformLink, {
+	dst->ifindex = src->ifindex;
+});
+_vt_cmd_plobj_id_copy (ip4_address, NMPlatformIP4Address, {
+	dst->ifindex = src->ifindex;
+	dst->plen = src->plen;
+	dst->address = src->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, {
+	dst->ifindex = src->ifindex;
+	dst->plen = src->plen;
+	dst->metric = src->metric;
+	dst->network = src->network;
+});
+_vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, {
+	dst->ifindex = src->ifindex;
+	dst->plen = src->plen;
+	dst->metric = src->metric;
+	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.
+ * */
+NMPObject *
+nmp_object_clone (const NMPObject *obj, gboolean id_only)
+{
+	NMPObject *dst;
+
+	if (!obj)
+		return NULL;
+
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+
+	dst = _nmp_object_new_from_class (NMP_OBJECT_GET_CLASS (obj));
+	nmp_object_copy (dst, obj, id_only);
+	return dst;
+}
+
+gboolean
+nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+	const NMPClass *klass;
+
+	if (obj1 == obj2)
+		return TRUE;
+	if (!obj1 || !obj2)
+		return FALSE;
+
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
+
+	klass = NMP_OBJECT_GET_CLASS (obj1);
+	return    klass == NMP_OBJECT_GET_CLASS (obj2)
+	       && klass->cmd_plobj_id_equal (&obj1->object, &obj2->object);
+}
+
+#define _vt_cmd_plobj_id_equal(type, plat_type, cmd) \
+static gboolean \
+_vt_cmd_plobj_id_equal_##type (const NMPlatformObject *_obj1, const NMPlatformObject *_obj2) \
+{ \
+	const plat_type *const obj1 = (const plat_type *) _obj1; \
+	const plat_type *const obj2 = (const plat_type *) _obj2; \
+	return (cmd); \
+}
+_vt_cmd_plobj_id_equal (link, NMPlatformLink,
+                           obj1->ifindex == obj2->ifindex);
+_vt_cmd_plobj_id_equal (ip4_address, NMPlatformIP4Address,
+                           obj1->ifindex == obj2->ifindex
+                        && obj1->plen == obj2->plen
+                        && obj1->address == obj2->address);
+_vt_cmd_plobj_id_equal (ip6_address, NMPlatformIP6Address,
+                           obj1->ifindex == obj2->ifindex
+                        && obj1->plen == obj2->plen
+                        && IN6_ARE_ADDR_EQUAL (&obj1->address, &obj2->address));
+_vt_cmd_plobj_id_equal (ip4_route, NMPlatformIP4Route,
+                           obj1->ifindex == obj2->ifindex
+                        && obj1->plen == obj2->plen
+                        && obj1->metric == obj2->metric
+                        && obj1->network == obj2->network);
+_vt_cmd_plobj_id_equal (ip6_route, NMPlatformIP6Route,
+                           obj1->ifindex == obj2->ifindex
+                        && obj1->plen == obj2->plen
+                        && obj1->metric == obj2->metric
+                        && IN6_ARE_ADDR_EQUAL( &obj1->network, &obj2->network));
+
+guint
+nmp_object_id_hash (const NMPObject *obj)
+{
+	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);
+}
+
+#define _vt_cmd_plobj_id_hash(type, plat_type, cmd) \
+static guint \
+_vt_cmd_plobj_id_hash_##type (const NMPlatformObject *_obj) \
+{ \
+	const plat_type *const obj = (const plat_type *) _obj; \
+	guint hash; \
+	{ cmd; } \
+	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);
+})
+_vt_cmd_plobj_id_hash (ip6_address, NMPlatformIP6Address, {
+	hash = (guint) 2907861637u;
+	hash = hash      + ((guint) obj->ifindex);
+	hash = hash * 33 + ((guint) obj->plen);
+	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);
+	hash = hash * 33 + ((guint) obj->metric);
+	hash = hash * 33 + ((guint) obj->network);
+})
+_vt_cmd_plobj_id_hash (ip6_route, NMPlatformIP6Route, {
+	hash = (guint) 3999787007u;
+	hash = hash      + ((guint) obj->ifindex);
+	hash = hash * 33 + ((guint) obj->plen);
+	hash = hash * 33 + ((guint) obj->metric);
+	hash = hash * 33 + _id_hash_ip6_addr (&obj->network);
+})
+
+gboolean
+nmp_object_is_alive (const NMPObject *obj)
+{
+	/* for convenience, allow NULL. */
+	if (!obj)
+		return FALSE;
+
+	return  NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_alive (obj);
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_link (const NMPObject *obj)
+{
+	return obj->object.ifindex > 0 && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device);
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_ipx_address (const NMPObject *obj)
+{
+	return obj->object.ifindex > 0;
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
+{
+	/* We want to ignore routes that are RTM_F_CLONED but we still
+	 * let nmp_object_from_nl() create such route objects, instead of
+	 * returning NULL right away.
+	 *
+	 * The idea is, that if we have the same route (according to its id)
+	 * in the cache with !RTM_F_CLONED, an update that changes the route
+	 * to be RTM_F_CLONED must remove the instance.
+	 *
+	 * If nmp_object_from_nl() would just return NULL, we couldn't look
+	 * into the cache to see if it contains a route that now disappears
+	 * (because it is cloned).
+	 *
+	 * Instead we create a dead object, and nmp_cache_update_netlink()
+	 * will remove the old version of the update.
+	 **/
+	return obj->object.ifindex > 0 && (obj->ip_route.source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED);
+}
+
+gboolean
+nmp_object_is_visible (const NMPObject *obj)
+
+{
+	/* for convenience, allow NULL. */
+	if (!obj)
+		return FALSE;
+
+	return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_visible (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];
+}
+
+static gboolean
+_vt_cmd_obj_is_visible_ipx_address (const NMPObject *obj)
+{
+	return obj->object.ifindex > 0;
+}
+
+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)
+{
+	return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_nl (platform, &obj->object, id_only);
+}
+
+/******************************************************************/
+
+gboolean
+nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b)
+{
+	/* just memcmp() the entire id. This is potentially dangerous, because
+	 * the struct is not __attribute__((packed)) and not all types have the
+	 * same size. It is important, to memset() the entire struct to 0,
+	 * not only the relevant fields.
+	 *
+	 * You anyway should use the nmp_cache_id_init_*() functions on a stack-allocated
+	 * struct. */
+	return memcmp (a, b, sizeof (NMPCacheId)) == 0;
+}
+
+guint
+nmp_cache_id_hash (const NMPCacheId *id)
+{
+	guint hash = 5381;
+	guint i;
+
+	for (i = 0; i < sizeof (NMPCacheId); i++)
+		hash = ((hash << 5) + hash) + ((char *) id)[i]; /* hash * 33 + c */
+	return hash;
+}
+
+NMPCacheId *
+nmp_cache_id_clone (const NMPCacheId *id)
+{
+	NMPCacheId *id2;
+
+	id2 = g_slice_new (NMPCacheId);
+	memcpy (id2, id, sizeof (NMPCacheId));
+	return id2;
+}
+
+void
+nmp_cache_id_destroy (NMPCacheId *id)
+{
+	g_slice_free (NMPCacheId, id);
+}
+
+/******************************************************************/
+
+NMPCacheId _nmp_cache_id_static;
+
+static NMPCacheId *
+_nmp_cache_id_init (NMPCacheId *id, NMPCacheIdType id_type)
+{
+	memset (id, 0, sizeof (NMPCacheId));
+	id->_id_type = id_type;
+	return id;
+}
+
+NMPCacheId *
+nmp_cache_id_init_object_type (NMPCacheId *id, NMPObjectType obj_type, gboolean visible_only)
+{
+	_nmp_cache_id_init (id, visible_only
+	                        ? NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY
+	                        : NMP_CACHE_ID_TYPE_OBJECT_TYPE);
+	id->object_type.obj_type = obj_type;
+	return id;
+}
+
+NMPCacheId *
+nmp_cache_id_init_addrroute_visible_by_ifindex (NMPCacheId *id,
+                                                NMPObjectType obj_type,
+                                                int ifindex)
+{
+	g_return_val_if_fail (NM_IN_SET (obj_type,
+	                                 NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                 NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP6_ROUTE), NULL);
+
+	if (ifindex <= 0)
+		return nmp_cache_id_init_object_type (id, obj_type, TRUE);
+
+	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX);
+	id->object_type_by_ifindex.obj_type = obj_type;
+	id->object_type_by_ifindex.ifindex = ifindex;
+	return id;
+}
+
+NMPCacheId *
+nmp_cache_id_init_routes_visible (NMPCacheId *id,
+                                  NMPObjectType obj_type,
+                                  gboolean with_default,
+                                  gboolean with_non_default,
+                                  int ifindex)
+{
+	g_return_val_if_fail (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE), NULL);
+
+	if (with_default && with_non_default) {
+		if (ifindex <= 0)
+			return nmp_cache_id_init_object_type (id, obj_type, TRUE);
+		return nmp_cache_id_init_addrroute_visible_by_ifindex (id, obj_type, ifindex);
+	}
+
+	if (with_default)
+		_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT);
+	else if (with_non_default)
+		_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT);
+	else
+		g_return_val_if_reached (NULL);
+
+	id->object_type_by_ifindex.obj_type = obj_type;
+	id->object_type_by_ifindex.ifindex = ifindex;
+	return id;
+}
+
+/******************************************************************/
+
+static gboolean
+_nmp_object_init_cache_id (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+
+	switch (id_type) {
+	case NMP_CACHE_ID_TYPE_OBJECT_TYPE:
+		*out_id = nmp_cache_id_init_object_type (id, klass->obj_type, FALSE);
+		return TRUE;
+	case NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY:
+		if (nmp_object_is_visible (obj))
+			*out_id = nmp_cache_id_init_object_type (id, klass->obj_type, TRUE);
+		else
+			*out_id = NULL;
+		return TRUE;
+	default:
+		return 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;
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_ipx_address (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+	switch (id_type) {
+	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
+		if (_vt_cmd_obj_is_visible_ipx_address (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;
+	default:
+		return FALSE;
+	}
+	*out_id = NULL;
+	return TRUE;
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+	switch (id_type) {
+	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
+		if (_vt_cmd_obj_is_visible_ipx_route (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)
+		    && !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);
+			return TRUE;
+		}
+		break;
+	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT:
+		if (   _vt_cmd_obj_is_visible_ipx_route (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);
+			return TRUE;
+		}
+		break;
+	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT:
+		if (   _vt_cmd_obj_is_visible_ipx_route (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);
+			return TRUE;
+		}
+		break;
+	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT:
+		if (   _vt_cmd_obj_is_visible_ipx_route (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);
+			return TRUE;
+		}
+		break;
+	default:
+		return FALSE;
+	}
+	*out_id = NULL;
+	return TRUE;
+}
+
+/******************************************************************/
+
+gboolean
+nmp_cache_use_udev_detect ()
+{
+	return access ("/sys", W_OK) == 0;
+}
+
+gboolean
+nmp_cache_use_udev_get (const NMPCache *cache)
+{
+	g_return_val_if_fail (cache, TRUE);
+
+	return cache->use_udev;
+}
+
+gboolean
+nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev)
+{
+	g_return_val_if_fail (cache, FALSE);
+
+	use_udev = !!use_udev;
+	if (use_udev == cache->use_udev)
+		return FALSE;
+
+	cache->use_udev = use_udev;
+	return TRUE;
+}
+
+/******************************************************************/
+
+/**
+ * nmp_cache_link_connected_needs_toggle:
+ * @cache: the platform cache
+ * @master: the link object, that is checked whether its connected property
+ *   needs to be toggled.
+ * @potential_slave: (allow-none): an additional link object that is treated
+ *   as if it was inside @cache. If given, it shaddows a link in the cache
+ *   with the same ifindex.
+ * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave
+ *   is not in the cache.
+ *
+ * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported
+ * from netlink) and master->link.connected. For bond and bridge master, kernel reports
+ * those links as IFF_LOWER_UP if they have no slaves attached. We want to present instead
+ * a combined @connected flag that shows masters without slaves as down.
+ *
+ * Check if the connected flag of @master should be toggled according to the content
+ * of @cache (including @potential_slave).
+ *
+ * Returns: %TRUE, if @master->link.connected should be flipped/toggled.
+ **/
+gboolean
+nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave)
+{
+	const NMPlatformLink *const *links;
+	gboolean is_lower_up = FALSE;
+	guint len, i;
+
+	if (   !master
+	    || NMP_OBJECT_GET_TYPE (master) != NMP_OBJECT_TYPE_LINK
+	    || master->link.ifindex <= 0
+	    || !nmp_object_is_visible (master)
+	    || !NM_IN_SET (master->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND))
+		return FALSE;
+
+	/* if native IFF_LOWER_UP is down, link.connected must also be down
+	 * regardless of the slaves. */
+	if (!NM_FLAGS_HAS (master->link.flags, IFF_LOWER_UP))
+		return !!master->link.connected;
+
+	if (potential_slave && NMP_OBJECT_GET_TYPE (potential_slave) != NMP_OBJECT_TYPE_LINK)
+		potential_slave = NULL;
+
+	if (   potential_slave
+	    && nmp_object_is_visible (potential_slave)
+	    && potential_slave->link.ifindex > 0
+	    && potential_slave->link.master == master->link.ifindex
+	    && potential_slave->link.connected) {
+		is_lower_up = TRUE;
+	} else {
+		links = (const NMPlatformLink *const *) nmp_cache_lookup_multi (cache, nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, NMP_OBJECT_TYPE_LINK, FALSE), &len);
+		for (i = 0; i < len; i++) {
+			const NMPlatformLink *link = links[i];
+			const NMPObject *obj = NMP_OBJECT_UP_CAST ((NMPlatformObject *) link);
+
+			nm_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST ((NMPlatformObject *) link)) == NMP_OBJECT_TYPE_LINK);
+
+			if (   (!potential_slave || potential_slave->link.ifindex != link->ifindex)
+			    && ignore_slave != obj
+			    && link->ifindex > 0
+			    && link->master == master->link.ifindex
+			    && nmp_object_is_visible (obj)
+			    && link->connected) {
+				is_lower_up = TRUE;
+				break;
+			}
+		}
+	}
+	return !!master->link.connected != is_lower_up;
+}
+
+/**
+ * nmp_cache_link_connected_needs_toggle_by_ifindex:
+ * @cache:
+ * @master_ifindex: the ifindex of a potential master that should be checked
+ *   whether it needs toggling.
+ * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle().
+ *   It considers @potential_slave as being inside the cache, replacing an existing
+ *   link with the same ifindex.
+ * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle().
+ *
+ * The flag obj->link.connected depends on the state of other links in the
+ * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex
+ * of a master, check if the cache contains such a master link that needs
+ * toogling of the connected flag.
+ *
+ * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled.
+ *   Otherwise, return the link object from inside the cache with the given ifindex.
+ *   The connected flag of that master should be toggled.
+ */
+const NMPObject *
+nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave)
+{
+	const NMPObject *master;
+
+	if (master_ifindex > 0) {
+		master = nmp_cache_lookup_link (cache, master_ifindex);
+		if (nmp_cache_link_connected_needs_toggle (cache, master, potential_slave, ignore_slave))
+			return master;
+	}
+	return NULL;
+}
+
+/******************************************************************/
+
+const NMPlatformObject *const *
+nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len)
+{
+	return (const NMPlatformObject *const *) nm_multi_index_lookup (cache->idx_multi,
+	                                                                (const NMMultiIndexId *) cache_id,
+	                                                                out_len);
+}
+
+GArray *
+nmp_cache_lookup_multi_to_array (const NMPCache *cache, NMPObjectType obj_type, const NMPCacheId *cache_id)
+{
+	const NMPClass *klass = nmp_class_from_type (obj_type);
+	guint len, i;
+	const NMPlatformObject *const *objects;
+	GArray *array;
+
+	g_return_val_if_fail (klass, NULL);
+
+	objects = nmp_cache_lookup_multi (cache, cache_id, &len);
+	array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
+
+	for (i = 0; i < len; i++) {
+		nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[i])) == klass);
+		g_array_append_vals (array, objects[i], 1);
+	}
+	return array;
+}
+
+const NMPObject *
+nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj)
+{
+	g_return_val_if_fail (obj, NULL);
+
+	return g_hash_table_lookup (cache->idx_main, obj);
+}
+
+const NMPObject *
+nmp_cache_lookup_link (const NMPCache *cache, int ifindex)
+{
+	NMPObject obj_needle;
+
+	return nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
+}
+
+const NMPObject *
+nmp_cache_lookup_link_full (const NMPCache *cache,
+                            int ifindex,
+                            const char *ifname,
+                            gboolean visible_only,
+                            NMLinkType link_type,
+                            NMPObjectMatchFn match_fn,
+                            gpointer user_data)
+{
+	NMPObject obj_needle;
+	const NMPObject *obj;
+	const NMPlatformObject *const *list;
+	guint i, len;
+	NMPCacheId cache_id;
+
+	if (ifindex > 0) {
+		obj = nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
+
+		if (   !obj
+		    || (visible_only && !nmp_object_is_visible (obj))
+		    || (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
+		    || (ifname && strcmp (obj->link.name, ifname))
+		    || (match_fn && !match_fn (obj, user_data)))
+			return NULL;
+		return obj;
+	} 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);
+		for (i = 0; i < len; i++) {
+			obj = NMP_OBJECT_UP_CAST (list[i]);
+
+			if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
+				continue;
+			if (ifname && strcmp (ifname, obj->link.name))
+				continue;
+			if (match_fn && !match_fn (obj, user_data))
+				continue;
+
+			return obj;
+		}
+		return NULL;
+	}
+}
+
+GHashTable *
+nmp_cache_lookup_all_to_hash (const NMPCache *cache,
+                              NMPCacheId *cache_id,
+                              GHashTable *hash)
+{
+	NMMultiIndexIdIter iter;
+	gpointer plobj;
+
+	nm_multi_index_id_iter_init (&iter, cache->idx_multi, (const NMMultiIndexId *) cache_id);
+
+	if (nm_multi_index_id_iter_next (&iter, &plobj)) {
+		if (!hash)
+			hash = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
+
+		do {
+			g_hash_table_add (hash, nmp_object_ref (NMP_OBJECT_UP_CAST (plobj)));
+		} while (nm_multi_index_id_iter_next (&iter, &plobj));
+	}
+
+	return hash;
+}
+
+/******************************************************************/
+
+static void
+_nmp_cache_update_cache (NMPCache *cache, NMPObject *obj, gboolean remove)
+{
+	NMPCacheIdType id_type;
+
+	for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) {
+		NMPCacheId cache_id_storage;
+		const NMPCacheId *cache_id;
+
+		if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage, &cache_id))
+			continue;
+		if (!cache_id)
+			continue;
+
+		/* We don't put @obj itself into the multi index, but &obj->object. As of now, all
+		 * users expect a pointer to NMPlatformObject, not NMPObject.
+		 * You can use NMP_OBJECT_UP_CAST() to retrieve the original @obj pointer.
+		 *
+		 * If need be, we could determine based on @id_type which pointer we want to store. */
+
+		if (remove) {
+			if (!nm_multi_index_remove (cache->idx_multi, &cache_id->base, &obj->object))
+				g_assert_not_reached ();
+		} else {
+			if (!nm_multi_index_add (cache->idx_multi, &cache_id->base, &obj->object))
+				g_assert_not_reached ();
+		}
+	}
+}
+
+static void
+_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))
+		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);
+}
+
+static void
+_nmp_cache_update_remove (NMPCache *cache, NMPObject *obj)
+{
+	nm_assert (obj->is_cached);
+	_nmp_cache_update_cache (cache, obj, TRUE);
+	obj->is_cached = FALSE;
+	if (!g_hash_table_remove (cache->idx_main, obj))
+		g_assert_not_reached ();
+
+	/* @obj is possibly a dangling pointer at this point. No problem, multi-index doesn't dereference. */
+	nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
+}
+
+static void
+_nmp_cache_update_update (NMPCache *cache, NMPObject *obj, const NMPObject *new)
+{
+	NMPCacheIdType id_type;
+
+	nm_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (new));
+	nm_assert (obj->is_cached);
+	nm_assert (!new->is_cached);
+
+	for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) {
+		NMPCacheId cache_id_storage_obj, cache_id_storage_new;
+		const NMPCacheId *cache_id_obj, *cache_id_new;
+
+		if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage_obj, &cache_id_obj))
+			continue;
+		if (!_nmp_object_init_cache_id (new, id_type, &cache_id_storage_new, &cache_id_new))
+			g_assert_not_reached ();
+		if (!nm_multi_index_move (cache->idx_multi, (NMMultiIndexId *) cache_id_obj, (NMMultiIndexId *) cache_id_new, &obj->object))
+			g_assert_not_reached ();
+	}
+	nmp_object_copy (obj, new, FALSE);
+}
+
+NMPCacheOpsType
+nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+	NMPObject *old;
+
+	nm_assert (NMP_OBJECT_IS_VALID (obj));
+
+	old = g_hash_table_lookup (cache->idx_main, obj);
+	if (!old) {
+		if (out_obj)
+			*out_obj = NULL;
+		if (out_was_visible)
+			*out_was_visible = FALSE;
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
+
+	if (out_obj)
+		*out_obj = nmp_object_ref (old);
+	if (out_was_visible)
+		*out_was_visible = nmp_object_is_visible (old);
+	if (equals_by_ptr && old != obj) {
+		/* We found an identical object, but we only delete it if it's the same pointer as
+		 * @obj. */
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
+	if (pre_hook)
+		pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+	_nmp_cache_update_remove (cache, old);
+	return NMP_CACHE_OPS_REMOVED;
+}
+
+NMPCacheOpsType
+nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+	if (NMP_OBJECT_GET_TYPE (obj_needle) == NMP_OBJECT_TYPE_LINK) {
+		NMPObject *old;
+		auto_nmp_obj 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
+		 * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink
+		 * flag. */
+
+		old = (NMPObject *) nmp_cache_lookup_link (cache, obj_needle->link.ifindex);
+		if (!old) {
+			if (out_obj)
+				*out_obj = NULL;
+			if (out_was_visible)
+				*out_was_visible = FALSE;
+			return NMP_CACHE_OPS_UNCHANGED;
+		}
+
+		if (out_obj)
+			*out_obj = nmp_object_ref (old);
+		if (out_was_visible)
+			*out_was_visible = nmp_object_is_visible (old);
+
+		if (!old->_link.netlink.is_in_netlink) {
+			nm_assert (old->_link.udev.device);
+			return NMP_CACHE_OPS_UNCHANGED;
+		}
+
+		if (!old->_link.udev.device) {
+			/* the update would make @old invalid. Remove it. */
+			if (pre_hook)
+				pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+			_nmp_cache_update_remove (cache, old);
+			return NMP_CACHE_OPS_REMOVED;
+		}
+
+		obj = nmp_object_clone (old, FALSE);
+		obj->_link.netlink.is_in_netlink = FALSE;
+
+		_nmp_object_fixup_link_master_connected (obj, cache);
+		_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+
+		if (pre_hook)
+			pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+		_nmp_cache_update_update (cache, old, obj);
+		return NMP_CACHE_OPS_UPDATED;
+	} else
+		return nmp_cache_remove (cache, obj_needle, FALSE, out_obj, out_was_visible, pre_hook, user_data);
+}
+
+/**
+ * nmp_cache_update_netlink:
+ * @cache: the platform cache
+ * @obj: a #NMPObject instance as received from netlink and created via
+ *    nmp_object_from_nl(). Especially for link, it must not have the udev
+ *    replated fields set.
+ *    This instance will be modified and might be put into the cache. When
+ *    calling nmp_cache_update_netlink() you hand @obj over to the cache.
+ *    Except, that the cache will increment the ref count as appropriate. You
+ *    must still unref the obj to release your part of the ownership.
+ * @out_obj: (allow-none): (out): return the object instance that is inside
+ *    the cache. If you specify non %NULL, you must always unref the returned
+ *    instance. If the return value indicates that the object was removed,
+ *    the object is no longer in the cache. Even if the return value indicates
+ *    that the object was unchanged, it will still return @out_obj -- if
+ *    such an object is in the cache.
+ * @out_was_visible: (allow-none): (out): whether the object was visible before
+ *    the update operation.
+ * @pre_hook: (allow-none): a callback *before* the object gets updated. You cannot
+ *    influence the outcome and must not do anything beyong inspecting the changes.
+ * @user_data:
+ *
+ * Returns: how the cache changed.
+ **/
+NMPCacheOpsType
+nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+	NMPObject *old;
+
+	nm_assert (NMP_OBJECT_IS_VALID (obj));
+	nm_assert (!NMP_OBJECT_IS_STACKINIT (obj));
+	nm_assert (!obj->is_cached);
+
+	/* A link object from netlink must have the udev related fields unset.
+	 * We could implement to handle that, but there is no need to support such
+	 * a use-case */
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) != NMP_OBJECT_TYPE_LINK ||
+	           (   !obj->_link.udev.device
+	            && !obj->link.driver));
+
+	old = g_hash_table_lookup (cache->idx_main, obj);
+
+	if (out_obj)
+		*out_obj = NULL;
+	if (out_was_visible)
+		*out_was_visible = FALSE;
+
+	if (!old) {
+		if (!nmp_object_is_alive (obj))
+			return NMP_CACHE_OPS_UNCHANGED;
+
+		if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
+			_nmp_object_fixup_link_master_connected (obj, cache);
+			_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+		}
+
+		if (out_obj)
+			*out_obj = nmp_object_ref (obj);
+
+		if (pre_hook)
+			pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data);
+		_nmp_cache_update_add (cache, obj);
+		return NMP_CACHE_OPS_ADDED;
+	} else if (old == obj) {
+		/* updating a cached object inplace is not supported because the object contributes to hash-key
+		 * for NMMultiIndex. Modifying an object that is inside NMMultiIndex means that these
+		 * keys change.
+		 * The problem is, that for a given object NMMultiIndex does not support (efficient)
+		 * reverse lookup to get all the NMPCacheIds to which it belongs. If that would be implemented,
+		 * it would be possible to implement inplace-update.
+		 *
+		 * There is an un-optimized reverse lookup via nm_multi_index_iter_init(), but we don't want
+		 * that because we might have a large number of indexes to search.
+		 *
+		 * 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().
+		 *
+		 * TL;DR: a cached object must never be modified.
+		 */
+		g_assert_not_reached ();
+	} else {
+		gboolean is_alive = FALSE;
+
+		nm_assert (old->is_cached);
+
+		if (out_obj)
+			*out_obj = nmp_object_ref (old);
+		if (out_was_visible)
+			*out_was_visible = nmp_object_is_visible (old);
+
+		if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
+			if (!obj->_link.netlink.is_in_netlink) {
+				if (!old->_link.netlink.is_in_netlink) {
+					nm_assert (old->_link.udev.device);
+					return NMP_CACHE_OPS_UNCHANGED;
+				}
+				if (old->_link.udev.device) {
+					/* @obj is not in netlink.
+					 *
+					 * This is similar to nmp_cache_remove_netlink(), but there we preserve the
+					 * preexisting netlink properties. The use case of that is when kernel_get_object()
+					 * cannot load an object (based on the id of a needle).
+					 *
+					 * Here we keep the data provided from @obj. The usecase is when receiving
+					 * a valid @obj instance from netlink with RTM_DELROUTE.
+					 */
+					is_alive = TRUE;
+				}
+			} else
+				is_alive = TRUE;
+
+			if (is_alive) {
+				_nmp_object_fixup_link_master_connected (obj, cache);
+
+				/* Merge the netlink parts with what we have from udev. */
+				g_clear_object (&obj->_link.udev.device);
+				obj->_link.udev.device = old->_link.udev.device ? g_object_ref (old->_link.udev.device) : NULL;
+				_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+			}
+		} else
+			is_alive = nmp_object_is_alive (obj);
+
+		if (!is_alive) {
+			/* the update would make @old invalid. Remove it. */
+			if (pre_hook)
+				pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+			_nmp_cache_update_remove (cache, old);
+			return NMP_CACHE_OPS_REMOVED;
+		}
+
+		if (nmp_object_equal (old, obj))
+			return NMP_CACHE_OPS_UNCHANGED;
+
+		if (pre_hook)
+			pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+		_nmp_cache_update_update (cache, old, obj);
+		return NMP_CACHE_OPS_UPDATED;
+	}
+}
+
+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;
+
+	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
+
+	if (out_obj)
+		*out_obj = NULL;
+	if (out_was_visible)
+		*out_was_visible = FALSE;
+
+	if (!old) {
+		if (!udev_device)
+			return NMP_CACHE_OPS_UNCHANGED;
+
+		obj = nmp_object_new (NMP_OBJECT_TYPE_LINK, NULL);
+		obj->link.ifindex = ifindex;
+		obj->_link.udev.device = g_object_ref (udev_device);
+
+		_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+
+		nm_assert (nmp_object_is_alive (obj));
+
+		if (out_obj)
+			*out_obj = nmp_object_ref (obj);
+
+		if (pre_hook)
+			pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data);
+		_nmp_cache_update_add (cache, obj);
+		return NMP_CACHE_OPS_ADDED;
+	} else {
+		nm_assert (old->is_cached);
+
+		if (out_obj)
+			*out_obj = nmp_object_ref (old);
+		if (out_was_visible)
+			*out_was_visible = nmp_object_is_visible (old);
+
+		if (old->_link.udev.device == udev_device)
+			return NMP_CACHE_OPS_UNCHANGED;
+
+		if (!udev_device && !old->_link.netlink.is_in_netlink) {
+			/* the update would make @old invalid. Remove it. */
+			if (pre_hook)
+				pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+			_nmp_cache_update_remove (cache, old);
+			return NMP_CACHE_OPS_REMOVED;
+		}
+
+		obj = nmp_object_clone (old, FALSE);
+
+		g_clear_object (&obj->_link.udev.device);
+		obj->_link.udev.device = udev_device ? g_object_ref (udev_device) : NULL;
+
+		_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+
+		nm_assert (nmp_object_is_alive (obj));
+
+		if (pre_hook)
+			pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+		_nmp_cache_update_update (cache, old, obj);
+		return NMP_CACHE_OPS_UPDATED;
+	}
+}
+
+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;
+
+	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
+
+	if (!old) {
+		if (out_obj)
+			*out_obj = NULL;
+		if (out_was_visible)
+			*out_was_visible = FALSE;
+
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
+
+	nm_assert (old->is_cached);
+
+	if (out_obj)
+		*out_obj = nmp_object_ref (old);
+	if (out_was_visible)
+		*out_was_visible = nmp_object_is_visible (old);
+
+	if (!nmp_cache_link_connected_needs_toggle (cache, old, NULL, NULL))
+		return NMP_CACHE_OPS_UNCHANGED;
+
+	obj = nmp_object_clone (old, FALSE);
+	obj->link.connected = !old->link.connected;
+
+	nm_assert (nmp_object_is_alive (obj));
+
+	if (pre_hook)
+		pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+	_nmp_cache_update_update (cache, old, obj);
+	return NMP_CACHE_OPS_UPDATED;
+}
+
+/******************************************************************/
+
+NMPCache *
+nmp_cache_new ()
+{
+	NMPCache *cache = g_new (NMPCache, 1);
+
+	cache->idx_main = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+	                                         (GEqualFunc) nmp_object_id_equal,
+	                                         (GDestroyNotify) nmp_object_unref,
+	                                         NULL);
+	cache->idx_multi = nm_multi_index_new ((NMMultiIndexFuncHash) nmp_cache_id_hash,
+	                                       (NMMultiIndexFuncEqual) nmp_cache_id_equal,
+	                                       (NMMultiIndexFuncClone) nmp_cache_id_clone,
+	                                       (NMMultiIndexFuncDestroy) nmp_cache_id_destroy);
+	cache->use_udev = nmp_cache_use_udev_detect ();
+	return cache;
+}
+
+void
+nmp_cache_free (NMPCache *cache)
+{
+	GHashTableIter iter;
+	NMPObject *obj;
+
+	/* No need to cumbersomely remove the objects properly. They are not hooked up
+	 * in a complicated way, we can just unref them together with cache->idx_main.
+	 *
+	 * But we must clear the @is_cached flag. */
+	g_hash_table_iter_init (&iter, cache->idx_main);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
+		nm_assert (obj->is_cached);
+		obj->is_cached = FALSE;
+	}
+
+	nm_multi_index_free (cache->idx_multi);
+	g_hash_table_unref (cache->idx_main);
+
+	g_free (cache);
+}
+
+/******************************************************************/
+
+void
+ASSERT_nmp_cache_is_consistent (const NMPCache *cache)
+{
+#ifdef NM_MORE_ASSERTS
+	NMMultiIndexIter iter_multi;
+	GHashTableIter iter_hash;
+	guint i, len;
+	NMPCacheId cache_id_storage;
+	const NMPCacheId *cache_id, *cache_id2;
+	const NMPlatformObject *const *objects;
+	const NMPObject *obj;
+
+	g_assert (cache);
+
+	g_hash_table_iter_init (&iter_hash, cache->idx_main);
+	while (g_hash_table_iter_next (&iter_hash, (gpointer *) &obj, NULL)) {
+		NMPCacheIdType id_type;
+
+		g_assert (NMP_OBJECT_IS_VALID (obj));
+		g_assert (nmp_object_is_alive (obj));
+
+		for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) {
+			if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage, &cache_id))
+				continue;
+			if (!cache_id)
+				continue;
+			g_assert (nm_multi_index_contains (cache->idx_multi, &cache_id->base, &obj->object));
+		}
+	}
+
+	nm_multi_index_iter_init (&iter_multi, cache->idx_multi, NULL);
+	while (nm_multi_index_iter_next (&iter_multi,
+	                                 (const NMMultiIndexId **) &cache_id,
+	                                 (void *const**) &objects,
+	                                 &len)) {
+		g_assert (len > 0 && objects && objects[len] == NULL);
+
+		for (i = 0; i < len; i++) {
+			g_assert (objects[i]);
+			obj = NMP_OBJECT_UP_CAST (objects[i]);
+			g_assert (NMP_OBJECT_IS_VALID (obj));
+
+			/* for now, enforce that all objects for a certain index are of the same type. */
+			g_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[0])));
+
+			if (!_nmp_object_init_cache_id (obj, cache_id->_id_type, &cache_id_storage, &cache_id2))
+				g_assert_not_reached ();
+			g_assert (cache_id2);
+			g_assert (nmp_cache_id_equal (cache_id, cache_id2));
+			g_assert_cmpint (nmp_cache_id_hash (cache_id), ==, nmp_cache_id_hash (cache_id2));
+
+			g_assert (obj == g_hash_table_lookup (cache->idx_main, obj));
+		}
+	}
+#endif
+}
+/******************************************************************/
+
+const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
+	[NMP_OBJECT_TYPE_LINK - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LINK,
+		.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                        = 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_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_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_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp,
+	},
+	[NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_IP4_ADDRESS,
+		.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                        = 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_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp,
+	},
+	[NMP_OBJECT_TYPE_IP6_ADDRESS - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_IP6_ADDRESS,
+		.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                        = 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_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp
+	},
+	[NMP_OBJECT_TYPE_IP4_ROUTE - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_IP4_ROUTE,
+		.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                        = 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_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp,
+	},
+	[NMP_OBJECT_TYPE_IP6_ROUTE - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_IP6_ROUTE,
+		.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                        = 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_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp,
+	},
+};
+
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
new file mode 100644
index 00000000..5b1887b8
--- /dev/null
+++ b/src/platform/nmp-object.h
@@ -0,0 +1,361 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NMP_OBJECT_H__
+#define __NMP_OBJECT_H__
+
+#include "config.h"
+
+#include "nm-platform.h"
+#include "nm-multi-index.h"
+#include "nm-macros-internal.h"
+
+#include <netlink/netlink.h>
+#include <gudev/gudev.h>
+
+
+typedef enum { /*< skip >*/
+	NMP_OBJECT_TO_STRING_ID,
+	NMP_OBJECT_TO_STRING_PUBLIC,
+	NMP_OBJECT_TO_STRING_ALL,
+} NMPObjectToStringMode;
+
+typedef enum { /*< skip >*/
+	NMP_CACHE_OPS_UNCHANGED       = NM_PLATFORM_SIGNAL_NONE,
+	NMP_CACHE_OPS_UPDATED         = NM_PLATFORM_SIGNAL_CHANGED,
+	NMP_CACHE_OPS_ADDED           = NM_PLATFORM_SIGNAL_ADDED,
+	NMP_CACHE_OPS_REMOVED         = NM_PLATFORM_SIGNAL_REMOVED,
+} NMPCacheOpsType;
+
+/* The NMPCacheIdType are the different index types.
+ *
+ * An object of a certain object-type, can be candidate to being
+ * indexed by a certain NMPCacheIdType or not. For example, all
+ * objects are indexed via an index of type NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+ * but only route objects can be indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT.
+ *
+ * Of one index type, there can be multiple indexes or not.
+ * For example, of the index type NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX there
+ * are multiple instances (for different route/addresses, v4/v6, per-ifindex).
+ *
+ * But one object, can only be indexed by one particular index of a
+ * type. For example, a certain address instance is only indexed by
+ * the index NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX with
+ * matching v4/v6 and ifindex -- or maybe not at all if it isn't visible.
+ * */
+typedef enum { /*< skip >*/
+	/* all the objects of a certain type */
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+
+	/* all the visible objects of a certain type */
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
+
+	/* indeces for the visible routes, ignoring ifindex. */
+	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT,
+	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT,
+
+	/* all the visible addresses/routes (by object-type) for an ifindex. */
+	NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX,
+
+	/* three indeces for the visible routes, per ifindex. */
+	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT,
+	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT,
+
+	__NMP_CACHE_ID_TYPE_MAX,
+	NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1,
+} NMPCacheIdType;
+
+typedef struct _NMPObject NMPObject;
+
+typedef struct {
+	union {
+		NMMultiIndexId base;
+		guint8 _id_type; /* NMPCacheIdType as guint8 */
+		struct {
+			/* NMP_CACHE_ID_TYPE_OBJECT_TYPE */
+			/* NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY */
+			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT */
+			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT */
+			guint8 _id_type;
+			guint8 obj_type; /* NMPObjectType as guint8 */
+		} object_type;
+		struct {
+			/* NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX */
+			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT */
+			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT */
+			guint8 _id_type;
+			guint8 obj_type; /* NMPObjectType as guint8 */
+			int ifindex;
+		} object_type_by_ifindex;
+	};
+} NMPCacheId;
+
+extern NMPCacheId _nmp_cache_id_static;
+#define NMP_CACHE_ID_STATIC  (&_nmp_cache_id_static)
+
+typedef struct {
+	NMPObjectType obj_type;
+	int addr_family;
+	int rtm_gettype;
+	int sizeof_data;
+	int sizeof_public;
+	const char *obj_type_name;
+	const char *nl_type;
+	const char *signal_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);
+	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);
+
+	/* 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);
+	int (*cmd_plobj_cmp) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
+} NMPClass;
+
+extern const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX];
+
+typedef struct {
+	NMPlatformLink _public;
+
+	struct {
+		guint8 is_in_netlink;
+	} netlink;
+
+	struct {
+		GUdevDevice *device;
+	} udev;
+} NMPObjectLink;
+
+typedef struct {
+	NMPlatformIP4Address _public;
+} NMPObjectIP4Address;
+
+typedef struct {
+	NMPlatformIP4Route _public;
+} NMPObjectIP4Route;
+
+typedef struct {
+	NMPlatformIP6Address _public;
+} NMPObjectIP6Address;
+
+typedef struct {
+	NMPlatformIP6Route _public;
+} NMPObjectIP6Route;
+
+struct _NMPObject {
+	const NMPClass *_class;
+	int _ref_count;
+	guint8 is_cached;
+	union {
+		NMPlatformObject        object;
+
+		NMPlatformLink          link;
+		NMPObjectLink           _link;
+
+		NMPlatformIPAddress     ip_address;
+		NMPlatformIPXAddress    ipx_address;
+		NMPlatformIP4Address    ip4_address;
+		NMPlatformIP6Address    ip6_address;
+		NMPObjectIP4Address     _ip4_address;
+		NMPObjectIP6Address     _ip6_address;
+
+		NMPlatformIPRoute       ip_route;
+		NMPlatformIPXRoute      ipx_route;
+		NMPlatformIP4Route      ip4_route;
+		NMPlatformIP6Route      ip6_route;
+		NMPObjectIP4Route       _ip4_route;
+		NMPObjectIP6Route       _ip6_route;
+	};
+};
+
+static inline gboolean
+NMP_CLASS_IS_VALID (const NMPClass *klass)
+{
+	return klass >= &_nmp_classes[0]
+	    && klass <= &_nmp_classes[G_N_ELEMENTS (_nmp_classes)]
+	    && ((((char *) klass) - ((char *) NULL)) % (&_nmp_classes[1] - &_nmp_classes[0])) == 0;
+}
+
+#define NMP_REF_COUNT_STACKINIT (G_MAXINT)
+
+static inline NMPObject *
+NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj)
+{
+	NMPObject *obj;
+
+	obj = plobj
+	      ? (NMPObject *) ( &(((char *) plobj)[-((int) G_STRUCT_OFFSET (NMPObject, object))]) )
+	      : NULL;
+	nm_assert (!obj || (obj->_ref_count > 0 && NMP_CLASS_IS_VALID (obj->_class)));
+	return obj;
+}
+#define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST ((const NMPlatformObject *) (plobj)))
+
+static inline gboolean
+NMP_OBJECT_IS_VALID (const NMPObject *obj)
+{
+	nm_assert (!obj || (   obj
+	                    && obj->_ref_count > 0
+	                    && NMP_CLASS_IS_VALID (obj->_class)));
+
+	/* There isn't really much to check. Either @obj is NULL, or we must
+	 * assume that it points to valid memory. */
+	return obj != NULL;
+}
+
+static inline gboolean
+NMP_OBJECT_IS_STACKINIT (const NMPObject *obj)
+{
+	nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
+
+	return obj && obj->_ref_count == NMP_REF_COUNT_STACKINIT;
+}
+
+static inline const NMPClass *
+NMP_OBJECT_GET_CLASS (const NMPObject *obj)
+{
+	nm_assert (NMP_OBJECT_IS_VALID (obj));
+
+	return obj->_class;
+}
+
+static inline NMPObjectType
+NMP_OBJECT_GET_TYPE (const NMPObject *obj)
+{
+	nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
+
+	return obj ? obj->_class->obj_type : NMP_OBJECT_TYPE_UNKNOWN;
+}
+
+
+
+const NMPClass *nmp_class_from_type (NMPObjectType obj_type);
+
+NMPObject *nmp_object_ref (NMPObject *object);
+void nmp_object_unref (NMPObject *object);
+NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob);
+NMPObject *nmp_object_new_link (int ifindex);
+
+const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, 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_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);
+
+const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size);
+int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2);
+gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2);
+void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only);
+NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only);
+gboolean nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2);
+guint nmp_object_id_hash (const NMPObject *obj);
+gboolean nmp_object_is_alive (const NMPObject *obj);
+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)))
+static inline void
+_nmp_auto_obj_cleanup (NMPObject **pobj)
+{
+	nmp_object_unref (*pobj);
+}
+
+typedef struct _NMPCache NMPCache;
+
+typedef void (*NMPCachePreHook) (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
+typedef gboolean (*NMPObjectMatchFn) (const NMPObject *obj, gpointer user_data);
+
+gboolean nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b);
+guint nmp_cache_id_hash (const NMPCacheId *id);
+NMPCacheId *nmp_cache_id_clone (const NMPCacheId *id);
+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);
+
+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);
+const NMPObject *nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj);
+const NMPObject *nmp_cache_lookup_link (const NMPCache *cache, int ifindex);
+
+const NMPObject *nmp_cache_lookup_link_full (const NMPCache *cache,
+                                             int ifindex,
+                                             const char *ifname,
+                                             gboolean visible_only,
+                                             NMLinkType link_type,
+                                             NMPObjectMatchFn match_fn,
+                                             gpointer user_data);
+GHashTable *nmp_cache_lookup_all_to_hash (const NMPCache *cache,
+                                          NMPCacheId *cache_id,
+                                          GHashTable *hash);
+
+gboolean nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave);
+const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave);
+
+gboolean nmp_cache_use_udev_detect (void);
+gboolean nmp_cache_use_udev_get (const NMPCache *cache);
+gboolean nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev);
+
+void ASSERT_nmp_cache_is_consistent (const NMPCache *cache);
+
+NMPCacheOpsType nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+NMPCacheOpsType nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+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);
+NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+
+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 c10600eb..54844ec1 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -37,6 +37,8 @@ noinst_PROGRAMS = \
 	test-link-linux \
 	test-address-fake \
 	test-address-linux \
+	test-general \
+	test-nmp-object \
 	test-route-fake \
 	test-route-linux \
 	test-cleanup-fake \
@@ -109,7 +111,27 @@ test_cleanup_linux_CPPFLAGS = \
 	-DKERNEL_HACKS=1
 test_cleanup_linux_LDADD = $(PLATFORM_LDADD)
 
-@VALGRIND_RULES@
-TESTS = test-link-fake test-address-fake test-route-fake test-cleanup-fake test-address-linux test-route-linux test-cleanup-linux
+test_nmp_object_SOURCES = \
+	test-nmp-object.c
+test_nmp_object_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
+test_general_SOURCES = \
+	test-general.c
+test_general_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
 
+@VALGRIND_RULES@
+TESTS = \
+	test-address-fake \
+	test-address-linux \
+	test-cleanup-fake \
+	test-cleanup-linux \
+	test-general \
+	test-link-fake \
+	test-link-linux \
+	test-nmp-object \
+	test-route-fake \
+	test-route-linux
 
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index 16a48430..a9333539 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -92,12 +92,14 @@ host_triplet = @host@
 noinst_PROGRAMS = dump$(EXEEXT) monitor$(EXEEXT) platform$(EXEEXT) \
 	test-link-fake$(EXEEXT) test-link-linux$(EXEEXT) \
 	test-address-fake$(EXEEXT) test-address-linux$(EXEEXT) \
+	test-general$(EXEEXT) test-nmp-object$(EXEEXT) \
 	test-route-fake$(EXEEXT) test-route-linux$(EXEEXT) \
 	test-cleanup-fake$(EXEEXT) test-cleanup-linux$(EXEEXT)
-TESTS = test-link-fake$(EXEEXT) test-address-fake$(EXEEXT) \
-	test-route-fake$(EXEEXT) test-cleanup-fake$(EXEEXT) \
-	test-address-linux$(EXEEXT) test-route-linux$(EXEEXT) \
-	test-cleanup-linux$(EXEEXT)
+TESTS = test-address-fake$(EXEEXT) test-address-linux$(EXEEXT) \
+	test-cleanup-fake$(EXEEXT) test-cleanup-linux$(EXEEXT) \
+	test-general$(EXEEXT) test-link-fake$(EXEEXT) \
+	test-link-linux$(EXEEXT) test-nmp-object$(EXEEXT) \
+	test-route-fake$(EXEEXT) test-route-linux$(EXEEXT)
 subdir = src/platform/tests
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
@@ -172,6 +174,9 @@ am_test_cleanup_linux_OBJECTS =  \
 	test_cleanup_linux-test-cleanup.$(OBJEXT) $(am__objects_9)
 test_cleanup_linux_OBJECTS = $(am_test_cleanup_linux_OBJECTS)
 test_cleanup_linux_DEPENDENCIES = $(PLATFORM_LDADD)
+am_test_general_OBJECTS = test-general.$(OBJEXT)
+test_general_OBJECTS = $(am_test_general_OBJECTS)
+test_general_DEPENDENCIES = $(top_builddir)/src/libNetworkManager.la
 am__objects_10 = test_link_fake-nm-platform.$(OBJEXT) \
 	test_link_fake-nm-fake-platform.$(OBJEXT) \
 	test_link_fake-nm-linux-platform.$(OBJEXT)
@@ -190,6 +195,10 @@ am_test_link_linux_OBJECTS = test_link_linux-test-link.$(OBJEXT) \
 	$(am__objects_13)
 test_link_linux_OBJECTS = $(am_test_link_linux_OBJECTS)
 test_link_linux_DEPENDENCIES = $(PLATFORM_LDADD)
+am_test_nmp_object_OBJECTS = test-nmp-object.$(OBJEXT)
+test_nmp_object_OBJECTS = $(am_test_nmp_object_OBJECTS)
+test_nmp_object_DEPENDENCIES =  \
+	$(top_builddir)/src/libNetworkManager.la
 am__objects_14 = test_route_fake-nm-platform.$(OBJEXT) \
 	test_route_fake-nm-fake-platform.$(OBJEXT) \
 	test_route_fake-nm-linux-platform.$(OBJEXT)
@@ -245,12 +254,14 @@ am__v_CCLD_1 =
 SOURCES = $(dump_SOURCES) $(monitor_SOURCES) $(platform_SOURCES) \
 	$(test_address_fake_SOURCES) $(test_address_linux_SOURCES) \
 	$(test_cleanup_fake_SOURCES) $(test_cleanup_linux_SOURCES) \
-	$(test_link_fake_SOURCES) $(test_link_linux_SOURCES) \
+	$(test_general_SOURCES) $(test_link_fake_SOURCES) \
+	$(test_link_linux_SOURCES) $(test_nmp_object_SOURCES) \
 	$(test_route_fake_SOURCES) $(test_route_linux_SOURCES)
 DIST_SOURCES = $(dump_SOURCES) $(monitor_SOURCES) $(platform_SOURCES) \
 	$(test_address_fake_SOURCES) $(test_address_linux_SOURCES) \
 	$(test_cleanup_fake_SOURCES) $(test_cleanup_linux_SOURCES) \
-	$(test_link_fake_SOURCES) $(test_link_linux_SOURCES) \
+	$(test_general_SOURCES) $(test_link_fake_SOURCES) \
+	$(test_link_linux_SOURCES) $(test_nmp_object_SOURCES) \
 	$(test_route_fake_SOURCES) $(test_route_linux_SOURCES)
 am__can_run_installinfo = \
   case $$AM_UPDATE_INFO_DIR in \
@@ -298,13 +309,196 @@ am__tty_colors = { \
     std=''; \
   fi; \
 }
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__recheck_rx = ^[ 	]*:recheck:[ 	]*
+am__global_test_result_rx = ^[ 	]*:global-test-result:[ 	]*
+am__copy_in_global_log_rx = ^[ 	]*:copy-in-global-log:[ 	]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+  recheck = 1; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+        { \
+          if ((getline line2 < ($$0 ".log")) < 0) \
+	    recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+        { \
+          recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+        { \
+          break; \
+        } \
+    }; \
+  if (recheck) \
+    print $$0; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+  print "fatal: making $@: " msg | "cat >&2"; \
+  exit 1; \
+} \
+function rst_section(header) \
+{ \
+  print header; \
+  len = length(header); \
+  for (i = 1; i <= len; i = i + 1) \
+    printf "="; \
+  printf "\n\n"; \
+} \
+{ \
+  copy_in_global_log = 1; \
+  global_test_result = "RUN"; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+         fatal("failed to read from " $$0 ".trs"); \
+      if (line ~ /$(am__global_test_result_rx)/) \
+        { \
+          sub("$(am__global_test_result_rx)", "", line); \
+          sub("[ 	]*$$", "", line); \
+          global_test_result = line; \
+        } \
+      else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+        copy_in_global_log = 0; \
+    }; \
+  if (copy_in_global_log) \
+    { \
+      rst_section(global_test_result ": " $$0); \
+      while ((rc = (getline line < ($$0 ".log"))) != 0) \
+      { \
+        if (rc < 0) \
+          fatal("failed to read from " $$0 ".log"); \
+        print line; \
+      }; \
+      printf "\n"; \
+    }; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/   &   /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this.  Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+  --color-tests "$$am__color_tests" \
+  --enable-hard-errors "$$am__enable_hard_errors" \
+  --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test.  Creates the
+# directory for the log if needed.  Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log.  Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT.  Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup);					\
+$(am__vpath_adj_setup) $(am__vpath_adj)			\
+$(am__tty_colors);					\
+srcdir=$(srcdir); export srcdir;			\
+case "$@" in						\
+  */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;;	\
+    *) am__odir=.;; 					\
+esac;							\
+test "x$$am__odir" = x"." || test -d "$$am__odir" 	\
+  || $(MKDIR_P) "$$am__odir" || exit $$?;		\
+if test -f "./$$f"; then dir=./;			\
+elif test -f "$$f"; then dir=;				\
+else dir="$(srcdir)/"; fi;				\
+tst=$$dir$$f; log='$@'; 				\
+if test -n '$(DISABLE_HARD_ERRORS)'; then		\
+  am__enable_hard_errors=no; 				\
+else							\
+  am__enable_hard_errors=yes; 				\
+fi; 							\
+case " $(XFAIL_TESTS) " in				\
+  *[\ \	]$$f[\ \	]* | *[\ \	]$$dir$$f[\ \	]*) \
+    am__expect_failure=yes;;				\
+  *)							\
+    am__expect_failure=no;;				\
+esac; 							\
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed).  The result is saved in the shell variable
+# '$bases'.  This honors runtime overriding of TESTS and TEST_LOGS.  Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+  bases='$(TEST_LOGS)'; \
+  bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+  bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+  case '$@' in \
+    */*) \
+      case '$*' in \
+        */*) b='$*';; \
+          *) b=`echo '$@' | sed 's/\.log$$//'`; \
+       esac;; \
+    *) \
+      b='$*';; \
+  esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+	$(TEST_LOG_FLAGS)
 am__DIST_COMMON = $(srcdir)/Makefile.in \
-	$(top_srcdir)/build-aux/depcomp
+	$(top_srcdir)/build-aux/depcomp \
+	$(top_srcdir)/build-aux/test-driver
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -418,6 +612,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
@@ -642,10 +837,22 @@ test_cleanup_linux_CPPFLAGS = \
 	-DKERNEL_HACKS=1
 
 test_cleanup_linux_LDADD = $(PLATFORM_LDADD)
+test_nmp_object_SOURCES = \
+	test-nmp-object.c
+
+test_nmp_object_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
+test_general_SOURCES = \
+	test-general.c
+
+test_general_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
 all: all-am
 
 .SUFFIXES:
-.SUFFIXES: .c .lo .o .obj
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
 $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
 	@for dep in $?; do \
 	  case '$(am__configure_deps)' in \
@@ -713,6 +920,10 @@ test-cleanup-linux$(EXEEXT): $(test_cleanup_linux_OBJECTS) $(test_cleanup_linux_
 	@rm -f test-cleanup-linux$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_cleanup_linux_OBJECTS) $(test_cleanup_linux_LDADD) $(LIBS)
 
+test-general$(EXEEXT): $(test_general_OBJECTS) $(test_general_DEPENDENCIES) $(EXTRA_test_general_DEPENDENCIES) 
+	@rm -f test-general$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(test_general_OBJECTS) $(test_general_LDADD) $(LIBS)
+
 test-link-fake$(EXEEXT): $(test_link_fake_OBJECTS) $(test_link_fake_DEPENDENCIES) $(EXTRA_test_link_fake_DEPENDENCIES) 
 	@rm -f test-link-fake$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_link_fake_OBJECTS) $(test_link_fake_LDADD) $(LIBS)
@@ -721,6 +932,10 @@ test-link-linux$(EXEEXT): $(test_link_linux_OBJECTS) $(test_link_linux_DEPENDENC
 	@rm -f test-link-linux$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_link_linux_OBJECTS) $(test_link_linux_LDADD) $(LIBS)
 
+test-nmp-object$(EXEEXT): $(test_nmp_object_OBJECTS) $(test_nmp_object_DEPENDENCIES) $(EXTRA_test_nmp_object_DEPENDENCIES) 
+	@rm -f test-nmp-object$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(test_nmp_object_OBJECTS) $(test_nmp_object_LDADD) $(LIBS)
+
 test-route-fake$(EXEEXT): $(test_route_fake_OBJECTS) $(test_route_fake_DEPENDENCIES) $(EXTRA_test_route_fake_DEPENDENCIES) 
 	@rm -f test-route-fake$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_route_fake_OBJECTS) $(test_route_fake_LDADD) $(LIBS)
@@ -741,6 +956,8 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-linux-platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/platform.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-general.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-nmp-object.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_address_fake-nm-fake-platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_address_fake-nm-linux-platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_address_fake-nm-platform.Po@am__quote@
@@ -1463,98 +1680,231 @@ cscopelist-am: $(am__tagged_files)
 distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
-check-TESTS: $(TESTS)
-	@failed=0; all=0; xfail=0; xpass=0; skip=0; \
-	srcdir=$(srcdir); export srcdir; \
-	list=' $(TESTS) '; \
-	$(am__tty_colors); \
-	if test -n "$$list"; then \
-	  for tst in $$list; do \
-	    if test -f ./$$tst; then dir=./; \
-	    elif test -f $$tst; then dir=; \
-	    else dir="$(srcdir)/"; fi; \
-	    if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xpass=`expr $$xpass + 1`; \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=XPASS; \
-	      ;; \
-	      *) \
-		col=$$grn; res=PASS; \
-	      ;; \
-	      esac; \
-	    elif test $$? -ne 77; then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xfail=`expr $$xfail + 1`; \
-		col=$$lgn; res=XFAIL; \
-	      ;; \
-	      *) \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=FAIL; \
-	      ;; \
-	      esac; \
-	    else \
-	      skip=`expr $$skip + 1`; \
-	      col=$$blu; res=SKIP; \
-	    fi; \
-	    echo "$${col}$$res$${std}: $$tst"; \
-	  done; \
-	  if test "$$all" -eq 1; then \
-	    tests="test"; \
-	    All=""; \
-	  else \
-	    tests="tests"; \
-	    All="All "; \
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'.  Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+	rm -f $< $@
+	$(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+	@:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+	@$(am__set_TESTS_bases); \
+	am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+	redo_bases=`for i in $$bases; do \
+	              am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+	            done`; \
+	if test -n "$$redo_bases"; then \
+	  redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+	  redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+	  if $(am__make_dryrun); then :; else \
+	    rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
 	  fi; \
-	  if test "$$failed" -eq 0; then \
-	    if test "$$xfail" -eq 0; then \
-	      banner="$$All$$all $$tests passed"; \
-	    else \
-	      if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
-	      banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
-	    fi; \
-	  else \
-	    if test "$$xpass" -eq 0; then \
-	      banner="$$failed of $$all $$tests failed"; \
+	fi; \
+	if test -n "$$am__remaking_logs"; then \
+	  echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+	       "recursion detected" >&2; \
+	elif test -n "$$redo_logs"; then \
+	  am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+	fi; \
+	if $(am__make_dryrun); then :; else \
+	  st=0;  \
+	  errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+	  for i in $$redo_bases; do \
+	    test -f $$i.trs && test -r $$i.trs \
+	      || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+	    test -f $$i.log && test -r $$i.log \
+	      || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+	  done; \
+	  test $$st -eq 0 || exit 1; \
+	fi
+	@$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+	ws='[ 	]'; \
+	results=`for b in $$bases; do echo $$b.trs; done`; \
+	test -n "$$results" || results=/dev/null; \
+	all=`  grep "^$$ws*:test-result:"           $$results | wc -l`; \
+	pass=` grep "^$$ws*:test-result:$$ws*PASS"  $$results | wc -l`; \
+	fail=` grep "^$$ws*:test-result:$$ws*FAIL"  $$results | wc -l`; \
+	skip=` grep "^$$ws*:test-result:$$ws*SKIP"  $$results | wc -l`; \
+	xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+	xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+	error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+	if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+	  success=true; \
+	else \
+	  success=false; \
+	fi; \
+	br='==================='; br=$$br$$br$$br$$br; \
+	result_count () \
+	{ \
+	    if test x"$$1" = x"--maybe-color"; then \
+	      maybe_colorize=yes; \
+	    elif test x"$$1" = x"--no-color"; then \
+	      maybe_colorize=no; \
 	    else \
-	      if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
-	      banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+	      echo "$@: invalid 'result_count' usage" >&2; exit 4; \
 	    fi; \
-	  fi; \
-	  dashes="$$banner"; \
-	  skipped=""; \
-	  if test "$$skip" -ne 0; then \
-	    if test "$$skip" -eq 1; then \
-	      skipped="($$skip test was not run)"; \
+	    shift; \
+	    desc=$$1 count=$$2; \
+	    if test $$maybe_colorize = yes && test $$count -gt 0; then \
+	      color_start=$$3 color_end=$$std; \
 	    else \
-	      skipped="($$skip tests were not run)"; \
+	      color_start= color_end=; \
 	    fi; \
-	    test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$skipped"; \
-	  fi; \
-	  report=""; \
-	  if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
-	    report="Please report to $(PACKAGE_BUGREPORT)"; \
-	    test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$report"; \
-	  fi; \
-	  dashes=`echo "$$dashes" | sed s/./=/g`; \
-	  if test "$$failed" -eq 0; then \
-	    col="$$grn"; \
-	  else \
-	    col="$$red"; \
-	  fi; \
-	  echo "$${col}$$dashes$${std}"; \
-	  echo "$${col}$$banner$${std}"; \
-	  test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
-	  test -z "$$report" || echo "$${col}$$report$${std}"; \
-	  echo "$${col}$$dashes$${std}"; \
-	  test "$$failed" -eq 0; \
-	else :; fi
+	    echo "$${color_start}# $$desc $$count$${color_end}"; \
+	}; \
+	create_testsuite_report () \
+	{ \
+	  result_count $$1 "TOTAL:" $$all   "$$brg"; \
+	  result_count $$1 "PASS: " $$pass  "$$grn"; \
+	  result_count $$1 "SKIP: " $$skip  "$$blu"; \
+	  result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+	  result_count $$1 "FAIL: " $$fail  "$$red"; \
+	  result_count $$1 "XPASS:" $$xpass "$$red"; \
+	  result_count $$1 "ERROR:" $$error "$$mgn"; \
+	}; \
+	{								\
+	  echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" |	\
+	    $(am__rst_title);						\
+	  create_testsuite_report --no-color;				\
+	  echo;								\
+	  echo ".. contents:: :depth: 2";				\
+	  echo;								\
+	  for b in $$bases; do echo $$b; done				\
+	    | $(am__create_global_log);					\
+	} >$(TEST_SUITE_LOG).tmp || exit 1;				\
+	mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG);			\
+	if $$success; then						\
+	  col="$$grn";							\
+	 else								\
+	  col="$$red";							\
+	  test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG);		\
+	fi;								\
+	echo "$${col}$$br$${std}"; 					\
+	echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}";	\
+	echo "$${col}$$br$${std}"; 					\
+	create_testsuite_report --maybe-color;				\
+	echo "$$col$$br$$std";						\
+	if $$success; then :; else					\
+	  echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}";		\
+	  if test -n "$(PACKAGE_BUGREPORT)"; then			\
+	    echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}";	\
+	  fi;								\
+	  echo "$$col$$br$$std";					\
+	fi;								\
+	$$success || exit 1
+
+check-TESTS:
+	@list='$(RECHECK_LOGS)';           test -z "$$list" || rm -f $$list
+	@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+	exit $$?;
+recheck: all 
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	bases=`for i in $$bases; do echo $$i; done \
+	         | $(am__list_recheck_tests)` || exit 1; \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	log_list=`echo $$log_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+	        am__force_recheck=am--force-recheck \
+	        TEST_LOGS="$$log_list"; \
+	exit $$?
+test-address-fake.log: test-address-fake$(EXEEXT)
+	@p='test-address-fake$(EXEEXT)'; \
+	b='test-address-fake'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-address-linux.log: test-address-linux$(EXEEXT)
+	@p='test-address-linux$(EXEEXT)'; \
+	b='test-address-linux'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-cleanup-fake.log: test-cleanup-fake$(EXEEXT)
+	@p='test-cleanup-fake$(EXEEXT)'; \
+	b='test-cleanup-fake'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-cleanup-linux.log: test-cleanup-linux$(EXEEXT)
+	@p='test-cleanup-linux$(EXEEXT)'; \
+	b='test-cleanup-linux'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-general.log: test-general$(EXEEXT)
+	@p='test-general$(EXEEXT)'; \
+	b='test-general'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-link-fake.log: test-link-fake$(EXEEXT)
+	@p='test-link-fake$(EXEEXT)'; \
+	b='test-link-fake'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-link-linux.log: test-link-linux$(EXEEXT)
+	@p='test-link-linux$(EXEEXT)'; \
+	b='test-link-linux'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-nmp-object.log: test-nmp-object$(EXEEXT)
+	@p='test-nmp-object$(EXEEXT)'; \
+	b='test-nmp-object'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-route-fake.log: test-route-fake$(EXEEXT)
+	@p='test-route-fake$(EXEEXT)'; \
+	b='test-route-fake'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-route-linux.log: test-route-linux$(EXEEXT)
+	@p='test-route-linux$(EXEEXT)'; \
+	b='test-route-linux'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+	@p='$<'; \
+	$(am__set_b); \
+	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@	@p='$<'; \
+@am__EXEEXT_TRUE@	$(am__set_b); \
+@am__EXEEXT_TRUE@	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@	--log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@	"$$tst" $(AM_TESTS_FD_REDIRECT)
 
 distdir: $(DISTFILES)
 	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
@@ -1611,6 +1961,9 @@ install-strip:
 	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
 	fi
 mostlyclean-generic:
+	-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+	-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+	-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
 
 clean-generic:
 
@@ -1706,7 +2059,7 @@ uninstall-am:
 	installcheck-am installdirs maintainer-clean \
 	maintainer-clean-generic mostlyclean mostlyclean-compile \
 	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
-	tags tags-am uninstall uninstall-am
+	recheck tags tags-am uninstall uninstall-am
 
 .PRECIOUS: Makefile
 
diff --git a/src/platform/tests/dump.c b/src/platform/tests/dump.c
index 3bb61da4..54de1da7 100644
--- a/src/platform/tests/dump.c
+++ b/src/platform/tests/dump.c
@@ -7,6 +7,7 @@
 #include "nm-platform.h"
 #include "nm-linux-platform.h"
 #include "nm-fake-platform.h"
+#include "nm-macros-internal.h"
 
 static void
 dump_interface (NMPlatformLink *link)
@@ -26,14 +27,14 @@ dump_interface (NMPlatformLink *link)
 	size_t addrlen;
 	int i;
 
-	g_assert (link->up || !link->connected);
+	g_assert (NM_FLAGS_HAS (link->flags, IFF_UP) || !link->connected);
 
-	printf ("%d: %s: %s", link->ifindex, link->name, link->type_name);
-	if (link->up)
+	printf ("%d: %s: %s", link->ifindex, link->name, nm_link_type_to_string (link->type));
+	if (NM_FLAGS_HAS (link->flags, IFF_UP))
 		printf (" %s", link->connected ? "CONNECTED" : "DISCONNECTED");
 	else
 		printf (" DOWN");
-	if (!link->arp)
+	if (NM_FLAGS_HAS (link->flags, IFF_NOARP))
 		printf (" noarp");
 	if (link->master)
 		printf (" master %d", link->master);
@@ -43,22 +44,22 @@ dump_interface (NMPlatformLink *link)
 	printf ("\n");
 	if (link->driver)
 		printf ("    driver: %s\n", link->driver);
-	printf ("    UDI: %s\n", link->udi);
-	if (!nm_platform_vlan_get_info (link->ifindex, &vlan_parent, &vlan_id))
+	printf ("    UDI: %s\n", nm_platform_link_get_udi (NM_PLATFORM_GET, link->ifindex));
+	if (!nm_platform_vlan_get_info (NM_PLATFORM_GET, link->ifindex, &vlan_parent, &vlan_id))
 		g_assert_not_reached ();
 	if (vlan_parent)
 		printf ("    vlan parent %d id %d\n", vlan_parent, vlan_id);
 
-	if (nm_platform_link_is_software (link->ifindex))
+	if (nm_platform_link_is_software (NM_PLATFORM_GET, link->ifindex))
 		printf ("    class software\n");
-	if (nm_platform_link_supports_slaves (link->ifindex))
+	if (nm_platform_link_supports_slaves (NM_PLATFORM_GET, link->ifindex))
 		printf ("    class supports-slaves\n");
-	if (nm_platform_link_supports_carrier_detect (link->ifindex))
+	if (nm_platform_link_supports_carrier_detect (NM_PLATFORM_GET, link->ifindex))
 		printf ("    feature carrier-detect\n");
-	if (nm_platform_link_supports_vlans (link->ifindex))
+	if (nm_platform_link_supports_vlans (NM_PLATFORM_GET, link->ifindex))
 		printf ("    feature vlans\n");
 
-	address = nm_platform_link_get_address (link->ifindex, &addrlen);
+	address = nm_platform_link_get_address (NM_PLATFORM_GET, link->ifindex, &addrlen);
 	if (address) {
 		printf ("    link-address ");
 		for (i = 0; i < addrlen; i++)
@@ -66,8 +67,8 @@ dump_interface (NMPlatformLink *link)
 		printf ("\n");
 	}
 
-	ip4_addresses = nm_platform_ip4_address_get_all (link->ifindex);
-	ip6_addresses = nm_platform_ip6_address_get_all (link->ifindex);
+	ip4_addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, link->ifindex);
+	ip6_addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, link->ifindex);
 
 	g_assert (ip4_addresses);
 	g_assert (ip6_addresses);
@@ -85,8 +86,8 @@ dump_interface (NMPlatformLink *link)
 	g_array_unref (ip4_addresses);
 	g_array_unref (ip6_addresses);
 
-	ip4_routes = nm_platform_ip4_route_get_all (link->ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
-	ip6_routes = nm_platform_ip6_route_get_all (link->ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+	ip4_routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, link->ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	ip6_routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, link->ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 
 	g_assert (ip4_routes);
 	g_assert (ip6_routes);
@@ -112,7 +113,7 @@ dump_interface (NMPlatformLink *link)
 static void
 dump_all (void)
 {
-	GArray *links = nm_platform_link_get_all ();
+	GArray *links = nm_platform_link_get_all (NM_PLATFORM_GET);
 	int i;
 
 	for (i = 0; i < links->len; i++)
diff --git a/src/platform/tests/platform.c b/src/platform/tests/platform.c
index eea6610d..24e828c1 100644
--- a/src/platform/tests/platform.c
+++ b/src/platform/tests/platform.c
@@ -44,13 +44,13 @@ typedef const char *string_t;
 static gboolean
 do_sysctl_set (char **argv)
 {
-	return nm_platform_sysctl_set (argv[0], argv[1]);
+	return nm_platform_sysctl_set (NM_PLATFORM_GET, argv[0], argv[1]);
 }
 
 static gboolean
 do_sysctl_get (char **argv)
 {
-	gs_free char *value = nm_platform_sysctl_get (argv[0]);
+	gs_free char *value = nm_platform_sysctl_get (NM_PLATFORM_GET, argv[0]);
 
 	printf ("%s\n", value);
 
@@ -66,7 +66,7 @@ parse_ifindex (const char *str)
 	ifindex = strtol (str, &endptr, 10);
 
 	if (*endptr) {
-		ifindex = nm_platform_link_get_ifindex (str);
+		ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, str);
 	}
 
 	return ifindex;
@@ -79,7 +79,7 @@ do_link_get_all (char **argv)
 	NMPlatformLink *device;
 	int i;
 
-	links = nm_platform_link_get_all ();
+	links = nm_platform_link_get_all (NM_PLATFORM_GET);
 	for (i = 0; i < links->len; i++) {
 		device = &g_array_index (links, NMPlatformLink, i);
 
@@ -93,25 +93,25 @@ do_link_get_all (char **argv)
 static gboolean
 do_dummy_add (char **argv)
 {
-	return nm_platform_dummy_add (argv[0]);
+	return nm_platform_dummy_add (NM_PLATFORM_GET, argv[0], NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 do_bridge_add (char **argv)
 {
-	return nm_platform_bridge_add (argv[0], NULL, 0);
+	return nm_platform_bridge_add (NM_PLATFORM_GET, argv[0], NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 do_bond_add (char **argv)
 {
-	return nm_platform_bond_add (argv[0]);
+	return nm_platform_bond_add (NM_PLATFORM_GET, argv[0], NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 do_team_add (char **argv)
 {
-	return nm_platform_team_add (argv[0]);
+	return nm_platform_team_add (NM_PLATFORM_GET, argv[0], NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -122,13 +122,13 @@ do_vlan_add (char **argv)
 	int vlanid = strtol (*argv++, NULL, 10);
 	guint32 vlan_flags = strtol (*argv++, NULL, 10);
 
-	return nm_platform_vlan_add (name, parent, vlanid, vlan_flags);
+	return nm_platform_vlan_add (NM_PLATFORM_GET, name, parent, vlanid, vlan_flags, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 do_link_exists (char **argv)
 {
-	gboolean value = nm_platform_link_exists (argv[0]);
+	gboolean value = !!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, argv[0]);
 
 	print_boolean (value);
 
@@ -140,7 +140,7 @@ do_link_exists (char **argv)
 	do_link_##cmdname (char **argv) \
 	{ \
 		int ifindex = parse_ifindex (argv[0]); \
-		return ifindex ? nm_platform_link_##cmdname (ifindex) : FALSE; \
+		return ifindex ? nm_platform_link_##cmdname (NM_PLATFORM_GET, ifindex) : FALSE; \
 	}
 
 #define LINK_CMD_GET_FULL(cmdname, type, cond) \
@@ -149,7 +149,7 @@ do_link_exists (char **argv)
 	{ \
 		int ifindex = parse_ifindex (argv[0]); \
 		if (ifindex) { \
-			type##_t value = nm_platform_link_##cmdname (ifindex); \
+			type##_t value = nm_platform_link_##cmdname (NM_PLATFORM_GET, ifindex); \
 			if (cond) { \
 				print_##type (value); \
 				return TRUE; \
@@ -169,7 +169,7 @@ LINK_CMD (delete)
 static gboolean
 do_link_get_ifindex (char **argv)
 {
-	int ifindex = nm_platform_link_get_ifindex (argv[0]);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, argv[0]);
 
 	if (ifindex)
 		printf ("%d\n", ifindex);
@@ -182,7 +182,14 @@ LINK_CMD_GET_FULL (get_type, decimal, value > 0)
 LINK_CMD_GET (is_software, boolean)
 LINK_CMD_GET (supports_slaves, boolean)
 
-LINK_CMD (set_up)
+static gboolean
+do_link_set_up (char **argv)
+{
+	int ifindex = parse_ifindex (argv[0]);
+
+	return ifindex ? nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL) : FALSE;
+}
+
 LINK_CMD (set_down)
 LINK_CMD (set_arp)
 LINK_CMD (set_noarp)
@@ -213,7 +220,7 @@ do_link_set_address (char **argv)
 		g_assert (!*endptr);
 	}
 
-	return nm_platform_link_set_address (ifindex, address, sizeof (address));
+	return nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, address, sizeof (address));
 }
 
 static gboolean
@@ -224,7 +231,7 @@ do_link_get_address (char **argv)
 	size_t length;
 	int i;
 
-	address = nm_platform_link_get_address (ifindex, &length);
+	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &length);
 
 	if (!address || length <= 0)
 		return FALSE;
@@ -242,7 +249,7 @@ do_link_set_mtu (char **argv)
 	int ifindex = parse_ifindex (*argv++);
 	int mtu = strtoul (*argv++, NULL, 10);
 
-	return nm_platform_link_set_mtu (ifindex, mtu);
+	return nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, mtu);
 }
 
 LINK_CMD_GET (get_mtu, decimal);
@@ -255,7 +262,7 @@ do_link_enslave (char **argv)
 	int master = parse_ifindex (*argv++);
 	int slave = parse_ifindex (*argv++);
 
-	return nm_platform_link_enslave (master, slave);
+	return nm_platform_link_enslave (NM_PLATFORM_GET, master, slave);
 }
 
 static gboolean
@@ -264,7 +271,7 @@ do_link_release (char **argv)
 	int master = parse_ifindex (*argv++);
 	int slave = parse_ifindex (*argv++);
 
-	return nm_platform_link_release (master, slave);
+	return nm_platform_link_release (NM_PLATFORM_GET, master, slave);
 }
 
 LINK_CMD_GET (get_master, decimal)
@@ -276,7 +283,7 @@ do_master_set_option (char **argv)
 	const char *option = *argv++;
 	const char *value = *argv++;
 
-	return nm_platform_master_set_option (ifindex, option, value);
+	return nm_platform_master_set_option (NM_PLATFORM_GET, ifindex, option, value);
 }
 
 static gboolean
@@ -284,7 +291,7 @@ do_master_get_option (char **argv)
 {
 	int ifindex = parse_ifindex (*argv++);
 	const char *option = *argv++;
-	gs_free char *value = nm_platform_master_get_option (ifindex, option);
+	gs_free char *value = nm_platform_master_get_option (NM_PLATFORM_GET, ifindex, option);
 
 	printf ("%s\n", value);
 
@@ -298,7 +305,7 @@ do_slave_set_option (char **argv)
 	const char *option = *argv++;
 	const char *value = *argv++;
 
-	return nm_platform_slave_set_option (ifindex, option, value);
+	return nm_platform_slave_set_option (NM_PLATFORM_GET, ifindex, option, value);
 }
 
 static gboolean
@@ -306,7 +313,7 @@ do_slave_get_option (char **argv)
 {
 	int ifindex = parse_ifindex (*argv++);
 	const char *option = *argv++;
-	gs_free char *value = nm_platform_slave_get_option (ifindex, option);
+	gs_free char *value = nm_platform_slave_get_option (NM_PLATFORM_GET, ifindex, option);
 
 	printf ("%s\n", value);
 
@@ -320,7 +327,7 @@ do_vlan_get_info (char **argv)
 	int parent;
 	int vlanid;
 
-	if (!nm_platform_vlan_get_info (ifindex, &parent, &vlanid))
+	if (!nm_platform_vlan_get_info (NM_PLATFORM_GET, ifindex, &parent, &vlanid))
 		return FALSE;
 
 	printf ("%d %d\n", parent, vlanid);
@@ -335,7 +342,7 @@ do_vlan_set_ingress_map (char **argv)
 	int from = strtol (*argv++, NULL, 10);
 	int to = strtol (*argv++, NULL, 10);
 
-	return nm_platform_vlan_set_ingress_map (ifindex, from, to);
+	return nm_platform_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, from, to);
 }
 
 static gboolean
@@ -345,7 +352,7 @@ do_vlan_set_egress_map (char **argv)
 	int from = strtol (*argv++, NULL, 10);
 	int to = strtol (*argv++, NULL, 10);
 
-	return nm_platform_vlan_set_egress_map (ifindex, from, to);
+	return nm_platform_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, from, to);
 }
 
 static gboolean
@@ -354,7 +361,7 @@ do_veth_get_properties (char **argv)
 	int ifindex = parse_ifindex (*argv++);
 	NMPlatformVethProperties props;
 
-	if (!nm_platform_veth_get_properties (ifindex, &props))
+	if (!nm_platform_veth_get_properties (NM_PLATFORM_GET, ifindex, &props))
 		return FALSE;
 
 	printf ("peer: %d\n", props.peer);
@@ -368,7 +375,7 @@ do_tun_get_properties (char **argv)
 	int ifindex = parse_ifindex (*argv++);
 	NMPlatformTunProperties props;
 
-	if (!nm_platform_tun_get_properties (ifindex, &props))
+	if (!nm_platform_tun_get_properties (NM_PLATFORM_GET, ifindex, &props))
 		return FALSE;
 
 	printf ("mode: %s\n", props.mode);
@@ -396,7 +403,7 @@ do_macvlan_get_properties (char **argv)
 	int ifindex = parse_ifindex (*argv++);
 	NMPlatformMacvlanProperties props;
 
-	if (!nm_platform_macvlan_get_properties (ifindex, &props))
+	if (!nm_platform_macvlan_get_properties (NM_PLATFORM_GET, ifindex, &props))
 		return FALSE;
 
 	printf ("parent: %d\n", props.parent_ifindex);
@@ -413,7 +420,7 @@ do_vxlan_get_properties (char **argv)
 	NMPlatformVxlanProperties props;
 	char addrstr[INET6_ADDRSTRLEN];
 
-	if (!nm_platform_vxlan_get_properties (ifindex, &props))
+	if (!nm_platform_vxlan_get_properties (NM_PLATFORM_GET, ifindex, &props))
 		return FALSE;
 
 	printf ("parent-ifindex: %u\n", props.parent_ifindex);
@@ -460,7 +467,7 @@ do_gre_get_properties (char **argv)
 	NMPlatformGreProperties props;
 	char addrstr[INET_ADDRSTRLEN];
 
-	if (!nm_platform_gre_get_properties (ifindex, &props))
+	if (!nm_platform_gre_get_properties (NM_PLATFORM_GET, ifindex, &props))
 		return FALSE;
 
 	printf ("parent-ifindex: %u\n", props.parent_ifindex);
@@ -496,7 +503,7 @@ do_ip4_address_get_all (char **argv)
 	int i;
 
 	if (ifindex) {
-		addresses = nm_platform_ip4_address_get_all (ifindex);
+		addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 		for (i = 0; i < addresses->len; i++) {
 			address = &g_array_index (addresses, NMPlatformIP4Address, i);
 			inet_ntop (AF_INET, &address->address, addrstr, sizeof (addrstr));
@@ -518,7 +525,7 @@ do_ip6_address_get_all (char **argv)
 	int i;
 
 	if (ifindex) {
-		addresses = nm_platform_ip6_address_get_all (ifindex);
+		addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
 		for (i = 0; i < addresses->len; i++) {
 			address = &g_array_index (addresses, NMPlatformIP6Address, i);
 			inet_ntop (AF_INET6, &address->address, addrstr, sizeof (addrstr));
@@ -576,7 +583,7 @@ do_ip4_address_add (char **argv)
 		guint32 lifetime = strtol (*argv++, NULL, 10);
 		guint32 preferred = strtol (*argv++, NULL, 10);
 
-		gboolean value = nm_platform_ip4_address_add (ifindex, address, 0, plen, lifetime, preferred, NULL);
+		gboolean value = nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, address, 0, plen, lifetime, preferred, NULL);
 		return value;
 	} else
 		return FALSE;
@@ -594,7 +601,7 @@ do_ip6_address_add (char **argv)
 		guint32 preferred = strtol (*argv++, NULL, 10);
 		guint flags = (*argv) ? rtnl_addr_str2flags (*argv++) : 0;
 
-		gboolean value = nm_platform_ip6_address_add (ifindex, address, in6addr_any, plen, lifetime, preferred, flags);
+		gboolean value = nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, address, in6addr_any, plen, lifetime, preferred, flags);
 		return value;
 	} else
 		return FALSE;
@@ -608,7 +615,7 @@ do_ip6_address_add (char **argv)
 		v##_t address; \
 		int plen; \
 		if (ifindex && parse_##v##_address (*argv++, &address, &plen)) { \
-			gboolean value = nm_platform_##v##_address_##cmdname (ifindex, address, plen, ##__VA_ARGS__); \
+			gboolean value = !!nm_platform_##v##_address_##cmdname (NM_PLATFORM_GET, ifindex, address, plen, ##__VA_ARGS__); \
 			if (print) { \
 				print_boolean (value); \
 				return TRUE; \
@@ -621,7 +628,7 @@ do_ip6_address_add (char **argv)
 #define ADDR_CMD_PRINT(cmdname) ADDR_CMD_FULL (ip4, cmdname, TRUE) ADDR_CMD_FULL (ip6, cmdname, TRUE)
 
 ADDR_CMD (delete)
-ADDR_CMD_PRINT (exists)
+ADDR_CMD_PRINT (get)
 
 static gboolean
 do_ip4_route_get_all (char **argv)
@@ -633,7 +640,7 @@ do_ip4_route_get_all (char **argv)
 	int i;
 
 	if (ifindex) {
-		routes = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+		routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 		for (i = 0; i < routes->len; i++) {
 			route = &g_array_index (routes, NMPlatformIP4Route, i);
 			inet_ntop (AF_INET, &route->network, networkstr, sizeof (networkstr));
@@ -657,7 +664,7 @@ do_ip6_route_get_all (char **argv)
 	int i;
 
 	if (ifindex) {
-		routes = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+		routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 		for (i = 0; i < routes->len; i++) {
 			route = &g_array_index (routes, NMPlatformIP6Route, i);
 			inet_ntop (AF_INET6, &route->network, networkstr, sizeof (networkstr));
@@ -683,7 +690,7 @@ do_ip4_route_add (char **argv)
 	metric = strtol (*argv++, NULL, 10);
 	mss = strtol (*argv++, NULL, 10);
 
-	return nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER,
+	return nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER,
 	                                  network, plen, gateway, 0,
 	                                  metric, mss);
 }
@@ -699,7 +706,7 @@ do_ip6_route_add (char **argv)
 	parse_ip6_address (*argv++, &gateway, NULL);
 	metric = strtol (*argv++, NULL, 10);
 	mss = strtol (*argv++, NULL, 10);
-	return nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER,
+	return nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER,
 	                                  network, plen, gateway,
 	                                  metric, mss);
 }
@@ -714,7 +721,7 @@ do_ip4_route_delete (char **argv)
 	parse_ip4_address (*argv++, &network, &plen);
 	metric = strtol (*argv++, NULL, 10);
 
-	return nm_platform_ip4_route_delete (ifindex, network, plen, metric);
+	return nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric);
 }
 
 static gboolean
@@ -727,11 +734,11 @@ do_ip6_route_delete (char **argv)
 	parse_ip6_address (*argv++, &network, &plen);
 	metric = strtol (*argv++, NULL, 10);
 
-	return nm_platform_ip6_route_delete (ifindex, network, plen, metric);
+	return nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric);
 }
 
 static gboolean
-do_ip4_route_exists (char **argv)
+do_ip4_route_get (char **argv)
 {
 	int ifindex = parse_ifindex (*argv++);
 	in_addr_t network;
@@ -740,12 +747,12 @@ do_ip4_route_exists (char **argv)
 	parse_ip4_address (*argv++, &network, &plen);
 	metric = strtol (*argv++, NULL, 10);
 
-	print_boolean (nm_platform_ip4_route_exists (ifindex, network, plen, metric));
+	print_boolean (!!nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	return TRUE;
 }
 
 static gboolean
-do_ip6_route_exists (char **argv)
+do_ip6_route_get (char **argv)
 {
 	int ifindex = parse_ifindex (*argv++);
 	struct in6_addr network;
@@ -754,7 +761,7 @@ do_ip6_route_exists (char **argv)
 	parse_ip6_address (*argv++, &network, &plen);
 	metric = strtol (*argv++, NULL, 10);
 
-	print_boolean (nm_platform_ip6_route_exists (ifindex, network, plen, metric));
+	print_boolean (!!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	return TRUE;
 }
 
@@ -831,9 +838,9 @@ static const command_t commands[] = {
 		"<ifname/ifindex> <address>/<plen>" },
 	{ "ip6-address-delete", "delete IPv6 address", do_ip6_address_delete, 2,
 		"<ifname/ifindex> <address>/<plen>" },
-	{ "ip4-address-exists", "check for existence of IPv4 address", do_ip4_address_exists, 2,
+	{ "ip4-address-exists", "check for existence of IPv4 address", do_ip4_address_get, 2,
 		"<ifname/ifindex> <address>/<plen>" },
-	{ "ip6-address-exists", "check for existence of IPv6 address", do_ip6_address_exists, 2,
+	{ "ip6-address-exists", "check for existence of IPv6 address", do_ip6_address_get, 2,
 		"<ifname/ifindex> <address>/<plen>" },
 	{ "ip4-route-get-all", "print all IPv4 routes", do_ip4_route_get_all, 1, "<ifname/ifindex>" },
 	{ "ip6-route-get-all", "print all IPv6 routes", do_ip6_route_get_all, 1, "<ifname/ifindex>" },
@@ -845,9 +852,9 @@ static const command_t commands[] = {
 		"<ifname/ifindex> <network>/<plen> <metric>" },
 	{ "ip6-route-delete", "delete IPv6 route", do_ip6_route_delete, 3,
 		"<ifname/ifindex> <network>/<plen> <metric>" },
-	{ "ip4-route-exists", "check for existence of IPv4 route", do_ip4_route_exists, 3,
+	{ "ip4-route-exists", "check for existence of IPv4 route", do_ip4_route_get, 3,
 		"<ifname/ifindex> <network>/<plen> <metric>" },
-	{ "ip6-route-exists", "check for existence of IPv6 route", do_ip6_route_exists, 3,
+	{ "ip6-route-exists", "check for existence of IPv6 route", do_ip6_route_get, 3,
 		"<ifname/ifindex> <network>/<plen> <metric>" },
 	{ NULL, NULL, NULL, 0, NULL },
 };
@@ -858,7 +865,6 @@ main (int argc, char **argv)
 	const char *arg0 = *argv++;
 	const command_t *command = NULL;
 	gboolean status = TRUE;
-	int error;
 
 #if !GLIB_CHECK_VERSION (2, 35, 0)
 	g_type_init ();
@@ -892,12 +898,5 @@ main (int argc, char **argv)
 		error ("\n");
 	}
 
-	error = nm_platform_get_error ();
-	if (error) {
-		const char *msg = nm_platform_get_error_msg ();
-
-		error ("nm-platform: %s\n", msg);
-	}
-
-	return !!error;
+	return EXIT_SUCCESS;
 }
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index 5561038d..d6f346b3 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -9,7 +9,7 @@
 #define IP6_PLEN 64
 
 static void
-ip4_address_callback (NMPlatform *platform, 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, NMPlatformReason reason, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -24,14 +24,12 @@ ip4_address_callback (NMPlatform *platform, int ifindex, NMPlatformIP4Address *r
 	if (data->loop)
 		g_main_loop_quit (data->loop);
 
-	if (data->received)
-		g_error ("Received signal '%s' a second time.", data->name);
-
-	data->received = TRUE;
+	data->received_count++;
+	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
-ip6_address_callback (NMPlatform *platform, 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, NMPlatformReason reason, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -46,16 +44,14 @@ ip6_address_callback (NMPlatform *platform, int ifindex, NMPlatformIP6Address *r
 	if (data->loop)
 		g_main_loop_quit (data->loop);
 
-	if (data->received)
-		g_error ("Received signal '%s' a second time.", data->name);
-
-	data->received = TRUE;
+	data->received_count++;
+	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
 test_ip4_address (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	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);
@@ -68,23 +64,18 @@ test_ip4_address (void)
 	inet_pton (AF_INET, IP4_ADDRESS, &addr);
 
 	/* Add address */
-	g_assert (!nm_platform_ip4_address_exists (ifindex, addr, IP4_PLEN));
-	no_error ();
-	g_assert (nm_platform_ip4_address_add (ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	no_error ();
-	g_assert (nm_platform_ip4_address_exists (ifindex, addr, IP4_PLEN));
-	no_error ();
+	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));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
-	g_assert (nm_platform_ip4_address_add (ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	no_error ();
-	accept_signal (address_changed);
+	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
+	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
-	addresses = nm_platform_ip4_address_get_all (ifindex);
+	addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 	g_assert (addresses);
-	no_error ();
 	g_assert_cmpint (addresses->len, ==, 1);
 	address = &g_array_index (addresses, NMPlatformIP4Address, 0);
 	g_assert_cmpint (address->ifindex, ==, ifindex);
@@ -93,14 +84,12 @@ test_ip4_address (void)
 	g_array_unref (addresses);
 
 	/* Remove address */
-	g_assert (nm_platform_ip4_address_delete (ifindex, addr, IP4_PLEN, 0));
-	no_error ();
-	g_assert (!nm_platform_ip4_address_exists (ifindex, addr, IP4_PLEN));
+	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));
 	accept_signal (address_removed);
 
 	/* Remove address again */
-	g_assert (nm_platform_ip4_address_delete (ifindex, addr, IP4_PLEN, 0));
-	no_error ();
+	g_assert (nm_platform_ip4_address_delete (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, 0));
 
 	free_signal (address_added);
 	free_signal (address_changed);
@@ -110,7 +99,7 @@ test_ip4_address (void)
 static void
 test_ip6_address (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	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);
@@ -124,23 +113,18 @@ test_ip6_address (void)
 	inet_pton (AF_INET6, IP6_ADDRESS, &addr);
 
 	/* Add address */
-	g_assert (!nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
-	no_error ();
-	g_assert (nm_platform_ip6_address_add (ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
-	no_error ();
-	g_assert (nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
-	no_error ();
+	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));
+	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 (ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
-	no_error ();
-	accept_signal (address_changed);
+	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
-	addresses = nm_platform_ip6_address_get_all (ifindex);
+	addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
 	g_assert (addresses);
-	no_error ();
 	g_assert_cmpint (addresses->len, ==, 1);
 	address = &g_array_index (addresses, NMPlatformIP6Address, 0);
 	g_assert_cmpint (address->ifindex, ==, ifindex);
@@ -149,14 +133,12 @@ test_ip6_address (void)
 	g_array_unref (addresses);
 
 	/* Remove address */
-	g_assert (nm_platform_ip6_address_delete (ifindex, addr, IP6_PLEN));
-	no_error ();
-	g_assert (!nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
+	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));
 	accept_signal (address_removed);
 
 	/* Remove address again */
-	g_assert (nm_platform_ip6_address_delete (ifindex, addr, IP6_PLEN));
-	no_error ();
+	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 
 	free_signal (address_added);
 	free_signal (address_changed);
@@ -168,7 +150,7 @@ test_ip4_address_external (void)
 {
 	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 (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	in_addr_t addr;
 	guint32 lifetime = 2000;
 	guint32 preferred = 1000;
@@ -179,28 +161,26 @@ 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_link_get_ifindex (DEVICE_NAME)));
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), 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_exists (ifindex, addr, IP4_PLEN));
+	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_exists (ifindex, addr, IP4_PLEN));
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
 
 	/* 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 (ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	no_error ();
-	g_assert (nm_platform_ip4_address_exists (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));
 	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));
-	no_error ();
-	g_assert (!nm_platform_ip4_address_exists (ifindex, addr, IP4_PLEN));
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
 	accept_signal (address_removed);*/
 
 	free_signal (address_added);
@@ -212,7 +192,7 @@ test_ip6_address_external (void)
 {
 	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 (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	struct in6_addr addr;
 	guint32 lifetime = 2000;
 	guint32 preferred = 1000;
@@ -224,22 +204,20 @@ test_ip6_address_external (void)
 	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);
-	g_assert (nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
+	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);
-	g_assert (!nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
+	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 (ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
-	no_error ();
-	g_assert (nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 	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 (ifindex, addr, IP6_PLEN));
-	no_error ();
-	g_assert (!nm_platform_ip6_address_exists (ifindex, addr, IP6_PLEN));
+	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);*/
 
 	free_signal (address_added);
@@ -257,9 +235,9 @@ setup_tests (void)
 {
 	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
 
-	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
-	g_assert (!nm_platform_link_exists (DEVICE_NAME));
-	g_assert (nm_platform_dummy_add (DEVICE_NAME));
+	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);
 
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 0d0f3f63..0b825114 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -35,30 +35,30 @@ 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 (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 (nm_platform_link_set_up (nm_platform_link_get_ifindex (DEVICE_NAME)));
-	ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	g_assert (ifindex > 0);
 
 	/* Add routes and addresses */
-	g_assert (nm_platform_ip4_address_add (ifindex, addr4, 0, plen4, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip6_address_add (ifindex, addr6, in6addr_any, plen6, lifetime, preferred, flags));
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, gateway4, 32, INADDR_ANY, 0, metric, mss));
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network4, plen4, gateway4, 0, metric, mss));
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway4, 0, metric, mss));
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, gateway6, 128, in6addr_any, metric, mss));
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network6, plen6, gateway6, metric, mss));
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway6, metric, mss));
-
-	addresses4 = nm_platform_ip4_address_get_all (ifindex);
-	addresses6 = nm_platform_ip6_address_get_all (ifindex);
-	routes4 = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
-	routes6 = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+	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_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));
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway6, 128, in6addr_any, metric, mss));
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network6, plen6, gateway6, metric, mss));
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway6, metric, mss));
+
+	addresses4 = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses6 = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	routes4 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	routes6 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 
 	g_assert_cmpint (addresses4->len, ==, 1);
-	g_assert_cmpint (addresses6->len, ==, 1);
+	g_assert_cmpint (addresses6->len, ==, 2); /* also has a IPv6 LL address. */
 	g_assert_cmpint (routes4->len, ==, 3);
 	g_assert_cmpint (routes6->len, ==, 3);
 
@@ -68,12 +68,12 @@ test_cleanup_internal (void)
 	g_array_unref (routes6);
 
 	/* Delete interface with all addresses and routes */
-	g_assert (nm_platform_link_delete (ifindex));
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
-	addresses4 = nm_platform_ip4_address_get_all (ifindex);
-	addresses6 = nm_platform_ip6_address_get_all (ifindex);
-	routes4 = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
-	routes6 = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+	addresses4 = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses6 = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	routes4 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	routes6 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 
 	g_assert_cmpint (addresses4->len, ==, 0);
 	g_assert_cmpint (addresses6->len, ==, 0);
@@ -95,8 +95,8 @@ init_tests (int *argc, char ***argv)
 void
 setup_tests (void)
 {
-	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
-	g_assert (!nm_platform_link_exists (DEVICE_NAME));
+	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_test_add_func ("/internal", test_cleanup_internal);
 	/* FIXME: add external cleanup check */
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 1980282d..b0eae63b 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1,9 +1,31 @@
 #include "config.h"
 
+#include <sys/mount.h>
+#include <sched.h>
+
 #include "test-common.h"
 
 #include "nm-test-utils.h"
 
+#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)
+{
+	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
+	return (SETUP == nm_linux_platform_setup);
+	NM_PRAGMA_WARNING_REENABLE
+}
+
+gboolean
+nmtst_platform_is_sysfs_writable (void)
+{
+	return    !nmtst_platform_is_root_test ()
+	       || (access ("/sys/devices", W_OK) == 0);
+}
+
 SignalData *
 add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCallback callback, int ifindex, const char *ifname)
 {
@@ -11,7 +33,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall
 
 	data->name = name;
 	data->change_type = change_type;
-	data->received = FALSE;
+	data->received_count = 0;
 	data->handler_id = g_signal_connect (nm_platform_get (), name, callback, data);
 	data->ifindex = ifindex;
 	data->ifname = ifname;
@@ -21,58 +43,60 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall
 	return data;
 }
 
-static const char *
-_change_type_to_string (NMPlatformSignalChangeType change_type)
+void
+_accept_signal (const char *file, int line, const char *func, SignalData *data)
 {
-    switch (change_type) {
-    case NM_PLATFORM_SIGNAL_ADDED:
-        return "added";
-    case NM_PLATFORM_SIGNAL_CHANGED:
-        return "changed";
-    case NM_PLATFORM_SIGNAL_REMOVED:
-        return "removed";
-    default:
-        g_return_val_if_reached ("UNKNOWN");
-    }
+	debug ("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;
 }
 
 void
-accept_signal (SignalData *data)
+_accept_signals (const char *file, int line, const char *func, SignalData *data, int min, int max)
 {
-	debug ("Accepting signal '%s-%s' ifindex %d ifname %s.", data->name, _change_type_to_string (data->change_type), data->ifindex, data->ifname);
-	if (!data->received)
-		g_error ("Attemted to accept a non-received signal '%s-%s'.", data->name, _change_type_to_string (data->change_type));
+	debug ("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;
+}
 
-	data->received = FALSE;
+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));
+	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
-wait_signal (SignalData *data)
+_wait_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	if (data->received)
-		g_error ("Signal '%s' received before waiting for it.", data->name);
+	debug ("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));
 
 	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 (data);
+	_accept_signal (file, line, func, data);
 }
 
 void
-free_signal (SignalData *data)
+_free_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	if (data->received)
-		g_error ("Attempted to free received but not accepted signal '%s-%s'.", data->name, _change_type_to_string (data->change_type));
+	debug ("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_free (data);
 }
 
 void
-link_callback (NMPlatform *platform, 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, NMPlatformReason reason, SignalData *data)
 {
-	
 	GArray *links;
 	NMPlatformLink *cached;
 	int i;
@@ -84,7 +108,7 @@ link_callback (NMPlatform *platform, int ifindex, NMPlatformLink *received, NMPl
 
 	if (data->ifindex && data->ifindex != received->ifindex)
 		return;
-	if (data->ifname && g_strcmp0 (data->ifname, nm_platform_link_get_name (ifindex)) != 0)
+	if (data->ifname && g_strcmp0 (data->ifname, nm_platform_link_get_name (NM_PLATFORM_GET, ifindex)) != 0)
 		return;
 	if (change_type != data->change_type)
 		return;
@@ -94,20 +118,17 @@ link_callback (NMPlatform *platform, int ifindex, NMPlatformLink *received, NMPl
 		g_main_loop_quit (data->loop);
 	}
 
-	if (data->received)
-		g_error ("Received signal '%s-%s' a second time.", data->name, _change_type_to_string (data->change_type));
-
-	debug ("Received signal '%s-%s' ifindex %d ifname '%s'.", data->name, _change_type_to_string (data->change_type), ifindex, received->name);
-	data->received = TRUE;
+	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);
 
 	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
-		g_assert (!nm_platform_link_get_name (ifindex));
+		g_assert (!nm_platform_link_get_name (NM_PLATFORM_GET, ifindex));
 	else
-		g_assert (nm_platform_link_get_name (ifindex));
+		g_assert (nm_platform_link_get_name (NM_PLATFORM_GET, ifindex));
 
 	/* Check the data */
 	g_assert (received->ifindex > 0);
-	links = nm_platform_link_get_all ();
+	links = nm_platform_link_get_all (NM_PLATFORM_GET);
 	for (i = 0; i < links->len; i++) {
 		cached = &g_array_index (links, NMPlatformLink, i);
 		if (cached->ifindex == received->ifindex) {
@@ -221,9 +242,9 @@ _assert_ip4_route_exists (const char *file, guint line, const char *func, gboole
 		         exists ? "doesn't" : "does");
 	}
 
-	ifindex = nm_platform_link_get_ifindex (ifname);
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, ifname);
 	g_assert (ifindex > 0);
-	if (!nm_platform_ip4_route_exists (ifindex, network, plen, metric) != !exists) {
+	if (!nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric) != !exists) {
 		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u %s, but platform thinks %s",
 		         file, line, func,
 		         nm_utils_inet4_ntop (network, NULL), plen, metric,
@@ -249,6 +270,46 @@ run_command (const char *format, ...)
 
 NMTST_DEFINE();
 
+static gboolean
+unshare_user (void)
+{
+	FILE *f;
+	uid_t uid = geteuid ();
+	gid_t gid = getegid ();
+
+	/* Already a root? */
+	if (gid == 0 && uid == 0)
+		return TRUE;
+
+	/* Become a root in new user NS. */
+	if (unshare (CLONE_NEWUSER) != 0)
+		return FALSE;
+
+	/* Since Linux 3.19 we have to disable setgroups() in order to map users.
+	 * Just proceed if the file is not there. */
+	f = fopen ("/proc/self/setgroups", "w");
+	if (f) {
+		fprintf (f, "deny");
+		fclose (f);
+	}
+
+	/* Map current UID to root in NS to be created. */
+	f = fopen ("/proc/self/uid_map", "w");
+	if (!f)
+		return FALSE;
+	fprintf (f, "0 %d 1", uid);
+	fclose (f);
+
+	/* Map current GID to root in NS to be created. */
+	f = fopen ("/proc/self/gid_map", "w");
+	if (!f)
+		return FALSE;
+	fprintf (f, "0 %d 1", gid);
+	fclose (f);
+
+	return TRUE;
+}
+
 int
 main (int argc, char **argv)
 {
@@ -257,20 +318,62 @@ main (int argc, char **argv)
 
 	init_tests (&argc, &argv);
 
-	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
-	if (SETUP == nm_linux_platform_setup && getuid() != 0) {
-		/* Try to exec as sudo, this function does not return, if a sudo-cmd is set. */
-		nmtst_reexec_sudo ();
+	if (   nmtst_platform_is_root_test ()
+	    && (geteuid () != 0 || getegid () != 0)) {
+		if (   g_getenv ("NMTST_FORCE_REAL_ROOT")
+		    || !unshare_user ()) {
+			/* Try to exec as sudo, this function does not return, if a sudo-cmd is set. */
+			nmtst_reexec_sudo ();
 
 #ifdef REQUIRE_ROOT_TESTS
-		g_print ("Fail test: requires root privileges (%s)\n", program);
-		return EXIT_FAILURE;
+			g_print ("Fail test: requires root privileges (%s)\n", program);
+			return EXIT_FAILURE;
 #else
-		g_print ("Skipping test: requires root privileges (%s)\n", program);
-		return 77;
+			g_print ("Skipping test: requires root privileges (%s)\n", program);
+			return g_test_run ();
 #endif
+		}
+	}
+
+	if (nmtst_platform_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) {
+		int errsv;
+
+		if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) {
+			errsv = errno;
+			g_error ("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", strerror (errsv), errsv);
+		}
+
+		/* 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);
+		}
+		if (mount ("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) {
+			errsv = errno;
+			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
+		}
+
+		/* Create a writable /sys/devices tree. This makes it possible to run tests
+		 * that modify values via sysfs (such as bridge forward delay). */
+		if (mount ("sys", "/sys/devices", "sysfs", 0, NULL) != 0) {
+			errsv = errno;
+			g_error ("mount(\"/sys/devices\") failed with %s (%d)", strerror (errsv), errsv);
+		}
+		if (mount (NULL, "/sys/devices", "sysfs", MS_REMOUNT, NULL) != 0) {
+			/* Read-write remount failed. Never mind, we're probably just a root in
+			 * our user NS. */
+			if (umount ("/sys/devices") != 0) {
+				errsv = errno;
+				g_error ("umount(\"/sys/devices\") failed with  %s (%d)", strerror (errsv), errsv);
+			}
+		} else {
+			if (mount ("/sys/devices/devices", "/sys/devices", "sysfs", MS_BIND, NULL) != 0) {
+				errsv = errno;
+				g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
+			}
+		}
 	}
-	NM_PRAGMA_WARNING_REENABLE
 
 	SETUP ();
 
@@ -278,8 +381,8 @@ main (int argc, char **argv)
 
 	result = g_test_run ();
 
-	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
-	nm_platform_free ();
+	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 367833d1..3d647543 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -15,33 +15,40 @@
 
 #define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
 
-#define error(err) g_assert (nm_platform_get_error () == err)
-#define no_error() error (NM_PLATFORM_ERROR_NONE)
-
 typedef struct {
 	int handler_id;
 	const char *name;
 	NMPlatformSignalChangeType change_type;
-	gboolean received;
+	gint received_count;
 	GMainLoop *loop;
 	int ifindex;
 	const char *ifname;
 } SignalData;
 
+gboolean nmtst_platform_is_root_test (void);
+gboolean nmtst_platform_is_sysfs_writable (void);
+
 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)
 #define add_signal_ifindex(name, change_type, callback, ifindex) add_signal_full (name, change_type, (GCallback) callback, ifindex, NULL)
 #define add_signal_ifname(name, change_type, callback, ifname) add_signal_full (name, change_type, (GCallback) callback, 0, ifname)
-void accept_signal (SignalData *data);
-void wait_signal (SignalData *data);
-void free_signal (SignalData *data);
+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 _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 ensure_no_signal(data) _ensure_no_signal(__FILE__, __LINE__, G_STRFUNC, data)
+#define free_signal(data) _free_signal(__FILE__, __LINE__, G_STRFUNC, data)
 
 gboolean ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric);
 
 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, 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, NMPlatformReason reason, SignalData *data);
 
 void run_command (const char *format, ...);
 
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
new file mode 100644
index 00000000..0a397b37
--- /dev/null
+++ b/src/platform/tests/test-general.c
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "nm-platform-utils.h"
+
+#include <linux/rtnetlink.h>
+
+#include "nm-linux-platform.h"
+#include "nm-logging.h"
+
+#include "nm-test-utils.h"
+
+
+/******************************************************************/
+
+static void
+test_init_linux_platform (void)
+{
+	gs_unref_object NMPlatform *platform = NULL;
+
+	platform = g_object_new (NM_TYPE_LINUX_PLATFORM, NULL);
+}
+
+/******************************************************************/
+
+static void
+test_link_get_all (void)
+{
+	gs_unref_object NMPlatform *platform = NULL;
+	gs_unref_array GArray *links = NULL;
+
+	platform = g_object_new (NM_TYPE_LINUX_PLATFORM, NULL);
+
+	links = nm_platform_link_get_all (platform);
+}
+
+/******************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+
+	g_test_add_func ("/general/init_linux_platform", test_init_linux_platform);
+	g_test_add_func ("/general/link_get_all", test_link_get_all);
+
+	return g_test_run ();
+}
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 99556e0a..dc7b8397 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -21,112 +21,94 @@ test_bogus(void)
 {
 	size_t addrlen;
 
-	g_assert (!nm_platform_link_exists (BOGUS_NAME));
-	no_error ();
-	g_assert (!nm_platform_link_delete (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_ifindex (BOGUS_NAME));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_name (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_type (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_type_name (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-
-	g_assert (!nm_platform_link_set_up (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_set_down (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_set_arp (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_set_noarp (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_is_up (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_is_connected (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_uses_arp (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-
-	g_assert (!nm_platform_link_get_address (BOGUS_IFINDEX, &addrlen));
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, BOGUS_NAME));
+	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, BOGUS_IFINDEX));
+	g_assert (!nm_platform_link_get_ifindex (NM_PLATFORM_GET, BOGUS_NAME));
+	g_assert (!nm_platform_link_get_name (NM_PLATFORM_GET, BOGUS_IFINDEX));
+	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_assert (!nm_platform_link_set_up (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+	g_assert (!nm_platform_link_set_down (NM_PLATFORM_GET, BOGUS_IFINDEX));
+	g_assert (!nm_platform_link_set_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
+	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));
+
+	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, &addrlen));
 	g_assert (!addrlen);
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_address (BOGUS_IFINDEX, NULL));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_set_mtu (BOGUS_IFINDEX, MTU));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_mtu (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-
-	g_assert (!nm_platform_link_supports_carrier_detect (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_supports_vlans (BOGUS_IFINDEX));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-
-	g_assert (!nm_platform_vlan_get_info (BOGUS_IFINDEX, NULL, NULL));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_vlan_set_ingress_map (BOGUS_IFINDEX, 0, 0));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_vlan_set_egress_map (BOGUS_IFINDEX, 0, 0));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
+	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
+	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));
 }
 
 static void
 test_loopback (void)
 {
-	g_assert (nm_platform_link_exists (LO_NAME));
-	g_assert_cmpint (nm_platform_link_get_type (LO_INDEX), ==, NM_LINK_TYPE_LOOPBACK);
-	g_assert_cmpint (nm_platform_link_get_ifindex (LO_NAME), ==, LO_INDEX);
-	g_assert_cmpstr (nm_platform_link_get_name (LO_INDEX), ==, LO_NAME);
-	g_assert_cmpstr (nm_platform_link_get_type_name (LO_INDEX), ==, LO_TYPEDESC);
-
-	g_assert (nm_platform_link_supports_carrier_detect (LO_INDEX));
-	g_assert (!nm_platform_link_supports_vlans (LO_INDEX));
+	g_assert (nm_platform_link_get_by_ifname (NM_PLATFORM_GET, LO_NAME));
+	g_assert_cmpint (nm_platform_link_get_type (NM_PLATFORM_GET, LO_INDEX), ==, NM_LINK_TYPE_LOOPBACK);
+	g_assert_cmpint (nm_platform_link_get_ifindex (NM_PLATFORM_GET, LO_NAME), ==, LO_INDEX);
+	g_assert_cmpstr (nm_platform_link_get_name (NM_PLATFORM_GET, LO_INDEX), ==, LO_NAME);
+	g_assert_cmpstr (nm_platform_link_get_type_name (NM_PLATFORM_GET, LO_INDEX), ==, LO_TYPEDESC);
+
+	g_assert (nm_platform_link_supports_carrier_detect (NM_PLATFORM_GET, LO_INDEX));
+	g_assert (!nm_platform_link_supports_vlans (NM_PLATFORM_GET, LO_INDEX));
 }
 
-static int
+static gboolean
 software_add (NMLinkType link_type, const char *name)
 {
 	switch (link_type) {
 	case NM_LINK_TYPE_DUMMY:
-		return nm_platform_dummy_add (name);
+		return nm_platform_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_BRIDGE:
-		return nm_platform_bridge_add (name, NULL, 0);
+		return nm_platform_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_BOND:
 		{
-			gboolean bond0_exists = nm_platform_link_exists ("bond0");
-			gboolean result = nm_platform_bond_add (name);
-			NMPlatformError error = nm_platform_get_error ();
+			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);
 
 			/* Check that bond0 is *not* automatically created. */
 			if (!bond0_exists)
-				g_assert (!nm_platform_link_exists ("bond0"));
-
-			nm_platform_set_error (error);
-			return result;
+				g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0"));
+			return plerr == NM_PLATFORM_ERROR_SUCCESS;
 		}
 	case NM_LINK_TYPE_TEAM:
-		return nm_platform_team_add (name);
+		return nm_platform_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 (PARENT_NAME, NULL, 0))
+		if (nm_platform_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
 			accept_signal (parent_added);
 		free_signal (parent_added);
 
 		{
-			int parent_ifindex = nm_platform_link_get_ifindex (PARENT_NAME);
+			int parent_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME);
+			gboolean was_up = nm_platform_link_is_up (NM_PLATFORM_GET, parent_ifindex);
 
 			parent_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, link_callback, parent_ifindex);
-			g_assert (nm_platform_link_set_up (parent_ifindex));
-			accept_signal (parent_changed);
+			g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, parent_ifindex, NULL));
+			if (was_up) {
+				/* when NM is running in the background, it will mess with addrgenmode which might cause additional signals. */
+				accept_signals (parent_changed, 0, 1);
+			} else
+				accept_signal (parent_changed);
 			free_signal (parent_changed);
 
-			return nm_platform_vlan_add (name, parent_ifindex, VLAN_ID, 0);
+			return nm_platform_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 		}
 	}
 	default:
@@ -142,9 +124,12 @@ test_slave (int master, int type, SignalData *master_changed)
 	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, SLAVE_NAME);
 	SignalData *link_changed, *link_removed;
 	char *value;
+	NMLinkType link_type = nm_platform_link_get_type (NM_PLATFORM_GET, master);
+
+	g_assert (NM_IN_SET (link_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE));
 
 	g_assert (software_add (type, SLAVE_NAME));
-	ifindex = nm_platform_link_get_ifindex (SLAVE_NAME);
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, SLAVE_NAME);
 	g_assert (ifindex > 0);
 	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);
@@ -154,46 +139,55 @@ test_slave (int master, int type, SignalData *master_changed)
 	 *
 	 * See https://bugzilla.redhat.com/show_bug.cgi?id=910348
 	 */
-	g_assert (nm_platform_link_set_down (ifindex));
-	g_assert (!nm_platform_link_is_up (ifindex));
-	accept_signal (link_changed);
+	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	ensure_no_signal (link_changed);
 
 	/* Enslave */
 	link_changed->ifindex = ifindex;
-	g_assert (nm_platform_link_enslave (master, ifindex)); no_error ();
-	g_assert_cmpint (nm_platform_link_get_master (ifindex), ==, master); no_error ();
-	accept_signal (link_changed);
-	accept_signal (master_changed);
+	g_assert (nm_platform_link_enslave (NM_PLATFORM_GET, master, ifindex));
+	g_assert_cmpint (nm_platform_link_get_master (NM_PLATFORM_GET, ifindex), ==, master);
+
+	accept_signals (link_changed, 1, 3);
+	accept_signals (master_changed, 0, 1);
+
+	/* enslaveing brings put the slave */
+	if (NM_IN_SET (link_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM))
+		g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	else
+		g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 
 	/* Set master up */
-	g_assert (nm_platform_link_set_up (master));
-	accept_signal (master_changed);
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, master, NULL));
+	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, master));
+	accept_signals (master_changed, 1, 2);
 
 	/* Master with a disconnected slave is disconnected
 	 *
 	 * For some reason, bonding and teaming slaves are automatically set up. We
 	 * need to set them back down for this test.
 	 */
-	switch (nm_platform_link_get_type (master)) {
+	switch (nm_platform_link_get_type (NM_PLATFORM_GET, master)) {
 	case NM_LINK_TYPE_BOND:
 	case NM_LINK_TYPE_TEAM:
-		g_assert (nm_platform_link_set_down (ifindex));
+		g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex));
 		accept_signal (link_changed);
-		accept_signal (master_changed);
+		accept_signals (master_changed, 0, 2);
 		break;
 	default:
 		break;
 	}
-	g_assert (!nm_platform_link_is_up (ifindex));
-	g_assert (!nm_platform_link_is_connected (ifindex));
-	if (nm_platform_link_is_connected (master)) {
-		if (nm_platform_link_get_type (master) == NM_LINK_TYPE_TEAM) {
+	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
+	if (nm_platform_link_is_connected (NM_PLATFORM_GET, master)) {
+		if (nm_platform_link_get_type (NM_PLATFORM_GET, master) == NM_LINK_TYPE_TEAM) {
 			/* Older team versions (e.g. Fedora 17) have a bug that team master stays
 			 * IFF_LOWER_UP even if its slave is down. Double check it with iproute2 and if
 			 * `ip link` also claims master to be up, accept it. */
 			char *stdout_str = NULL;
 
-			nmtst_spawn_sync (NULL, &stdout_str, NULL, 0, "/sbin/ip", "link", "show", "dev", nm_platform_link_get_name (master));
+			nmtst_spawn_sync (NULL, &stdout_str, NULL, 0, "/sbin/ip", "link", "show", "dev", nm_platform_link_get_name (NM_PLATFORM_GET, master));
 
 			g_assert (strstr (stdout_str, "LOWER_UP"));
 			g_free (stdout_str);
@@ -202,47 +196,59 @@ test_slave (int master, int type, SignalData *master_changed)
 	}
 
 	/* Set slave up and see if master gets up too */
-	g_assert (nm_platform_link_set_up (ifindex)); no_error ();
-	g_assert (nm_platform_link_is_connected (ifindex));
-	g_assert (nm_platform_link_is_connected (master));
-	accept_signal (link_changed);
-	accept_signal (master_changed);
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, master));
+	accept_signals (link_changed, 1, 3);
+	/* NM running, can cause additional change of addrgenmode */
+	accept_signals (master_changed, 1, 2);
 
 	/* Enslave again
 	 *
 	 * Gracefully succeed if already enslaved.
 	 */
-	g_assert (nm_platform_link_enslave (master, ifindex)); no_error ();
-	accept_signal (link_changed);
-	accept_signal (master_changed);
+	ensure_no_signal (link_changed);
+	g_assert (nm_platform_link_enslave (NM_PLATFORM_GET, master, ifindex));
+	accept_signals (link_changed, 0, 2);
+	ensure_no_signal (master_changed);
 
 	/* Set slave option */
 	switch (type) {
 	case NM_LINK_TYPE_BRIDGE:
-		g_assert (nm_platform_slave_set_option (ifindex, "priority", "789"));
-		no_error ();
-		value = nm_platform_slave_get_option (ifindex, "priority");
-		no_error ();
-		g_assert_cmpstr (value, ==, "789");
-		g_free (value);
+		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");
+			g_assert_cmpstr (value, ==, "789");
+			g_free (value);
+		}
 		break;
 	default:
 		break;
 	}
 
 	/* Release */
-	g_assert (nm_platform_link_release (master, ifindex));
-	g_assert_cmpint (nm_platform_link_get_master (ifindex), ==, 0); no_error ();
-	accept_signal (link_changed);
-	accept_signal (master_changed);
+	ensure_no_signal (link_changed);
+	g_assert (nm_platform_link_release (NM_PLATFORM_GET, master, ifindex));
+	g_assert_cmpint (nm_platform_link_get_master (NM_PLATFORM_GET, ifindex), ==, 0);
+	accept_signals (link_changed, 1, 3);
+	if (link_type != NM_LINK_TYPE_TEAM)
+		accept_signals (master_changed, 1, 2);
+	else
+		accept_signals (master_changed, 1, 1);
+
+	ensure_no_signal (master_changed);
 
 	/* Release again */
-	g_assert (!nm_platform_link_release (master, ifindex));
-	error (NM_PLATFORM_ERROR_NOT_SLAVE);
+	ensure_no_signal (link_changed);
+	g_assert (!nm_platform_link_release (NM_PLATFORM_GET, master, ifindex));
+
+	ensure_no_signal (master_changed);
 
 	/* Remove */
-	g_assert (nm_platform_link_delete (ifindex));
-	no_error ();
+	ensure_no_signal (link_changed);
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	accept_signals (master_changed, 0, 1);
+	accept_signals (link_changed, 0, 1);
 	accept_signal (link_removed);
 
 	free_signal (link_added);
@@ -262,53 +268,50 @@ test_software (NMLinkType link_type, const char *link_typename)
 	/* Add */
 	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
 	g_assert (software_add (link_type, DEVICE_NAME));
-	no_error ();
 	accept_signal (link_added);
-	g_assert (nm_platform_link_exists (DEVICE_NAME));
-	ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	g_assert (nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	g_assert (ifindex >= 0);
-	g_assert_cmpint (nm_platform_link_get_type (ifindex), ==, link_type);
-	g_assert_cmpstr (nm_platform_link_get_type_name (ifindex), ==, link_typename);
+	g_assert_cmpint (nm_platform_link_get_type (NM_PLATFORM_GET, ifindex), ==, link_type);
+	g_assert_cmpstr (nm_platform_link_get_type_name (NM_PLATFORM_GET, ifindex), ==, 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 (ifindex, &vlan_parent, &vlan_id));
-		g_assert_cmpint (vlan_parent, ==, nm_platform_link_get_ifindex (PARENT_NAME));
+		g_assert (nm_platform_vlan_get_info (NM_PLATFORM_GET, ifindex, &vlan_parent, &vlan_id));
+		g_assert_cmpint (vlan_parent, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME));
 		g_assert_cmpint (vlan_id, ==, VLAN_ID);
-		no_error ();
 	}
 
 	/* Add again */
 	g_assert (!software_add (link_type, DEVICE_NAME));
-	error (NM_PLATFORM_ERROR_EXISTS);
 
 	/* Set ARP/NOARP */
-	g_assert (nm_platform_link_uses_arp (ifindex));
-	g_assert (nm_platform_link_set_noarp (ifindex));
-	g_assert (!nm_platform_link_uses_arp (ifindex));
-	accept_signal (link_changed);
-	g_assert (nm_platform_link_set_arp (ifindex));
-	g_assert (nm_platform_link_uses_arp (ifindex));
+	g_assert (nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_set_noarp (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
+	accept_signals (link_changed, 1, 2);
+	g_assert (nm_platform_link_set_arp (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 	accept_signal (link_changed);
 
 	/* Set master option */
 	switch (link_type) {
 	case NM_LINK_TYPE_BRIDGE:
-		g_assert (nm_platform_master_set_option (ifindex, "forward_delay", "789"));
-		no_error ();
-		value = nm_platform_master_get_option (ifindex, "forward_delay");
-		no_error ();
-		g_assert_cmpstr (value, ==, "789");
-		g_free (value);
+		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");
+			g_assert_cmpstr (value, ==, "789");
+			g_free (value);
+		}
 		break;
 	case NM_LINK_TYPE_BOND:
-		g_assert (nm_platform_master_set_option (ifindex, "mode", "active-backup"));
-		no_error ();
-		value = nm_platform_master_get_option (ifindex, "mode");
-		no_error ();
-		/* When reading back, the output looks slightly different. */
-		g_assert (g_str_has_prefix (value, "active-backup"));
-		g_free (value);
+		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");
+			/* When reading back, the output looks slightly different. */
+			g_assert (g_str_has_prefix (value, "active-backup"));
+			g_free (value);
+		}
 		break;
 	default:
 		break;
@@ -326,33 +329,29 @@ test_software (NMLinkType link_type, const char *link_typename)
 	default:
 		break;
 	}
+	free_signal (link_changed);
 
 	/* Delete */
-	g_assert (nm_platform_link_delete (ifindex));
-	no_error ();
-	g_assert (!nm_platform_link_exists (DEVICE_NAME)); no_error ();
-	g_assert_cmpint (nm_platform_link_get_type (ifindex), ==, NM_LINK_TYPE_NONE);
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
-	g_assert (!nm_platform_link_get_type (ifindex));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
+	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));
 	accept_signal (link_removed);
 
 	/* Delete again */
-	g_assert (!nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME)));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
+	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME)));
 
 	/* 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 (vlan_parent));
+		g_assert (nm_platform_link_delete (NM_PLATFORM_GET, vlan_parent));
 		accept_signal (link_removed_parent);
 		free_signal (link_removed_parent);
 	}
 
 	/* No pending signal */
 	free_signal (link_added);
-	free_signal (link_changed);
 	free_signal (link_removed);
 }
 
@@ -365,14 +364,12 @@ test_bridge (void)
 static void
 test_bond (void)
 {
-	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
-	if (SETUP == nm_linux_platform_setup &&
+	if (nmtst_platform_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");
 		return;
 	}
-	NM_PRAGMA_WARNING_REENABLE
 
 	test_software (NM_LINK_TYPE_BOND, "bond");
 }
@@ -400,76 +397,70 @@ test_internal (void)
 	int ifindex;
 
 	/* Check the functions for non-existent devices */
-	g_assert (!nm_platform_link_exists (DEVICE_NAME)); no_error ();
-	g_assert (!nm_platform_link_get_ifindex (DEVICE_NAME));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (!nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
 	/* Add device */
-	g_assert (nm_platform_dummy_add (DEVICE_NAME));
-	no_error ();
+	g_assert (nm_platform_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 (DEVICE_NAME));
-	error (NM_PLATFORM_ERROR_EXISTS);
+	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_EXISTS);
 
 	/* Check device index, name and type */
-	ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	g_assert (ifindex > 0);
-	g_assert_cmpstr (nm_platform_link_get_name (ifindex), ==, DEVICE_NAME);
-	g_assert_cmpint (nm_platform_link_get_type (ifindex), ==, NM_LINK_TYPE_DUMMY);
-	g_assert_cmpstr (nm_platform_link_get_type_name (ifindex), ==, DUMMY_TYPEDESC);
+	g_assert_cmpstr (nm_platform_link_get_name (NM_PLATFORM_GET, ifindex), ==, DEVICE_NAME);
+	g_assert_cmpint (nm_platform_link_get_type (NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_DUMMY);
+	g_assert_cmpstr (nm_platform_link_get_type_name (NM_PLATFORM_GET, ifindex), ==, DUMMY_TYPEDESC);
 	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);
 
 	/* Up/connected */
-	g_assert (!nm_platform_link_is_up (ifindex)); no_error ();
-	g_assert (!nm_platform_link_is_connected (ifindex)); no_error ();
-	g_assert (nm_platform_link_set_up (ifindex)); no_error ();
-	g_assert (nm_platform_link_is_up (ifindex)); no_error ();
-	g_assert (nm_platform_link_is_connected (ifindex)); no_error ();
+	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 	accept_signal (link_changed);
-	g_assert (nm_platform_link_set_down (ifindex)); no_error ();
-	g_assert (!nm_platform_link_is_up (ifindex)); no_error ();
-	g_assert (!nm_platform_link_is_connected (ifindex)); no_error ();
+	g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 	accept_signal (link_changed);
 
 	/* arp/noarp */
-	g_assert (!nm_platform_link_uses_arp (ifindex));
-	g_assert (nm_platform_link_set_arp (ifindex));
-	g_assert (nm_platform_link_uses_arp (ifindex));
+	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_set_arp (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 	accept_signal (link_changed);
-	g_assert (nm_platform_link_set_noarp (ifindex));
-	g_assert (!nm_platform_link_uses_arp (ifindex));
+	g_assert (nm_platform_link_set_noarp (NM_PLATFORM_GET, ifindex));
+	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 	accept_signal (link_changed);
 
 	/* Features */
-	g_assert (!nm_platform_link_supports_carrier_detect (ifindex));
-	g_assert (nm_platform_link_supports_vlans (ifindex));
+	g_assert (!nm_platform_link_supports_carrier_detect (NM_PLATFORM_GET, ifindex));
+	g_assert (nm_platform_link_supports_vlans (NM_PLATFORM_GET, ifindex));
 
 	/* Set MAC address */
-	g_assert (nm_platform_link_set_address (ifindex, mac, sizeof (mac)));
-	address = nm_platform_link_get_address (ifindex, &addrlen);
+	g_assert (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac)));
+	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &addrlen);
 	g_assert (addrlen == sizeof(mac));
 	g_assert (!memcmp (address, mac, addrlen));
-	address = nm_platform_link_get_address (ifindex, NULL);
+	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, NULL);
 	g_assert (!memcmp (address, mac, addrlen));
 	accept_signal (link_changed);
 
 	/* Set MTU */
-	g_assert (nm_platform_link_set_mtu (ifindex, MTU));
-	no_error ();
-	g_assert_cmpint (nm_platform_link_get_mtu (ifindex), ==, MTU);
+	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU));
+	g_assert_cmpint (nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex), ==, MTU);
 	accept_signal (link_changed);
 
 	/* Delete device */
-	g_assert (nm_platform_link_delete (ifindex));
-	no_error ();
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 	accept_signal (link_removed);
 
 	/* Try to delete again */
-	g_assert (!nm_platform_link_delete (ifindex));
-	error (NM_PLATFORM_ERROR_NOT_FOUND);
+	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
 	free_signal (link_added);
 	free_signal (link_changed);
@@ -479,60 +470,56 @@ test_internal (void)
 static void
 test_external (void)
 {
-	NMPlatformLink link;
+	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;
 	int ifindex;
-	gboolean success;
 
 	run_command ("ip link add %s type %s", DEVICE_NAME, "dummy");
 	wait_signal (link_added);
-	g_assert (nm_platform_link_exists (DEVICE_NAME));
-	ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+
+	g_assert (nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	g_assert (ifindex > 0);
-	g_assert_cmpstr (nm_platform_link_get_name (ifindex), ==, DEVICE_NAME);
-	g_assert_cmpint (nm_platform_link_get_type (ifindex), ==, NM_LINK_TYPE_DUMMY);
-	g_assert_cmpstr (nm_platform_link_get_type_name (ifindex), ==, DUMMY_TYPEDESC);
+	g_assert_cmpstr (nm_platform_link_get_name (NM_PLATFORM_GET, ifindex), ==, DEVICE_NAME);
+	g_assert_cmpint (nm_platform_link_get_type (NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_DUMMY);
+	g_assert_cmpstr (nm_platform_link_get_type_name (NM_PLATFORM_GET, ifindex), ==, DUMMY_TYPEDESC);
 	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);
 
-	success = nm_platform_link_get (ifindex, &link);
-	g_assert (success);
-	if (!link.driver) {
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+	g_assert (pllink);
+	if (!pllink->initialized) {
 		/* we still lack the notification via UDEV. Expect another link changed signal. */
 		wait_signal (link_changed);
 	}
 
 	/* Up/connected/arp */
-	g_assert (!nm_platform_link_is_up (ifindex));
-	g_assert (!nm_platform_link_is_connected (ifindex));
-	g_assert (!nm_platform_link_uses_arp (ifindex));
+	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
+	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);
 	wait_signal (link_changed);
-	g_assert (nm_platform_link_is_up (ifindex));
-	g_assert (nm_platform_link_is_connected (ifindex));
+
+	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);
 	wait_signal (link_changed);
-	g_assert (!nm_platform_link_is_up (ifindex));
-	g_assert (!nm_platform_link_is_connected (ifindex));
-	/* This test doesn't trigger a netlink event at least on
-	 * 3.8.2-206.fc18.x86_64. Disabling the waiting and checking code
-	 * because of that.
-	 */
+	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);
-#if 0
 	wait_signal (link_changed);
-	g_assert (nm_platform_link_uses_arp (ifindex));
-#endif
+	g_assert (nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 	run_command ("ip link set %s arp off", DEVICE_NAME);
-#if 0
 	wait_signal (link_changed);
-	g_assert (!nm_platform_link_uses_arp (ifindex));
-#endif
+	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 
 	run_command ("ip link del %s", DEVICE_NAME);
 	wait_signal (link_removed);
-	g_assert (!nm_platform_link_exists (DEVICE_NAME));
+	accept_signals (link_changed, 0, 1);
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
 
 	free_signal (link_added);
 	free_signal (link_changed);
@@ -548,12 +535,12 @@ init_tests (int *argc, char ***argv)
 void
 setup_tests (void)
 {
-	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
-	nm_platform_link_delete (nm_platform_link_get_ifindex (SLAVE_NAME));
-	nm_platform_link_delete (nm_platform_link_get_ifindex (PARENT_NAME));
-	g_assert (!nm_platform_link_exists (DEVICE_NAME));
-	g_assert (!nm_platform_link_exists (SLAVE_NAME));
-	g_assert (!nm_platform_link_exists (PARENT_NAME));
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, SLAVE_NAME));
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, SLAVE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, PARENT_NAME));
 
 	g_test_add_func ("/link/bogus", test_bogus);
 	g_test_add_func ("/link/loopback", test_loopback);
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
new file mode 100644
index 00000000..88bb0707
--- /dev/null
+++ b/src/platform/tests/test-nmp-object.c
@@ -0,0 +1,425 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "nmp-object.h"
+
+#include "nm-logging.h"
+
+#include "nm-test-utils.h"
+
+struct {
+	GList *udev_devices;
+} global;
+
+/******************************************************************/
+
+static gboolean
+_nmp_object_id_equal (const NMPObject *a, const NMPObject *b)
+{
+	gboolean a_b = nmp_object_id_equal (a, b);
+
+	g_assert (NM_IN_SET (a_b, FALSE, TRUE) && a_b == nmp_object_id_equal (b, a));
+	return a_b;
+}
+#define nmp_object_id_equal _nmp_object_id_equal
+
+static gboolean
+_nmp_object_equal (const NMPObject *a, const NMPObject *b)
+{
+	gboolean a_b = nmp_object_equal (a, b);
+
+	g_assert (NM_IN_SET (a_b, FALSE, TRUE) && a_b == nmp_object_equal (b, a));
+	return a_b;
+}
+#define nmp_object_equal _nmp_object_equal
+
+/******************************************************************/
+
+static void
+_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMPCacheId *cache_id, const NMPObject *obj, gboolean contains)
+{
+	const NMPlatformObject *const *objects;
+	guint i, len;
+	gboolean found;
+
+	g_assert (cache_id);
+	g_assert (NMP_OBJECT_IS_VALID (obj));
+
+	g_assert (nmp_cache_lookup_obj (cache, obj) == obj);
+
+	objects = nmp_cache_lookup_multi (cache, cache_id, &len);
+
+	g_assert ((len == 0 && !objects) || (len > 0 && objects && !objects[len]));
+
+	found = FALSE;
+	for (i = 0; i < len; i++) {
+		NMPObject *o;
+
+		g_assert (objects[i]);
+		o = NMP_OBJECT_UP_CAST (objects[i]);
+		g_assert (NMP_OBJECT_IS_VALID (o));
+
+		if (obj == o) {
+			g_assert (!found);
+			found = TRUE;
+		}
+	}
+
+	g_assert (!!contains == found);
+}
+
+/******************************************************************/
+
+typedef struct {
+	NMPCache *cache;
+	NMPCacheOpsType expected_ops_type;
+	const NMPObject *obj_clone;
+	NMPObject *new_clone;
+	gboolean was_visible;
+	gboolean called;
+} _NMPCacheUpdateData;
+
+static void
+_nmp_cache_update_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+{
+	_NMPCacheUpdateData *data = user_data;
+
+	g_assert (data);
+	g_assert (!data->called);
+	g_assert (data->cache == cache);
+
+	g_assert_cmpint (data->expected_ops_type, ==, ops_type);
+
+	switch (ops_type) {
+	case NMP_CACHE_OPS_ADDED:
+		g_assert (!old);
+		g_assert (NMP_OBJECT_IS_VALID (new));
+		g_assert (nmp_object_is_alive (new));
+		g_assert (nmp_object_id_equal (data->obj_clone, new));
+		g_assert (nmp_object_equal (data->obj_clone, new));
+		break;
+	case NMP_CACHE_OPS_UPDATED:
+		g_assert (NMP_OBJECT_IS_VALID (old));
+		g_assert (NMP_OBJECT_IS_VALID (new));
+		g_assert (nmp_object_is_alive (old));
+		g_assert (nmp_object_is_alive (new));
+		g_assert (nmp_object_id_equal (data->obj_clone, new));
+		g_assert (nmp_object_id_equal (data->obj_clone, old));
+		g_assert (nmp_object_id_equal (old, new));
+		g_assert (nmp_object_equal (data->obj_clone, new));
+		g_assert (!nmp_object_equal (data->obj_clone, old));
+		g_assert (!nmp_object_equal (old, new));
+		break;
+	case NMP_CACHE_OPS_REMOVED:
+		g_assert (!new);
+		g_assert (NMP_OBJECT_IS_VALID (old));
+		g_assert (nmp_object_is_alive (old));
+		g_assert (nmp_object_id_equal (data->obj_clone, old));
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	data->was_visible = old ? nmp_object_is_visible (old) : FALSE;
+	data->new_clone = new ? nmp_object_clone (new, FALSE) : NULL;
+	data->called = TRUE;
+}
+
+static void
+_nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCacheOpsType expected_ops_type)
+{
+	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;
+	const NMPObject *obj_old;
+	_NMPCacheUpdateData data = {
+		.cache = cache,
+		.expected_ops_type = expected_ops_type,
+		.obj_clone = obj_clone,
+	};
+
+	obj_old = nmp_cache_lookup_link (cache, obj->object.ifindex);
+	if (obj_old && obj_old->_link.udev.device)
+		obj_clone->_link.udev.device = g_object_ref (obj_old->_link.udev.device);
+	_nmp_object_fixup_link_udev_fields (obj_clone, nmp_cache_use_udev_get (cache));
+
+	g_assert (cache);
+	g_assert (NMP_OBJECT_IS_VALID (obj));
+
+	ops_type = nmp_cache_update_netlink (cache, obj, &obj2, &was_visible, _nmp_cache_update_hook, &data);
+
+	new_clone = data.new_clone;
+
+	g_assert_cmpint (ops_type, ==, expected_ops_type);
+
+	if (ops_type != NMP_CACHE_OPS_UNCHANGED) {
+		g_assert (NMP_OBJECT_IS_VALID (obj2));
+		g_assert (data.called);
+		g_assert_cmpint (data.was_visible, ==, was_visible);
+
+		if (ops_type == NMP_CACHE_OPS_REMOVED)
+			g_assert (!data.new_clone);
+		else {
+			g_assert (data.new_clone);
+			g_assert (nmp_object_equal (obj2, data.new_clone));
+		}
+	} else {
+		g_assert (!data.called);
+		g_assert (!obj2 || was_visible == nmp_object_is_visible (obj2));
+	}
+
+	g_assert (!obj2 || nmp_object_id_equal (obj, obj2));
+	if (ops_type != NMP_CACHE_OPS_REMOVED && obj2)
+		g_assert (nmp_object_equal (obj, obj2));
+
+	if (out_obj)
+		*out_obj = obj2;
+	else
+		nmp_object_unref (obj2);
+	if (out_was_visible)
+		*out_was_visible = was_visible;
+}
+
+static const NMPlatformLink pl_link_2 = {
+	.ifindex = 2,
+	.name = "eth0",
+	.type = NM_LINK_TYPE_ETHERNET,
+};
+
+static const NMPlatformLink pl_link_3 = {
+	.ifindex = 3,
+	.name = "wlan0",
+	.type = NM_LINK_TYPE_WIFI,
+};
+
+static void
+test_cache_link (void)
+{
+	NMPCache *cache;
+	NMPObject *obj1, *obj2;
+	NMPObject objs1;
+	gboolean was_visible;
+	NMPCacheId cache_id_storage;
+	GUdevDevice *udev_device_2 = g_list_nth_data (global.udev_devices, 0);
+	GUdevDevice *udev_device_3 = g_list_nth_data (global.udev_devices, 0);
+	NMPCacheOpsType ops_type;
+
+	cache = nmp_cache_new ();
+
+	nmp_cache_use_udev_set (cache, g_rand_int_range (nmtst_get_rand (), 0, 2));
+
+	/* if we have a link, and don't set is_in_netlink, adding it has no effect. */
+	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	g_assert (NMP_OBJECT_UP_CAST (&obj1->object) == obj1);
+	g_assert (!nmp_object_is_alive (obj1));
+	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED);
+	ASSERT_nmp_cache_is_consistent (cache);
+	g_assert (!obj2);
+	g_assert (!was_visible);
+	g_assert (!nmp_cache_lookup_obj (cache, obj1));
+	g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
+	nmp_object_unref (obj1);
+
+	/* Only when setting @is_in_netlink the link is added. */
+	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	obj1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (obj1));
+	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_ADDED);
+	ASSERT_nmp_cache_is_consistent (cache);
+	g_assert (nmp_object_equal (obj1, obj2));
+	g_assert (!was_visible);
+	g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+	g_assert (nmp_object_is_visible (obj2));
+	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
+	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+	nmp_object_unref (obj1);
+	nmp_object_unref (obj2);
+
+	/* updating the same link with identical value, has no effect. */
+	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	obj1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (obj1));
+	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED);
+	ASSERT_nmp_cache_is_consistent (cache);
+	g_assert (obj2 != obj1);
+	g_assert (nmp_object_equal (obj1, obj2));
+	g_assert (was_visible);
+	g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+	nmp_object_unref (obj1);
+	nmp_object_unref (obj2);
+
+	/* remove the link from netlink */
+	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	g_assert (!nmp_object_is_alive (obj1));
+	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_REMOVED);
+	ASSERT_nmp_cache_is_consistent (cache);
+	g_assert (obj2 != obj1);
+	g_assert (was_visible);
+	g_assert (!nmp_cache_lookup_obj (cache, obj1));
+	g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
+	nmp_object_unref (obj1);
+	nmp_object_unref (obj2);
+
+	if (udev_device_2) {
+		/* now add the link only with aspect UDEV. */
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_2.ifindex, udev_device_2, &obj2, &was_visible, NULL, NULL);
+		ASSERT_nmp_cache_is_consistent (cache);
+		g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED);
+		g_assert (!was_visible);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+		g_assert (!nmp_object_is_visible (obj2));
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, FALSE);
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+		nmp_object_unref (obj2);
+	}
+
+	/* add it in netlink too. */
+	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	obj1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (obj1));
+	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED);
+	ASSERT_nmp_cache_is_consistent (cache);
+	g_assert (nmp_object_equal (obj1, obj2));
+	g_assert (!was_visible);
+	g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+	g_assert (nmp_object_is_visible (obj2));
+	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
+	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+	nmp_object_unref (obj1);
+	nmp_object_unref (obj2);
+
+	/* remove again from netlink. */
+	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	obj1->_link.netlink.is_in_netlink = FALSE;
+	g_assert (!nmp_object_is_alive (obj1));
+	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED);
+	ASSERT_nmp_cache_is_consistent (cache);
+	g_assert (obj2 != obj1);
+	g_assert (was_visible);
+	if (udev_device_2) {
+		g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+		g_assert (!nmp_object_is_visible (obj2));
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, FALSE);
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+	} else {
+		g_assert (nmp_cache_lookup_obj (cache, obj1) == NULL);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == NULL);
+		g_assert (nmp_object_is_visible (obj2));
+	}
+	nmp_object_unref (obj1);
+	nmp_object_unref (obj2);
+
+	/* now another link only with aspect UDEV. */
+	if (udev_device_3) {
+		/* now add the link only with aspect UDEV. */
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, udev_device_3, &obj2, &was_visible, NULL, NULL);
+		g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED);
+		ASSERT_nmp_cache_is_consistent (cache);
+		g_assert (NMP_OBJECT_IS_VALID (obj2));
+		g_assert (!was_visible);
+		g_assert (!nmp_object_is_visible (obj2));
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, FALSE);
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+		g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, FALSE);
+		g_assert_cmpint (obj2->link.initialized, ==, FALSE);
+		nmp_object_unref (obj2);
+
+		/* add it in netlink too. */
+		obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3);
+		obj1->_link.netlink.is_in_netlink = TRUE;
+		g_assert (nmp_object_is_alive (obj1));
+		_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UPDATED);
+		ASSERT_nmp_cache_is_consistent (cache);
+		g_assert (obj2 != obj1);
+		g_assert (nmp_object_equal (obj1, obj2));
+		g_assert (!was_visible);
+		g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
+		g_assert (nmp_object_is_visible (obj2));
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+		g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE);
+		g_assert_cmpint (obj2->link.initialized, ==, TRUE);
+		nmp_object_unref (obj1);
+		nmp_object_unref (obj2);
+
+		/* remove UDEV. */
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj2, &was_visible, NULL, NULL);
+		g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_UPDATED);
+		ASSERT_nmp_cache_is_consistent (cache);
+		g_assert (was_visible);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
+		g_assert (nmp_object_is_visible (obj2));
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
+		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+		g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE);
+		g_assert_cmpint (obj2->link.initialized, ==, !nmp_cache_use_udev_get (cache));
+		nmp_object_unref (obj2);
+	}
+
+	nmp_cache_free (cache);
+}
+
+/******************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	int result;
+	gs_unref_object GUdevClient *udev_client = NULL;
+
+	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+
+	udev_client = g_udev_client_new ((const char *[]) { "net", NULL });
+	{
+		gs_unref_object GUdevEnumerator *udev_enumerator = g_udev_enumerator_new (udev_client);
+
+		g_udev_enumerator_add_match_subsystem (udev_enumerator, "net");
+
+		/* Demand that the device is initialized (udev rules ran,
+		 * device has a stable name now) in case udev is running
+		 * (not in a container). */
+		if (access ("/sys", W_OK) == 0)
+			g_udev_enumerator_add_match_is_initialized (udev_enumerator);
+
+		global.udev_devices = g_udev_enumerator_execute (udev_enumerator);
+	}
+
+	g_test_add_func ("/nmp-object/cache_link", test_cache_link);
+
+	result = g_test_run ();
+
+	while (global.udev_devices) {
+		g_object_unref (global.udev_devices->data);
+		global.udev_devices = g_list_remove (global.udev_devices, global.udev_devices->data);
+	}
+
+	return result;
+}
+
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 2d3caaf1..cf322dec 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -1,5 +1,7 @@
 #include "config.h"
 
+#include <linux/rtnetlink.h>
+
 #include "test-common.h"
 #include "nm-test-utils.h"
 #include "NetworkManagerUtils.h"
@@ -7,7 +9,7 @@
 #define DEVICE_NAME "nm-test-device"
 
 static void
-ip4_route_callback (NMPlatform *platform, int ifindex, 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, NMPlatformReason reason, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -22,14 +24,12 @@ ip4_route_callback (NMPlatform *platform, int ifindex, NMPlatformIP4Route *recei
 	if (data->loop)
 		g_main_loop_quit (data->loop);
 
-	if (data->received)
-		g_error ("Received signal '%s' a second time.", data->name);
-
-	data->received = TRUE;
+	data->received_count++;
+	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
-ip6_route_callback (NMPlatform *platform, int ifindex, 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, NMPlatformReason reason, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -44,16 +44,14 @@ ip6_route_callback (NMPlatform *platform, int ifindex, NMPlatformIP6Route *recei
 	if (data->loop)
 		g_main_loop_quit (data->loop);
 
-	if (data->received)
-		g_error ("Received signal '%s' a second time.", data->name);
-
-	data->received = TRUE;
+	data->received_count++;
+	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
 test_ip4_route_metric0 (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	SignalData *route_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_route_callback);
 	SignalData *route_changed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip4_route_callback);
 	SignalData *route_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_route_callback);
@@ -67,48 +65,42 @@ test_ip4_route_metric0 (void)
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
 
 	/* add the first route */
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, metric, mss));
-	no_error ();
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, metric, mss));
 	accept_signal (route_added);
 
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
 	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
 
 	/* Deleting route with metric 0 does nothing */
-	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, 0));
-	no_error ();
-	g_assert (!route_removed->received);
+	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	ensure_no_signal (route_removed);
 
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
 	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
 
 	/* add the second route */
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, 0, mss));
-	no_error ();
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, 0, mss));
 	accept_signal (route_added);
 
 	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, 0);
 	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
 
 	/* Delete route with metric 0 */
-	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, 0));
-	no_error ();
+	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	accept_signal (route_removed);
 
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
 	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
 
 	/* Delete route with metric 0 again (we expect nothing to happen) */
-	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, 0));
-	no_error ();
-	g_assert (!route_removed->received);
+	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	ensure_no_signal (route_removed);
 
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
 	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
 
 	/* Delete the other route */
-	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, metric));
-	no_error ();
+	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	accept_signal (route_removed);
 
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
@@ -122,7 +114,7 @@ test_ip4_route_metric0 (void)
 static void
 test_ip4_route (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	SignalData *route_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_route_callback);
 	SignalData *route_changed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip4_route_callback);
 	SignalData *route_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_route_callback);
@@ -139,40 +131,31 @@ test_ip4_route (void)
 	inet_pton (AF_INET, "198.51.100.1", &gateway);
 
 	/* Add route to gateway */
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 32, INADDR_ANY, 0, metric, mss));
-	no_error ();
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 32, INADDR_ANY, 0, metric, mss));
 	accept_signal (route_added);
 
 	/* Add route */
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
-	no_error ();
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss));
-	no_error ();
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss));
 	assert_ip4_route_exists (TRUE, DEVICE_NAME, network, plen, metric);
-	no_error ();
 	accept_signal (route_added);
 
 	/* Add route again */
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss));
-	no_error ();
-	accept_signal (route_changed);
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss));
+	accept_signals (route_changed, 0, 1);
 
 	/* Add default route */
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, 0, 0, metric);
-	no_error ();
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss));
-	no_error ();
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss));
 	assert_ip4_route_exists (TRUE, DEVICE_NAME, 0, 0, metric);
-	no_error ();
 	accept_signal (route_added);
 
 	/* Add default route again */
-	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss));
-	no_error ();
-	accept_signal (route_changed);
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss));
+	accept_signals (route_changed, 0, 1);
 
 	/* Test route listing */
-	routes = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+	routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 	memset (rts, 0, sizeof (rts));
 	rts[0].source = NM_IP_CONFIG_SOURCE_USER;
 	rts[0].network = gateway;
@@ -181,6 +164,7 @@ test_ip4_route (void)
 	rts[0].gateway = INADDR_ANY;
 	rts[0].metric = metric;
 	rts[0].mss = mss;
+	rts[0].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK);
 	rts[1].source = NM_IP_CONFIG_SOURCE_USER;
 	rts[1].network = network;
 	rts[1].plen = plen;
@@ -188,6 +172,7 @@ test_ip4_route (void)
 	rts[1].gateway = gateway;
 	rts[1].metric = metric;
 	rts[1].mss = mss;
+	rts[1].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE);
 	rts[2].source = NM_IP_CONFIG_SOURCE_USER;
 	rts[2].network = 0;
 	rts[2].plen = 0;
@@ -195,20 +180,18 @@ test_ip4_route (void)
 	rts[2].gateway = gateway;
 	rts[2].metric = metric;
 	rts[2].mss = mss;
+	rts[2].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE);
 	g_assert_cmpint (routes->len, ==, 3);
-	g_assert (!memcmp (routes->data, rts, sizeof (rts)));
 	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len, TRUE);
 	g_array_unref (routes);
 
 	/* Remove route */
-	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, metric));
-	no_error ();
+	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
 	accept_signal (route_removed);
 
 	/* Remove route again */
-	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, metric));
-	no_error ();
+	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 
 	free_signal (route_added);
 	free_signal (route_changed);
@@ -218,7 +201,7 @@ test_ip4_route (void)
 static void
 test_ip6_route (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	SignalData *route_added = add_signal (NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip6_route_callback);
 	SignalData *route_changed = add_signal (NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip6_route_callback);
 	SignalData *route_removed = add_signal (NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip6_route_callback);
@@ -235,40 +218,31 @@ test_ip6_route (void)
 	inet_pton (AF_INET6, "2001:db8:c:d:1:2:3:4", &gateway);
 
 	/* Add route to gateway */
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 128, in6addr_any, metric, mss));
-	no_error ();
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 128, in6addr_any, metric, mss));
 	accept_signal (route_added);
 
 	/* Add route */
-	g_assert (!nm_platform_ip6_route_exists (ifindex, network, plen, metric));
-	no_error ();
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, metric, mss));
-	no_error ();
-	g_assert (nm_platform_ip6_route_exists (ifindex, network, plen, metric));
-	no_error ();
+	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, metric, mss));
+	g_assert (nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	accept_signal (route_added);
 
 	/* Add route again */
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, metric, mss));
-	no_error ();
-	accept_signal (route_changed);
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, metric, mss));
+	accept_signals (route_changed, 0, 1);
 
 	/* Add default route */
-	g_assert (!nm_platform_ip6_route_exists (ifindex, in6addr_any, 0, metric));
-	no_error ();
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway, metric, mss));
-	no_error ();
-	g_assert (nm_platform_ip6_route_exists (ifindex, in6addr_any, 0, metric));
-	no_error ();
+	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway, metric, mss));
+	g_assert (nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
 	accept_signal (route_added);
 
 	/* Add default route again */
-	g_assert (nm_platform_ip6_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway, metric, mss));
-	no_error ();
-	accept_signal (route_changed);
+	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway, metric, mss));
+	accept_signals (route_changed, 0, 1);
 
 	/* Test route listing */
-	routes = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ALL);
+	routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 	memset (rts, 0, sizeof (rts));
 	rts[0].source = NM_IP_CONFIG_SOURCE_USER;
 	rts[0].network = gateway;
@@ -292,19 +266,16 @@ test_ip6_route (void)
 	rts[2].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[2].mss = mss;
 	g_assert_cmpint (routes->len, ==, 3);
-	g_assert (!memcmp (routes->data, rts, sizeof (rts)));
 	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len, TRUE);
 	g_array_unref (routes);
 
 	/* Remove route */
-	g_assert (nm_platform_ip6_route_delete (ifindex, network, plen, metric));
-	no_error ();
-	g_assert (!nm_platform_ip6_route_exists (ifindex, network, plen, metric));
+	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	accept_signal (route_removed);
 
 	/* Remove route again */
-	g_assert (nm_platform_ip6_route_delete (ifindex, network, plen, metric));
-	no_error ();
+	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 
 	free_signal (route_added);
 	free_signal (route_changed);
@@ -322,13 +293,13 @@ setup_tests (void)
 {
 	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
 
-	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
-	g_assert (!nm_platform_link_exists (DEVICE_NAME));
-	g_assert (nm_platform_dummy_add (DEVICE_NAME));
+	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 (nm_platform_link_set_up (nm_platform_link_get_ifindex (DEVICE_NAME)));
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
 
 	g_test_add_func ("/route/ip4", test_ip4_route);
 	g_test_add_func ("/route/ip6", test_ip6_route);
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
index daef881b..068c404e 100644
--- a/src/platform/wifi/wifi-utils.c
+++ b/src/platform/wifi/wifi-utils.c
@@ -162,20 +162,13 @@ wifi_utils_deinit (WifiData *data)
 }
 
 gboolean
-wifi_utils_is_wifi (const char *iface, const char *sysfs_path, const char *devtype)
+wifi_utils_is_wifi (const char *iface, const char *sysfs_path)
 {
 	char phy80211_path[255];
 	struct stat s;
 
 	g_return_val_if_fail (iface != NULL, FALSE);
 
-	if (g_strcmp0 (devtype, "wlan") == 0) {
-		/* All Wi-Fi drivers should set DEVTYPE=wlan.  Since the kernel's
-		 * cfg80211/nl80211 stack does, this check should match any nl80211
-		 * capable driver (including mac82011-based ones). */
-		return TRUE;
-	}
-
 	if (sysfs_path) {
 		/* Check for nl80211 sysfs paths */
 		g_snprintf (phy80211_path, sizeof (phy80211_path), "%s/phy80211", sysfs_path);
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index eb37bc22..588386ab 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -29,7 +29,7 @@
 
 typedef struct WifiData WifiData;
 
-gboolean wifi_utils_is_wifi (const char *iface, const char *sysfs_path, const char *devtype);
+gboolean wifi_utils_is_wifi (const char *iface, const char *sysfs_path);
 
 WifiData *wifi_utils_init (const char *iface, int ifindex, gboolean check_scan);