about summary refs log tree commit diff
path: root/src/platform/nm-fake-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
committerMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
commit90e8691111889a7b5f3c812f5a41f15a8a058913 (patch)
treef101a879eca27c34a9bfa5f3da52266b22539a36 /src/platform/nm-fake-platform.c
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'src/platform/nm-fake-platform.c')
-rw-r--r--src/platform/nm-fake-platform.c1437
1 files changed, 679 insertions, 758 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 38706f37..c199c5ed 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -32,6 +32,7 @@
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
+#include "nm-platform-private.h"
 #include "nmp-object.h"
 
 #include "nm-test-utils-core.h"
@@ -39,20 +40,14 @@
 /*****************************************************************************/
 
 typedef struct {
-	NMPlatformLink link;
-
+	const NMPObject *obj;
 	char *udi;
-	NMPObject *lnk;
 	struct in6_addr ip6_lladdr;
 } NMFakePlatformLink;
 
 typedef struct {
 	GHashTable *options;
 	GArray *links;
-	GArray *ip4_addresses;
-	GArray *ip6_addresses;
-	GArray *ip4_routes;
-	GArray *ip6_routes;
 } NMFakePlatformPrivate;
 
 struct _NMFakePlatform {
@@ -96,7 +91,22 @@ G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
 
 /*****************************************************************************/
 
-static void link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal);
+static void link_changed (NMPlatform *platform,
+                          NMFakePlatformLink *device,
+                          NMPCacheOpsType cache_op,
+                          const NMPObject *obj_old);
+
+static gboolean ipx_address_delete (NMPlatform *platform,
+                                    int addr_family,
+                                    int ifindex,
+                                    gconstpointer addr,
+                                    const guint8 *plen,
+                                    gconstpointer peer_addr);
+
+static gboolean ipx_route_delete (NMPlatform *platform,
+                                  int addr_family,
+                                  int ifindex,
+                                  const NMPObject *obj);
 
 static gboolean ip6_address_add (NMPlatform *platform,
                                  int ifindex,
@@ -110,14 +120,6 @@ static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in
 
 /*****************************************************************************/
 
-static gboolean
-_ip4_address_equal_peer_net (in_addr_t peer1, in_addr_t peer2, guint8 plen)
-{
-	return ((peer1 ^ peer2) & nm_utils_ip4_prefix_to_netmask (plen)) == 0;
-}
-
-/*****************************************************************************/
-
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
 	G_STMT_START { \
 		const char *const _pathid = (pathid); \
@@ -158,288 +160,338 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	return g_strdup (g_hash_table_lookup (priv->options, path));
 }
 
-static const char *
-type_to_type_name (NMLinkType type)
-{
-	switch (type) {
-	case NM_LINK_TYPE_UNKNOWN:
-		return "unknown";
-	case NM_LINK_TYPE_LOOPBACK:
-		return "loopback";
-	case NM_LINK_TYPE_ETHERNET:
-		return "ethernet";
-	case NM_LINK_TYPE_DUMMY:
-		return "dummy";
-	case NM_LINK_TYPE_BRIDGE:
-		return "bridge";
-	case NM_LINK_TYPE_BOND:
-		return "bond";
-	case NM_LINK_TYPE_TEAM:
-		return "team";
-	case NM_LINK_TYPE_VLAN:
-		return "vlan";
-	case NM_LINK_TYPE_NONE:
-	default:
-		return NULL;
-	}
-}
-
-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.kind = type_to_type_name (type);
-	device->link.driver = type_to_type_name (type);
-	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.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP);
-		break;
-	default:
-		device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP);
-		break;
-	}
-}
-
 static NMFakePlatformLink *
 link_get (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMFakePlatformLink *device;
+	int idx;
+
+	if (ifindex <= 0)
+		g_return_val_if_reached (NULL);
 
-	if (ifindex >= priv->links->len)
+	idx = ifindex - 1;
+	if (idx >= priv->links->len)
 		goto not_found;
-	device = &g_array_index (priv->links, NMFakePlatformLink, ifindex);
-	if (!device->link.ifindex)
+
+	device = &g_array_index (priv->links, NMFakePlatformLink, idx);
+	if (!device->obj)
 		goto not_found;
 
+	g_assert (ifindex == NMP_OBJECT_CAST_LINK (device->obj)->ifindex);
+	g_assert (device->obj == nm_platform_link_get_obj (platform, ifindex, FALSE));
+
 	return device;
 not_found:
 	_LOGD ("link not found: %d", ifindex);
 	return NULL;
 }
 
-static GArray *
-link_get_all (NMPlatform *platform)
+static void
+link_add_prepare (NMPlatform *platform,
+                  NMFakePlatformLink *device,
+                  NMPObject *obj_tmp)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *links = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), priv->links->len);
-	int i;
+	gboolean connected;
 
-	for (i = 0; i < priv->links->len; i++)
-		if (g_array_index (priv->links, NMFakePlatformLink, i).link.ifindex)
-			g_array_append_val (links, g_array_index (priv->links, NMFakePlatformLink, i).link);
+	/* we must clear the driver, because platform cache want's to set it */
+	g_assert (obj_tmp->link.driver == g_intern_string (obj_tmp->link.driver));
+	obj_tmp->link.driver = NULL;
 
-	return links;
-}
-
-static const NMPlatformLink *
-_nm_platform_link_get (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
+	if (NM_IN_SET (obj_tmp->link.type, NM_LINK_TYPE_BRIDGE,
+	                                   NM_LINK_TYPE_BOND)) {
+		connected = FALSE;
+		if (NM_FLAGS_HAS (obj_tmp->link.n_ifi_flags, IFF_UP)) {
+			NMPLookup lookup;
+			NMDedupMultiIter iter;
+			const NMPObject *slave_candidate = NULL;
+
+			nmp_cache_iter_for_each (&iter,
+			                         nmp_cache_lookup (nm_platform_get_cache (platform),
+			                                           nmp_lookup_init_obj_type (&lookup,
+			                                                                     NMP_OBJECT_TYPE_LINK)),
+			                         &slave_candidate) {
+				if (nmp_cache_link_connected_for_slave (obj_tmp->link.ifindex, slave_candidate)) {
+					connected = TRUE;
+					break;
+				}
+			}
+		}
+	} else
+		connected = NM_FLAGS_HAS (obj_tmp->link.n_ifi_flags, IFF_UP);
 
-	return device ? &device->link : NULL;
+	obj_tmp->link.n_ifi_flags = NM_FLAGS_ASSIGN (obj_tmp->link.n_ifi_flags, IFF_LOWER_UP, connected);
+	obj_tmp->link.connected = connected;
 }
 
-static const NMPlatformLink *
-_nm_platform_link_get_by_ifname (NMPlatform *platform, const char *ifname)
+static NMFakePlatformLink *
+link_add_pre (NMPlatform *platform,
+              const char *name,
+              NMLinkType type,
+              const void *address,
+              size_t address_len)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	guint i;
+	NMFakePlatformLink *device;
+	int ifindex;
+	NMPObject *o;
+	NMPlatformLink *link;
+	gs_free char *ip6_lladdr = NULL;
 
-	for (i = 0; i < priv->links->len; i++) {
-		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+	g_assert (!name || strlen (name) < IFNAMSIZ);
 
-		if (!strcmp (device->link.name, ifname))
-			return &device->link;
-	}
-	return NULL;
-}
+	g_array_set_size (priv->links, priv->links->len + 1);
+	device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1);
+	ifindex = priv->links->len;
 
-static const NMPlatformLink *
-_nm_platform_link_get_by_address (NMPlatform *platform,
-                                  gconstpointer address,
-                                  size_t length)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	guint i;
+	memset (device, 0, sizeof (*device));
 
-	if (   length == 0
-	    || length > NM_UTILS_HWADDR_LEN_MAX
-	    || !address)
-		g_return_val_if_reached (NULL);
+	o = nmp_object_new_link (ifindex);
+	link = NMP_OBJECT_CAST_LINK (o);
 
-	for (i = 0; i < priv->links->len; i++) {
-		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+	ip6_lladdr = ifindex > 0 ? g_strdup_printf ("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL;
 
-		if (   device->link.addr.len == length
-		    && memcmp (device->link.addr.data, address, length) == 0) {
-			return &device->link;
-		}
+	link->ifindex = name ? ifindex : 0;
+	link->type = type;
+	link->kind = g_intern_string (nm_link_type_to_string (type));
+	link->initialized = TRUE;
+	if (name)
+		strcpy (link->name, name);
+	switch (link->type) {
+	case NM_LINK_TYPE_DUMMY:
+		link->n_ifi_flags = NM_FLAGS_SET (link->n_ifi_flags, IFF_NOARP);
+		break;
+	default:
+		link->n_ifi_flags = NM_FLAGS_UNSET (link->n_ifi_flags, IFF_NOARP);
+		break;
 	}
-	return NULL;
-}
 
-static const NMPObject *
-link_get_lnk (NMPlatform *platform,
-              int ifindex,
-              NMLinkType link_type,
-              const NMPlatformLink **out_link)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
+	o->_link.netlink.is_in_netlink = TRUE;
 
-	if (!device)
-		return NULL;
-
-	NM_SET_OUT (out_link, &device->link);
-
-	if (!device->lnk)
-		return NULL;
-
-	if (link_type == NM_LINK_TYPE_NONE)
-		return device->lnk;
+	if (address) {
+		g_assert (address_len > 0 && address_len <= sizeof (link->addr.data));
+		memcpy (link->addr.data, address, address_len);
+		link->addr.len = address_len;
+	} else
+		g_assert (address_len == 0);
 
-	if (   link_type != device->link.type
-	    || link_type != NMP_OBJECT_GET_CLASS (device->lnk)->lnk_link_type)
-		return NULL;
+	device->obj = o;
+	device->udi = g_strdup_printf ("fake:%d", ifindex);
+	device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr);
 
-	return device->lnk;
+	return device;
 }
 
 static gboolean
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
+          const char *veth_peer,
           const void *address,
           size_t address_len,
           const NMPlatformLink **out_link)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	NMFakePlatformLink device;
-	NMFakePlatformLink *new_device;
+	NMFakePlatformLink *device;
+	NMFakePlatformLink *device_veth = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old_veth = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new_veth = NULL;
+	NMPCacheOpsType cache_op;
+	NMPCacheOpsType cache_op_veth = NMP_CACHE_OPS_UNCHANGED;
+
+	device = link_add_pre (platform, name, type, address, address_len);
+
+	if (veth_peer) {
+		g_assert (type == NM_LINK_TYPE_VETH);
+		device_veth = link_add_pre (platform, veth_peer, type, NULL, 0);
+	} else
+		g_assert (type != NM_LINK_TYPE_VETH);
+
+	link_add_prepare (platform, device, (NMPObject *) device->obj);
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     (NMPObject *) device->obj,
+	                                     FALSE,
+	                                     &obj_old, &obj_new);
+	g_assert (cache_op == NMP_CACHE_OPS_ADDED);
+	nmp_object_unref (device->obj);
+	device->obj = nmp_object_ref (obj_new);
+	if (veth_peer) {
+		link_add_prepare (platform, device_veth, (NMPObject *) device_veth->obj);
+		cache_op_veth = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+		                                          (NMPObject *) device_veth->obj,
+		                                          FALSE,
+		                                          &obj_old_veth, &obj_new_veth);
+		g_assert (cache_op == NMP_CACHE_OPS_ADDED);
+		nmp_object_unref (device->obj);
+		device->obj = nmp_object_ref (obj_new);
+	}
 
-	link_init (&device, priv->links->len, type, name);
+	if (out_link)
+		*out_link = NMP_OBJECT_CAST_LINK (device->obj);
 
-	if (address) {
-		g_return_val_if_fail (address_len > 0 && address_len <= sizeof (device.link.addr.data), FALSE);
-		memcpy (device.link.addr.data, address, address_len);
-		device.link.addr.len = address_len;
-	}
+	link_changed (platform, device, cache_op, NULL);
+	if (veth_peer)
+		link_changed (platform, device_veth, cache_op_veth, NULL);
+
+	return TRUE;
+}
+
+static NMFakePlatformLink *
+link_add_one (NMPlatform *platform,
+              const char *name,
+              NMLinkType link_type,
+              void (*prepare_fcn) (NMPlatform *platform, NMFakePlatformLink *device, gconstpointer user_data),
+              gconstpointer user_data,
+              const NMPlatformLink **out_link)
+{
+	NMFakePlatformLink *device;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	NMPCacheOpsType cache_op;
+	int ifindex;
 
-	g_array_append_val (priv->links, device);
-	new_device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1);
+	device = link_add_pre (platform, name, NM_LINK_TYPE_VLAN, NULL, 0);
 
-	if (device.link.ifindex) {
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, device.link.ifindex, &device, (int) NM_PLATFORM_SIGNAL_ADDED);
+	ifindex = NMP_OBJECT_CAST_LINK (device->obj)->ifindex;
 
-		link_changed (platform, &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1), FALSE);
-	}
+	if (prepare_fcn)
+		prepare_fcn (platform, device, user_data);
 
-	if (out_link)
-		*out_link = &new_device->link;
-	return TRUE;
+	link_add_prepare (platform, device, (NMPObject *) device->obj);
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     (NMPObject *) device->obj,
+	                                     FALSE,
+	                                     &obj_old, &obj_new);
+	g_assert (cache_op == NMP_CACHE_OPS_ADDED);
+	nmp_object_unref (device->obj);
+	device->obj = nmp_object_ref (obj_new);
+
+	link_changed (platform, device, cache_op, obj_old);
+
+	device = link_get (platform, ifindex);
+	if (!device)
+		g_assert_not_reached ();
+
+	NM_SET_OUT (out_link, NMP_OBJECT_CAST_LINK (device->obj));
+	return device;
 }
 
 static gboolean
 link_delete (NMPlatform *platform, int ifindex)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMFakePlatformLink *device = link_get (platform, ifindex);
-	NMPlatformLink deleted_device;
-	int i;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old2 = NULL;
+	NMPCacheOpsType cache_op;
 
-	if (!device || !device->link.ifindex)
+	if (!device)
 		return FALSE;
 
-	memcpy (&deleted_device, &device->link, sizeof (deleted_device));
-	memset (&device->link, 0, sizeof (device->link));
-	g_clear_pointer (&device->lnk, nmp_object_unref);
+	obj_old = g_steal_pointer (&device->obj);
 	g_clear_pointer (&device->udi, g_free);
 
+	cache_op = nmp_cache_remove (nm_platform_get_cache (platform),
+	                             obj_old,
+	                             FALSE,
+	                             FALSE,
+	                             &obj_old2);
+	g_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+	g_assert (obj_old2);
+	g_assert (obj_old == obj_old2);
+
 	/* Remove addresses and routes which belong to the deleted interface */
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+	ipx_address_delete (platform, AF_INET, ifindex, NULL, NULL, NULL);
+	ipx_address_delete (platform, AF_INET6, ifindex, NULL, NULL, NULL);
+	ipx_route_delete (platform, AF_INET, ifindex, NULL);
+	ipx_route_delete (platform, AF_INET6, ifindex, NULL);
+
+	nm_platform_cache_update_emit_signal (platform,
+	                                      cache_op,
+	                                      obj_old2,
+	                                      NULL);
+	return TRUE;
+}
 
-		if (address->ifindex == ifindex)
-			memset (address, 0, sizeof (*address));
-	}
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+static void
+link_set_obj (NMPlatform *platform,
+              NMFakePlatformLink *device,
+              NMPObject *obj_tmp)
+{
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj NMPObject *obj_tmp_tmp = NULL;
+	NMPCacheOpsType cache_op;
 
-		if (address->ifindex == ifindex)
-			memset (address, 0, sizeof (*address));
-	}
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+	g_assert (device);
+	g_assert (NMP_OBJECT_GET_TYPE (device->obj) == NMP_OBJECT_TYPE_LINK);
 
-		if (route->ifindex == ifindex)
-			memset (route, 0, sizeof (*route));
+	if (!obj_tmp) {
+		obj_tmp_tmp = nmp_object_clone (device->obj, FALSE);
+		obj_tmp = obj_tmp_tmp;
 	}
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
 
-		if (route->ifindex == ifindex)
-			memset (route, 0, sizeof (*route));
-	}
+	g_assert (NMP_OBJECT_GET_TYPE (obj_tmp) == NMP_OBJECT_TYPE_LINK);
 
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, ifindex, &deleted_device, (int) NM_PLATFORM_SIGNAL_REMOVED);
+	link_add_prepare (platform, device, obj_tmp);
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     obj_tmp,
+	                                     FALSE,
+	                                     &obj_old, &obj_new);
+	g_assert (NM_IN_SET (cache_op, NMP_CACHE_OPS_UNCHANGED,
+	                               NMP_CACHE_OPS_UPDATED));
+	g_assert (obj_old == device->obj);
+	g_assert (obj_new);
 
-	return TRUE;
-}
+	nmp_object_unref (device->obj);
+	device->obj = nmp_object_ref (obj_new);
 
-static const char *
-link_get_type_name (NMPlatform *platform, int ifindex)
-{
-	return type_to_type_name (nm_platform_link_get_type (platform, ifindex));
+	link_changed (platform, device, cache_op, obj_old);
 }
 
 static void
-link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal)
+link_set_flags (NMPlatform *platform,
+                NMFakePlatformLink *device,
+                guint n_ifi_flags)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-	if (raise_signal)
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, device->link.ifindex, &device->link, (int) NM_PLATFORM_SIGNAL_CHANGED);
+	g_assert (device);
+	g_assert (NMP_OBJECT_GET_TYPE (device->obj) == NMP_OBJECT_TYPE_LINK);
 
-	if (device->link.ifindex && !IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) {
-		if (device->link.connected)
-			ip6_address_add (platform, device->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
-		else
-			ip6_address_delete (platform, device->link.ifindex, device->ip6_lladdr, 64);
-	}
+	obj_tmp = nmp_object_clone (device->obj, FALSE);
+	obj_tmp->link.n_ifi_flags = n_ifi_flags;
+	link_set_obj (platform, device, obj_tmp);
+}
 
-	if (device->link.master) {
-		gboolean connected = FALSE;
+static void
+link_changed (NMPlatform *platform,
+              NMFakePlatformLink *device,
+              NMPCacheOpsType cache_op,
+              const NMPObject *obj_old)
+{
+	g_assert (device->obj);
 
-		NMFakePlatformLink *master = link_get (platform, device->link.master);
+	g_assert (!nmp_cache_link_connected_needs_toggle (nm_platform_get_cache (platform),
+	                                                  device->obj, NULL, NULL));
 
-		g_return_if_fail (master && master != device);
+	nm_platform_cache_update_emit_signal (platform,
+	                                      cache_op,
+	                                      obj_old,
+	                                      device->obj);
 
-		for (i = 0; i < priv->links->len; i++) {
-			NMFakePlatformLink *slave = &g_array_index (priv->links, NMFakePlatformLink, i);
+	if (!IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) {
+		if (device->obj->link.connected)
+			ip6_address_add (platform, device->obj->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
+		else
+			ip6_address_delete (platform, device->obj->link.ifindex, device->ip6_lladdr, 64);
+	}
 
-			if (slave && slave->link.master == master->link.ifindex && slave->link.connected)
-				connected = TRUE;
-		}
+	if (device->obj->link.master) {
+		NMFakePlatformLink *master;
 
-		if (master->link.connected != connected) {
-			master->link.connected = connected;
-			link_changed (platform, master, TRUE);
-		}
+		master = link_get (platform, device->obj->link.master);
+		link_set_obj (platform, master, NULL);
 	}
 }
 
@@ -447,7 +499,6 @@ static gboolean
 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;
@@ -457,29 +508,9 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 		return FALSE;
 	}
 
-	up = TRUE;
-	connected = TRUE;
-	switch (device->link.type) {
-	case NM_LINK_TYPE_DUMMY:
-	case NM_LINK_TYPE_VLAN:
-		break;
-	case NM_LINK_TYPE_BRIDGE:
-	case NM_LINK_TYPE_BOND:
-	case NM_LINK_TYPE_TEAM:
-		connected = FALSE;
-		break;
-	default:
-		connected = FALSE;
-		g_error ("Unexpected device type: %d", device->link.type);
-	}
-
-	if (   NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) != !!up
-	    || device->link.connected != connected) {
-		device->link.n_ifi_flags = NM_FLAGS_ASSIGN (device->link.n_ifi_flags, IFF_UP, up);
-		device->link.connected = connected;
-		link_changed (platform, device, TRUE);
-	}
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_ASSIGN (device->obj->link.n_ifi_flags, IFF_UP, TRUE));
 	return TRUE;
 }
 
@@ -493,13 +524,9 @@ link_set_down (NMPlatform *platform, int ifindex)
 		return FALSE;
 	}
 
-	if (NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) || device->link.connected) {
-		device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_UP);
-		device->link.connected = FALSE;
-
-		link_changed (platform, device, TRUE);
-	}
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_UNSET (device->obj->link.n_ifi_flags, IFF_UP));
 	return TRUE;
 }
 
@@ -513,10 +540,9 @@ link_set_arp (NMPlatform *platform, int ifindex)
 		return FALSE;
 	}
 
-	device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP);
-
-	link_changed (platform, device, TRUE);
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_UNSET (device->obj->link.n_ifi_flags, IFF_NOARP));
 	return TRUE;
 }
 
@@ -530,10 +556,9 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 		return FALSE;
 	}
 
-	device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP);
-
-	link_changed (platform, device, TRUE);
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_SET (device->obj->link.n_ifi_flags, IFF_NOARP));
 	return TRUE;
 }
 
@@ -541,36 +566,40 @@ static NMPlatformError
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-	if (   !device
-	    || len == 0
+	if (   len == 0
 	    || len > NM_UTILS_HWADDR_LEN_MAX
 	    || !addr)
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
-	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);
-	}
+	if (!device)
+		return NM_PLATFORM_ERROR_EXISTS;
 
+	obj_tmp = nmp_object_clone (device->obj, FALSE);
+	obj_tmp->link.addr.len = len;
+	memset (obj_tmp->link.addr.data, 0, sizeof (obj_tmp->link.addr.data));
+	memcpy (obj_tmp->link.addr.data, addr, len);
+
+	link_set_obj (platform, device, obj_tmp);
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gboolean
+static NMPlatformError
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-	if (device) {
-		device->link.mtu = mtu;
-		link_changed (platform, device, TRUE);
-	} else
+	if (!device) {
 		_LOGE ("failure changing link: netlink error (No such device)");
+		return NM_PLATFORM_ERROR_EXISTS;
+	}
 
-	return !!device;
+	obj_tmp = nmp_object_clone (device->obj, FALSE);
+	obj_tmp->link.mtu = mtu;
+	link_set_obj (platform, device, obj_tmp);
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -614,7 +643,7 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	switch (device->link.type) {
+	switch (device->obj->link.type) {
 	case NM_LINK_TYPE_DUMMY:
 		return FALSE;
 	default:
@@ -630,7 +659,7 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	switch (device->link.type) {
+	switch (device->obj->link.type) {
 	case NM_LINK_TYPE_LOOPBACK:
 		return FALSE;
 	default:
@@ -646,7 +675,7 @@ link_supports_sriov (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	switch (device->link.type) {
+	switch (device->obj->link.type) {
 	case NM_LINK_TYPE_LOOPBACK:
 		return FALSE;
 	default:
@@ -663,15 +692,14 @@ link_enslave (NMPlatform *platform, int master, int slave)
 	g_return_val_if_fail (device, FALSE);
 	g_return_val_if_fail (master_device, FALSE);
 
-	if (device->link.master != master) {
-		device->link.master = master;
-
-		if (NM_IN_SET (master_device->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) {
-			device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_UP);
-			device->link.connected = TRUE;
-		}
+	if (device->obj->link.master != master) {
+		nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-		link_changed (platform, device, TRUE);
+		obj_tmp = nmp_object_clone (device->obj, FALSE);
+		obj_tmp->link.master = master;
+		if (NM_IN_SET (master_device->obj->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM))
+			obj_tmp->link.n_ifi_flags = NM_FLAGS_SET (device->obj->link.n_ifi_flags, IFF_UP);
+		link_set_obj (platform, device, obj_tmp);
 	}
 
 	return TRUE;
@@ -682,40 +710,56 @@ link_release (NMPlatform *platform, int master_idx, int slave_idx)
 {
 	NMFakePlatformLink *master = link_get (platform, master_idx);
 	NMFakePlatformLink *slave = link_get (platform, slave_idx);
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
 	g_return_val_if_fail (master, FALSE);
 	g_return_val_if_fail (slave, FALSE);
 
-	if (slave->link.master != master->link.ifindex)
+	if (slave->obj->link.master != master->obj->link.ifindex)
 		return FALSE;
 
-	slave->link.master = 0;
-
-	link_changed (platform, slave, TRUE);
-	link_changed (platform, master, TRUE);
-
+	obj_tmp = nmp_object_clone (slave->obj, FALSE);
+	obj_tmp->link.master = 0;
+	link_set_obj (platform, slave, obj_tmp);
 	return TRUE;
 }
 
-static gboolean
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
+struct vlan_add_data {
+	guint32 vlan_flags;
+	int parent;
+	int vlan_id;
+};
+
+static void
+_vlan_add_prepare (NMPlatform *platform,
+                   NMFakePlatformLink *device,
+                   gconstpointer user_data)
 {
-	NMFakePlatformLink *device;
+	const struct vlan_add_data *d = user_data;
+	NMPObject *obj_tmp;
+	NMPObject *lnk;
 
-	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, out_link))
-		return FALSE;
+	obj_tmp = (NMPObject *) device->obj;
 
-	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
+	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
+	lnk->lnk_vlan.id = d->vlan_id;
+	lnk->lnk_vlan.flags = d->vlan_flags;
 
-	g_return_val_if_fail (device, FALSE);
-	g_return_val_if_fail (!device->lnk, FALSE);
+	obj_tmp->link.parent = d->parent;
+	obj_tmp->_link.netlink.lnk = lnk;
+}
 
-	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-	device->lnk->lnk_vlan.id = vlan_id;
-	device->link.parent = parent;
+static gboolean
+vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
+{
+	const struct vlan_add_data d = {
+		.parent = parent,
+		.vlan_id = vlan_id,
+		.vlan_flags = vlan_flags,
+	};
 
-	if (out_link)
-		*out_link = &device->link;
+	link_add_one (platform, name, NM_LINK_TYPE_VLAN,
+	              _vlan_add_prepare, &d, out_link);
 	return TRUE;
 }
 
@@ -734,53 +778,76 @@ link_vlan_change (NMPlatform *platform,
 	return FALSE;
 }
 
+static void
+_vxlan_add_prepare (NMPlatform *platform,
+                    NMFakePlatformLink *device,
+                    gconstpointer user_data)
+{
+	const NMPlatformLnkVxlan *props = user_data;
+	NMPObject *obj_tmp;
+	NMPObject *lnk;
+
+	obj_tmp = (NMPObject *) device->obj;
+
+	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
+	lnk->lnk_vxlan = *props;
+
+	obj_tmp->link.parent = props->parent_ifindex;
+	obj_tmp->_link.netlink.lnk = lnk;
+}
+
 static gboolean
 link_vxlan_add (NMPlatform *platform,
                 const char *name,
                 const NMPlatformLnkVxlan *props,
                 const NMPlatformLink **out_link)
 {
-	NMFakePlatformLink *device;
+	link_add_one (platform, name, NM_LINK_TYPE_VXLAN,
+	              _vxlan_add_prepare, props, out_link);
+	return TRUE;
+}
 
-	if (!link_add (platform, name, NM_LINK_TYPE_VXLAN, NULL, 0, out_link))
-		return FALSE;
+struct infiniband_add_data {
+	int parent;
+	int p_key;
+};
 
-	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
+static void
+_infiniband_add_prepare (NMPlatform *platform,
+                         NMFakePlatformLink *device,
+                         gconstpointer user_data)
+{
+	const struct infiniband_add_data *d = user_data;
+	NMPObject *obj_tmp;
+	NMPObject *lnk;
 
-	g_return_val_if_fail (device, FALSE);
-	g_return_val_if_fail (!device->lnk, FALSE);
+	obj_tmp = (NMPObject *) device->obj;
 
-	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
-	device->lnk->lnk_vxlan = *props;
-	device->link.parent = props->parent_ifindex;
+	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL);
+	lnk->lnk_infiniband.p_key = d->p_key;
+	lnk->lnk_infiniband.mode = "datagram";
 
-	if (out_link)
-		*out_link = &device->link;
-	return TRUE;
+	obj_tmp->link.parent = d->parent;
+	obj_tmp->_link.netlink.lnk = lnk;
 }
 
 static gboolean
 infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
-	NMFakePlatformLink *device, *parent_device;
+	NMFakePlatformLink *parent_device;
 	char name[IFNAMSIZ];
+	const struct infiniband_add_data d = {
+		.parent = parent,
+		.p_key = p_key,
+	};
 
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
-	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
-
-	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);
-	g_return_val_if_fail (!device->lnk, FALSE);
+	nm_utils_new_infiniband_name (name, parent_device->obj->link.name, p_key);
 
-	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-	device->lnk->lnk_infiniband.p_key = p_key;
-	device->lnk->lnk_infiniband.mode = "datagram";
-	device->link.parent = parent;
+	link_add_one (platform, name, NM_LINK_TYPE_INFINIBAND,
+	              _infiniband_add_prepare, &d, out_link);
 	return TRUE;
 }
 
@@ -793,7 +860,7 @@ infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
-	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
+	nm_utils_new_infiniband_name (name, parent_device->obj->link.name, p_key);
 	return link_delete (platform, nm_platform_link_get_ifindex (platform, name));
 }
 
@@ -804,7 +871,7 @@ wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabiliti
 
 	g_return_val_if_fail (device, FALSE);
 
-	if (device->link.type != NM_LINK_TYPE_WIFI)
+	if (device->obj->link.type != NM_LINK_TYPE_WIFI)
 		return FALSE;
 
 	if (caps) {
@@ -894,59 +961,25 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 
 /*****************************************************************************/
 
-static GArray *
-ip4_address_get_all (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *addresses;
-	NMPlatformIP4Address *address;
-	int count = 0, i;
-
-	/* Count addresses */
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-		if (address && address->ifindex == ifindex)
-			count++;
-	}
-
-	addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP4Address), count);
-
-	/* Fill addresses */
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-		if (address && address->ifindex == ifindex)
-			g_array_append_val (addresses, *address);
-	}
-
-	return addresses;
-}
-
-static GArray *
-ip6_address_get_all (NMPlatform *platform, int ifindex)
+static gboolean
+ipx_address_add (NMPlatform *platform, int addr_family, const NMPlatformObject *address)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *addresses;
-	NMPlatformIP6Address *address;
-	int count = 0, i;
-
-	/* Count addresses */
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-		if (address && address->ifindex == ifindex)
-			count++;
-	}
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPCacheOpsType cache_op;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	NMPCache *cache = nm_platform_get_cache (platform);
 
-	addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP6Address), count);
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	/* Fill addresses */
-	count = 0;
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-		if (address && address->ifindex == ifindex)
-			g_array_append_val (addresses, *address);
-	}
+	obj = nmp_object_new (addr_family == AF_INET
+	                        ? NMP_OBJECT_TYPE_IP4_ADDRESS
+	                        : NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                      address);
 
-	return addresses;
+	cache_op = nmp_cache_update_netlink (cache, obj, FALSE, &obj_old, &obj_new);
+	nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+	return TRUE;
 }
 
 static gboolean
@@ -960,9 +993,7 @@ ip4_address_add (NMPlatform *platform,
                  guint32 flags,
                  const char *label)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMPlatformIP4Address address;
-	int i;
 
 	memset (&address, 0, sizeof (address));
 	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
@@ -977,28 +1008,7 @@ ip4_address_add (NMPlatform *platform,
 	if (label)
 		g_strlcpy (address.label, label, sizeof (address.label));
 
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *item = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-		gboolean changed;
-
-		if (   item->ifindex != address.ifindex
-		    || item->address != address.address
-		    || item->plen != address.plen
-		    || !_ip4_address_equal_peer_net (item->peer_address, address.peer_address, address.plen))
-			continue;
-
-		changed = !nm_platform_ip4_address_cmp (item, &address);
-
-		memcpy (item, &address, sizeof (address));
-		if (changed)
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
-	}
-
-	g_array_append_val (priv->ip4_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_ADDED);
-
-	return TRUE;
+	return ipx_address_add (platform, AF_INET, (const NMPlatformObject *) &address);
 }
 
 static gboolean
@@ -1011,9 +1021,7 @@ ip6_address_add (NMPlatform *platform,
                  guint32 preferred,
                  guint32 flags)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMPlatformIP6Address address;
-	int i;
 
 	memset (&address, 0, sizeof (address));
 	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
@@ -1026,380 +1034,321 @@ ip6_address_add (NMPlatform *platform,
 	address.preferred = preferred;
 	address.n_ifa_flags = flags;
 
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *item = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-		gboolean changed;
-
-		if (   item->ifindex != address.ifindex
-		    || !IN6_ARE_ADDR_EQUAL (&item->address, &address.address))
-			continue;
+	return ipx_address_add (platform, AF_INET6, (const NMPlatformObject *) &address);
+}
 
-		changed = !nm_platform_ip6_address_cmp (item, &address);
+static gboolean
+ipx_address_delete (NMPlatform *platform,
+                    int addr_family,
+                    int ifindex,
+                    gconstpointer addr,
+                    const guint8 *plen,
+                    gconstpointer peer_addr)
+{
+	gs_unref_ptrarray GPtrArray *objs = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	NMDedupMultiIter iter;
+	const NMPObject *o = NULL;
+	guint i;
+	guint32 peer_addr_i;
+
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	peer_addr_i = peer_addr ? *((guint32 *) peer_addr) : 0;
+
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup_addrroute (platform,
+	                                                       addr_family == AF_INET
+	                                                         ? NMP_OBJECT_TYPE_IP4_ADDRESS
+	                                                         : NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                                       0),
+	                         &o) {
+		const NMPObject *obj_old = NULL;
+
+		if (addr_family == AF_INET) {
+			const NMPlatformIP4Address *address = NMP_OBJECT_CAST_IP4_ADDRESS (o);
+
+			if (   address->ifindex != ifindex
+			    || (addr && address->address != *((guint32 *) addr))
+			    || (plen && address->plen != *plen)
+			    || (   peer_addr
+			        && (((peer_addr_i ^ address->peer_address) & _nm_utils_ip4_prefix_to_netmask (address->plen)) != 0)))
+				continue;
+		} else {
+			const NMPlatformIP6Address *address = NMP_OBJECT_CAST_IP6_ADDRESS (o);
+
+			g_assert (!peer_addr);
+			if (   address->ifindex != ifindex
+			    || (addr && !IN6_ARE_ADDR_EQUAL (&address->address, addr))
+			    || (plen && address->plen != *plen))
+				continue;
+		}
 
-		memcpy (item, &address, sizeof (address));
-		if (changed)
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
+		if (nmp_cache_remove (nm_platform_get_cache (platform),
+		                      o,
+		                      TRUE,
+		                      FALSE,
+		                      &obj_old) != NMP_CACHE_OPS_REMOVED)
+			g_assert_not_reached ();
+		g_assert (obj_old);
+		g_ptr_array_add (objs, (gpointer) obj_old);
 	}
 
-	g_array_append_val (priv->ip6_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_ADDED);
-
+	for (i = 0; i < objs->len; i++) {
+		nm_platform_cache_update_emit_signal (platform,
+		                                      NMP_CACHE_OPS_REMOVED,
+		                                      objs->pdata[i],
+		                                      NULL);
+	}
 	return TRUE;
 }
 
 static gboolean
 ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && address->address == addr
-		    && ((peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (plen)) == 0) {
-			NMPlatformIP4Address deleted_address;
-
-			memcpy (&deleted_address, address, sizeof (deleted_address));
-			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &deleted_address, (int) NM_PLATFORM_SIGNAL_REMOVED);
-			return TRUE;
-		}
-	}
-
-	return TRUE;
+	return ipx_address_delete (platform, AF_INET, ifindex, &addr, &plen, &peer_address);
 }
 
 static gboolean
 ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && IN6_ARE_ADDR_EQUAL (&address->address, &addr)) {
-			NMPlatformIP6Address deleted_address;
-
-			memcpy (&deleted_address, address, sizeof (deleted_address));
-			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &deleted_address, (int) NM_PLATFORM_SIGNAL_REMOVED);
-			return TRUE;
-		}
-	}
-
-	return TRUE;
-}
-
-static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && address->address == addr
-		    && _ip4_address_equal_peer_net (address->peer_address, peer_address, plen))
-			return address;
-	}
-
-	return NULL;
-}
-
-static const NMPlatformIP6Address *
-ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && IN6_ARE_ADDR_EQUAL (&address->address, &addr))
-			return address;
-	}
-
-	return NULL;
+	return ipx_address_delete (platform, AF_INET6, ifindex, &addr, &plen, NULL);
 }
 
 /*****************************************************************************/
 
-static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
+static gboolean
+ipx_route_delete (NMPlatform *platform,
+                  int addr_family,
+                  int ifindex,
+                  const NMPObject *obj)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *routes;
-	NMPlatformIP4Route *route;
+	gs_unref_ptrarray GPtrArray *objs = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	NMDedupMultiIter iter;
+	const NMPObject *o = NULL;
 	guint i;
-
-	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 (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
-					g_array_append_val (routes, *route);
-			} else {
-				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-					g_array_append_val (routes, *route);
-			}
-		}
+	NMPObjectType obj_type;
+
+	if (addr_family == AF_UNSPEC) {
+		g_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+		                                                NMP_OBJECT_TYPE_IP6_ROUTE));
+		g_assert (ifindex == -1);
+		ifindex = obj->object.ifindex;
+		obj_type = NMP_OBJECT_GET_TYPE (obj);
+	} else {
+		g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+		g_assert (!obj);
+		g_assert (ifindex > 0);
+		obj_type =   addr_family == AF_INET
+	               ? NMP_OBJECT_TYPE_IP4_ROUTE
+	               : NMP_OBJECT_TYPE_IP6_ROUTE;
 	}
 
-	return routes;
-}
-
-static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *routes;
-	NMPlatformIP6Route *route;
-	guint i;
-
-	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 (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
-					g_array_append_val (routes, *route);
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup_addrroute (platform,
+	                                                       obj_type,
+	                                                       ifindex),
+	                         &o) {
+		const NMPObject *obj_old = NULL;
+
+		if (obj) {
+			if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) {
+				const NMPlatformIP4Route *route = NMP_OBJECT_CAST_IP4_ROUTE (o);
+				const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (obj);
+
+				if (   route->network != r->network
+				    || route->plen != r->plen
+				    || route->metric != r->metric)
+					continue;
 			} else {
-				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-					g_array_append_val (routes, *route);
+				const NMPlatformIP6Route *route = NMP_OBJECT_CAST_IP6_ROUTE (o);
+				const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (obj);
+
+				if (   !IN6_ARE_ADDR_EQUAL (&route->network, &r->network)
+				    || route->plen != r->plen
+				    || route->metric != r->metric)
+					continue;
 			}
 		}
-	}
-
-	return routes;
-}
 
-static gboolean
-ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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, (int) NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &deleted_route, (int) NM_PLATFORM_SIGNAL_REMOVED);
+		if (nmp_cache_remove (nm_platform_get_cache (platform),
+		                      o,
+		                      TRUE,
+		                      FALSE,
+		                      &obj_old) != NMP_CACHE_OPS_REMOVED)
+			g_assert_not_reached ();
+		g_assert (obj_old);
+		g_ptr_array_add (objs, (gpointer) obj_old);
 	}
 
+	for (i = 0; i < objs->len; i++) {
+		nm_platform_cache_update_emit_signal (platform,
+		                                      NMP_CACHE_OPS_REMOVED,
+		                                      objs->pdata[i],
+		                                      NULL);
+	}
 	return TRUE;
 }
 
 static gboolean
-ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
+ip_route_delete (NMPlatform *platform, const NMPObject *obj)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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, (int) NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &deleted_route, (int) NM_PLATFORM_SIGNAL_REMOVED);
-	}
+	g_assert (NM_IS_FAKE_PLATFORM (platform));
+	g_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	return TRUE;
+	return ipx_route_delete (platform, AF_UNSPEC, -1, obj);
 }
 
-static gboolean
-ip4_route_add (NMPlatform *platform, const NMPlatformIP4Route *route)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	NMPlatformIP4Route rt = *route;
-	guint i;
-
-	rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-	rt.network = nm_utils_ip4_address_clear_host_address (rt.network, rt.plen);
-	rt.scope_inv = nm_platform_route_scope_inv (rt.gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK);
+static NMPlatformError
+ip_route_add (NMPlatform *platform,
+              NMPNlmFlags flags,
+              int addr_family,
+              const NMPlatformIPRoute *route)
+{
+	NMDedupMultiIter iter;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPCacheOpsType cache_op;
+	const NMPObject *o = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_replace = NULL;
+	NMPCache *cache = nm_platform_get_cache (platform);
+	gboolean has_gateway = FALSE;
+	NMPlatformIPRoute *r = NULL;
+	NMPlatformIP4Route *r4 = NULL;
+	NMPlatformIP6Route *r6 = NULL;
+	gboolean has_same_weak_id;
+	gboolean only_dirty;
+	guint16 nlmsgflags;
+
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	flags = NM_FLAGS_UNSET (flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE);
+
+	/* currently, only replace is implemented. */
+	g_assert (flags == NMP_NLM_FLAG_REPLACE);
+
+	obj = nmp_object_new (addr_family == AF_INET
+	                        ? NMP_OBJECT_TYPE_IP4_ROUTE
+	                        : NMP_OBJECT_TYPE_IP6_ROUTE,
+	                      (const NMPlatformObject *) route);
+	r = NMP_OBJECT_CAST_IP_ROUTE (obj);
+	nm_platform_ip_route_normalize (addr_family, r);
+
+	switch (addr_family) {
+	case AF_INET:
+		r4 = NMP_OBJECT_CAST_IP4_ROUTE (obj);
+		if (r4->gateway)
+			has_gateway = TRUE;
+		break;
+	case AF_INET6:
+		r6 = NMP_OBJECT_CAST_IP6_ROUTE (obj);
+		if (!IN6_IS_ADDR_UNSPECIFIED (&r6->gateway))
+			has_gateway = TRUE;
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
 
-	if (rt.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 (rt.gateway) >> (32 - item->plen);
+	if (has_gateway) {
+		gboolean has_route_to_gw = FALSE;
+
+		nmp_cache_iter_for_each (&iter,
+		                         nm_platform_lookup_addrroute (platform,
+		                                                       NMP_OBJECT_GET_TYPE (obj),
+		                                                       0),
+		                         &o) {
+			if (addr_family == AF_INET) {
+				const NMPlatformIP4Route *item = NMP_OBJECT_CAST_IP4_ROUTE (o);
+				guint32 n = nm_utils_ip4_address_clear_host_address (item->network, item->plen);
+				guint32 g = nm_utils_ip4_address_clear_host_address (r4->gateway, item->plen);
+
+				if (   r->ifindex == item->ifindex
+				    && n == g) {
+					has_route_to_gw = TRUE;
+					break;
+				}
+			} else {
+				const NMPlatformIP6Route *item = NMP_OBJECT_CAST_IP6_ROUTE (o);
 
-			if (rt.ifindex == item->ifindex && gate == host)
-				break;
+				if (   r->ifindex == item->ifindex
+				    && nm_utils_ip6_address_same_prefix (&r6->gateway, &item->network, item->plen)) {
+					has_route_to_gw = TRUE;
+					break;
+				}
+			}
 		}
-		if (i == priv->ip4_routes->len) {
-			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
-			             rt.ifindex, nm_utils_inet4_ntop (rt.network, NULL), rt.plen, rt.metric);
-			return FALSE;
+		if (!has_route_to_gw) {
+			if (addr_family == AF_INET) {
+				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
+				             r->ifindex, nm_utils_inet4_ntop (r4->network, NULL), r->plen, r->metric);
+			} else {
+				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
+				             r->ifindex, nm_utils_inet6_ntop (&r6->network, NULL), r->plen, r->metric);
+			}
+			return NM_PLATFORM_ERROR_UNSPECIFIED;
 		}
 	}
 
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
-
-		if (item->network != rt.network)
-			continue;
-		if (item->plen != rt.plen)
-			continue;
-		if (item->metric != rt.metric)
-			continue;
-
-		if (item->ifindex != rt.ifindex) {
-			ip4_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
-			i--;
-			continue;
+	has_same_weak_id = FALSE;
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup_all (platform,
+	                                                 NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
+	                                                 obj),
+	                         &o) {
+		if (addr_family == AF_INET) {
+			if (nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (o), r4, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0)
+				continue;
+		} else {
+			if (nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (o), r6, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0)
+				continue;
 		}
-
-		memcpy (item, &rt, sizeof (rt));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE,
-		                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
+		has_same_weak_id = TRUE;
 	}
 
-	g_array_append_val (priv->ip4_routes, rt);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE,
-	                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_ADDED);
-
-	return TRUE;
-}
-
-static gboolean
-ip6_route_add (NMPlatform *platform, const NMPlatformIP6Route *route)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	NMPlatformIP6Route rt = *route;
-	guint i;
-
-	rt.metric = nm_utils_ip6_route_metric_normalize (rt.metric);
-	rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-	nm_utils_ip6_address_clear_host_address (&rt.network, &rt.network, rt.plen);
-
-	if (!IN6_IS_ADDR_UNSPECIFIED (&rt.gateway)) {
-		for (i = 0; i < priv->ip6_routes->len; i++) {
-			NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes,
-			                                           NMPlatformIP6Route, i);
-			guint8 gate_bits = rt.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 (   rt.ifindex == item->ifindex
-			    && memcmp (&rt.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",
-			             rt.ifindex, nm_utils_inet6_ntop (&rt.network, NULL), rt.plen, rt.metric);
-			return FALSE;
+	nlmsgflags = 0;
+	if (has_same_weak_id) {
+		switch (flags) {
+		case NMP_NLM_FLAG_REPLACE:
+			nlmsgflags = NLM_F_REPLACE;
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
 		}
 	}
 
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
-
-		if (!IN6_ARE_ADDR_EQUAL (&item->network, &rt.network))
-			continue;
-		if (item->plen != rt.plen)
-			continue;
-		if (item->metric != rt.metric)
-			continue;
-
-		if (item->ifindex != rt.ifindex) {
-			ip6_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
-			i--;
-			continue;
+	/* we manipulate the cache the same was as NMLinuxPlatform does it. */
+	cache_op = nmp_cache_update_netlink_route (cache,
+	                                           obj,
+	                                           FALSE,
+	                                           nlmsgflags,
+	                                           &obj_old,
+	                                           &obj_new,
+	                                           &obj_replace,
+	                                           NULL);
+	only_dirty = FALSE;
+	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+		if (obj_replace) {
+			const NMDedupMultiEntry *entry_replace;
+
+			entry_replace = nmp_cache_lookup_entry (cache, obj_replace);
+			nm_assert (entry_replace && entry_replace->obj == obj_replace);
+			nm_dedup_multi_entry_set_dirty (entry_replace, TRUE);
+			only_dirty = TRUE;
 		}
-
-		memcpy (item, &rt, sizeof (rt));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE,
-		                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
+		nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 	}
 
-	g_array_append_val (priv->ip6_routes, rt);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE,
-	                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_ADDED);
-
-	return TRUE;
-}
-
-static const NMPlatformIP4Route *
-ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
-
-		if (route->ifindex == ifindex
-				&& route->network == network
-				&& route->plen == plen
-				&& route->metric == metric)
-			return route;
-	}
-
-	return NULL;
-}
-
-static const NMPlatformIP6Route *
-ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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);
-
-		if (route->ifindex == ifindex
-				&& IN6_ARE_ADDR_EQUAL (&route->network, &network)
-				&& route->plen == plen
-				&& route->metric == metric)
-			return route;
+	if (obj_replace) {
+		cache_op = nmp_cache_remove (cache, obj_replace, TRUE, only_dirty, NULL);
+		if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+			nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+			nm_platform_cache_update_emit_signal (platform, cache_op, obj_replace, NULL);
+		}
 	}
 
-	return NULL;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 /*****************************************************************************/
@@ -1409,12 +1358,8 @@ nm_fake_platform_init (NMFakePlatform *fake_platform)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (fake_platform);
 
-	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	priv->options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 	priv->links = g_array_new (TRUE, TRUE, sizeof (NMFakePlatformLink));
-	priv->ip4_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Address));
-	priv->ip6_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Address));
-	priv->ip4_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route));
-	priv->ip6_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));
 }
 
 void
@@ -1428,16 +1373,13 @@ nm_fake_platform_setup (void)
 
 	nm_platform_setup (platform);
 
-	/* skip zero element */
-	link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0, NULL);
-
 	/* add loopback interface */
-	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0, NULL);
+	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, NULL, 0, NULL);
 
 	/* add some ethernets */
-	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);
+	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
+	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
+	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
 }
 
 static void
@@ -1451,13 +1393,9 @@ finalize (GObject *object)
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
 		g_free (device->udi);
-		g_clear_pointer (&device->lnk, nmp_object_unref);
+		g_clear_pointer (&device->obj, nmp_object_unref);
 	}
 	g_array_unref (priv->links);
-	g_array_unref (priv->ip4_addresses);
-	g_array_unref (priv->ip6_addresses);
-	g_array_unref (priv->ip4_routes);
-	g_array_unref (priv->ip6_routes);
 
 	G_OBJECT_CLASS (nm_fake_platform_parent_class)->finalize (object);
 }
@@ -1473,15 +1411,8 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	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_type_name = link_get_type_name;
-
-	platform_class->link_get_lnk = link_get_lnk;
 
 	platform_class->link_get_udi = link_get_udi;
 
@@ -1525,21 +1456,11 @@ 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_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->ip_route_add = ip_route_add;
+	platform_class->ip_route_delete = ip_route_delete;
 }