summary refs log tree commit diff
path: root/src/platform/tests
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/tests
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'src/platform/tests')
-rw-r--r--src/platform/tests/monitor.c2
-rw-r--r--src/platform/tests/test-address.c87
-rw-r--r--src/platform/tests/test-cleanup.c32
-rw-r--r--src/platform/tests/test-common.c445
-rw-r--r--src/platform/tests/test-common.h174
-rw-r--r--src/platform/tests/test-general.c2
-rw-r--r--src/platform/tests/test-link.c31
-rw-r--r--src/platform/tests/test-nmp-object.c494
-rw-r--r--src/platform/tests/test-route.c541
9 files changed, 1248 insertions, 560 deletions
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index d0c58aeb..e1220052 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -78,6 +78,8 @@ main (int argc, char **argv)
 
 	nm_linux_platform_setup ();
 
+	nm_platform_check_kernel_support (NM_PLATFORM_GET, ~((NMPlatformKernelSupportFlags) 0));
+
 	if (global_opt.persist)
 		g_main_loop_run (loop);
 
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index 4c139ef0..93851ff7 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -22,7 +22,6 @@
 
 #include "test-common.h"
 
-#define DEVICE_NAME "nm-test-device"
 #define IP4_ADDRESS "192.0.2.1"
 #define IP4_ADDRESS_PEER "192.0.2.2"
 #define IP4_ADDRESS_PEER2 "192.0.3.1"
@@ -30,8 +29,8 @@
 #define IP6_ADDRESS "2001:db8:a:b:1:2:3:4"
 #define IP6_PLEN 64
 
-static int DEVICE_IFINDEX = -1;
-static int EX = -1;
+#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX
+#define EX             NMTSTP_ENV1_EX
 
 /*****************************************************************************/
 
@@ -103,7 +102,7 @@ test_ip4_address_general (void)
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
-	addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 	g_assert (addresses);
 	g_assert_cmpint (addresses->len, ==, 1);
 	address = &g_array_index (addresses, NMPlatformIP4Address, 0);
@@ -143,9 +142,9 @@ test_ip6_address_general (void)
 	inet_pton (AF_INET6, IP6_ADDRESS, &addr);
 
 	/* Add address */
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
@@ -153,7 +152,7 @@ test_ip6_address_general (void)
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
-	addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
 	g_assert (addresses);
 	g_assert_cmpint (addresses->len, ==, 1);
 	address = &g_array_index (addresses, NMPlatformIP6Address, 0);
@@ -164,7 +163,7 @@ test_ip6_address_general (void)
 
 	/* Remove address */
 	nmtstp_ip6_address_del (NULL, EX, ifindex, addr, IP6_PLEN);
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 	accept_signal (address_removed);
 
 	/* Remove address again */
@@ -229,20 +228,20 @@ test_ip6_address_general_2 (void)
 	/* Add/delete notification */
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
 	accept_signal (address_added);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	nmtstp_ip6_address_del (NULL, EX, ifindex, addr, IP6_PLEN);
 	accept_signal (address_removed);
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	/* Add/delete conflict */
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
 	accept_signal (address_added);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
 	ensure_no_signal (address_added);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	free_signal (address_added);
 	free_signal (address_removed);
@@ -330,7 +329,7 @@ test_ip4_address_peer_zero (void)
 
 		nmtstp_ip4_address_add (NULL, EX, ifindex, addr, plen, r_peers[i], lifetime, preferred, 0, label);
 
-		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		addrs = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 		g_assert (addrs);
 		g_assert_cmpint (addrs->len, ==, i + 1);
 		g_array_unref (addrs);
@@ -345,7 +344,7 @@ test_ip4_address_peer_zero (void)
 
 		nmtstp_ip4_address_del (NULL, EX, ifindex, addr, plen, r_peers[i]);
 
-		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		addrs = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 		g_assert (addrs);
 		g_assert_cmpint (addrs->len, ==, G_N_ELEMENTS (peers) - i - 1);
 		g_array_unref (addrs);
@@ -366,62 +365,16 @@ _nmtstp_init_tests (int *argc, char ***argv)
  * SETUP TESTS
  *****************************************************************************/
 
-typedef struct {
-	const char *testpath;
-	GTestFunc test_func;
-} TestSetup;
-
-static void
-_g_test_run (gconstpointer user_data)
-{
-	const TestSetup *s = user_data;
-	int ifindex;
-
-	_LOGT ("TEST: start %s", s->testpath);
-
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
-
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
-	g_assert_cmpint (ifindex, >, 0);
-	g_assert_cmpint (DEVICE_IFINDEX, ==, -1);
-
-	DEVICE_IFINDEX = ifindex;
-	EX = nmtstp_run_command_check_external_global ();
-
-	s->test_func ();
-
-	g_assert_cmpint (DEVICE_IFINDEX, ==, ifindex);
-	DEVICE_IFINDEX = -1;
-
-	g_assert_cmpint (ifindex, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
-	_LOGT ("TEST: finished %s", s->testpath);
-}
-
-static void
-_g_test_add_func (const char *testpath,
-                  GTestFunc   test_func)
-{
-	TestSetup *s;
-
-	s = g_new0 (TestSetup, 1);
-	s->testpath = testpath;
-	s->test_func = test_func;
-
-	g_test_add_data_func_full (testpath, s, _g_test_run, g_free);
-}
-
 void
 _nmtstp_setup_tests (void)
 {
-	_g_test_add_func ("/address/ipv4/general", test_ip4_address_general);
-	_g_test_add_func ("/address/ipv6/general", test_ip6_address_general);
+#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, FALSE)
+	add_test_func ("/address/ipv4/general", test_ip4_address_general);
+	add_test_func ("/address/ipv6/general", test_ip6_address_general);
 
-	_g_test_add_func ("/address/ipv4/general-2", test_ip4_address_general_2);
-	_g_test_add_func ("/address/ipv6/general-2", test_ip6_address_general_2);
+	add_test_func ("/address/ipv4/general-2", test_ip4_address_general_2);
+	add_test_func ("/address/ipv6/general-2", test_ip6_address_general_2);
 
-	_g_test_add_func ("/address/ipv4/peer", test_ip4_address_peer);
-	_g_test_add_func ("/address/ipv4/peer/zero", test_ip4_address_peer_zero);
+	add_test_func ("/address/ipv4/peer", test_ip4_address_peer);
+	add_test_func ("/address/ipv4/peer/zero", test_ip4_address_peer_zero);
 }
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 71a92cbf..937cd12c 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -22,8 +22,6 @@
 
 #include "test-common.h"
 
-#define DEVICE_NAME "nm-test-device"
-
 static void
 test_cleanup_internal (void)
 {
@@ -31,8 +29,8 @@ test_cleanup_internal (void)
 	int ifindex;
 	GArray *addresses4;
 	GArray *addresses6;
-	GArray *routes4;
-	GArray *routes6;
+	GPtrArray *routes4;
+	GPtrArray *routes6;
 	in_addr_t addr4;
 	in_addr_t network4;
 	int plen4 = 24;
@@ -72,10 +70,10 @@ test_cleanup_internal (void)
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network6, plen6, gateway6, in6addr_any, metric, mss);
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway6, in6addr_any, 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);
+	addresses4 = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses6 = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	routes4 = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
+	routes6 = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, ifindex);
 
 	g_assert_cmpint (addresses4->len, ==, 1);
 	g_assert_cmpint (addresses6->len, ==, 2); /* also has a IPv6 LL address. */
@@ -84,26 +82,24 @@ test_cleanup_internal (void)
 
 	g_array_unref (addresses4);
 	g_array_unref (addresses6);
-	g_array_unref (routes4);
-	g_array_unref (routes6);
+	g_ptr_array_unref (routes4);
+	g_ptr_array_unref (routes6);
 
 	/* Delete interface with all addresses and routes */
 	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
-	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);
+	addresses4 = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses6 = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	routes4 = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
+	routes6 = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, ifindex);
 
 	g_assert_cmpint (addresses4->len, ==, 0);
 	g_assert_cmpint (addresses6->len, ==, 0);
-	g_assert_cmpint (routes4->len, ==, 0);
-	g_assert_cmpint (routes6->len, ==, 0);
+	g_assert (!routes4);
+	g_assert (!routes6);
 
 	g_array_unref (addresses4);
 	g_array_unref (addresses6);
-	g_array_unref (routes4);
-	g_array_unref (routes6);
 }
 
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index a9d0694d..18076712 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -30,6 +30,9 @@
 #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
 
+int NMTSTP_ENV1_IFINDEX = -1;
+int NMTSTP_ENV1_EX = -1;
+
 /*****************************************************************************/
 
 void
@@ -67,6 +70,90 @@ _init_platform (NMPlatform **platform, gboolean external_command)
 
 /*****************************************************************************/
 
+static GArray *
+_ipx_address_get_all (NMPlatform *self, int ifindex, NMPObjectType obj_type)
+{
+	NMPLookup lookup;
+
+	g_assert (NM_IS_PLATFORM (self));
+	g_assert (ifindex > 0);
+	g_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
+	nmp_lookup_init_addrroute (&lookup,
+	                           obj_type,
+	                           ifindex);
+	return nmp_cache_lookup_to_array (nm_platform_lookup (self, &lookup),
+	                                  obj_type,
+	                                  FALSE /*addresses are always visible. */);
+}
+
+GArray *
+nmtstp_platform_ip4_address_get_all (NMPlatform *self, int ifindex)
+{
+	return _ipx_address_get_all (self, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS);
+}
+
+GArray *
+nmtstp_platform_ip6_address_get_all (NMPlatform *self, int ifindex)
+{
+	return _ipx_address_get_all (self, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
+}
+
+/*****************************************************************************/
+
+gboolean
+nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
+{
+	NMDedupMultiIter iter;
+
+	nm_platform_process_events (platform);
+
+	nm_dedup_multi_iter_for_each (&iter,
+	                              nm_platform_lookup_addrroute (platform,
+	                                                            NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                            ifindex)) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (iter.current->obj);
+
+		if (   r->ifindex != ifindex
+		    || r->network != network
+		    || r->plen != plen
+		    || r->metric != metric) {
+			continue;
+		}
+
+		return nm_platform_ip_route_delete (platform, NMP_OBJECT_UP_CAST (r));
+	}
+
+	return TRUE;
+}
+
+gboolean
+nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
+{
+	NMDedupMultiIter iter;
+
+	nm_platform_process_events (platform);
+
+	nm_dedup_multi_iter_for_each (&iter,
+	                              nm_platform_lookup_addrroute (platform,
+	                                                            NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                                            ifindex)) {
+		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (iter.current->obj);
+
+		if (   r->ifindex != ifindex
+		    || !IN6_ARE_ADDR_EQUAL (&r->network, &network)
+		    || r->plen != plen
+		    || r->metric != metric) {
+			continue;
+		}
+
+		return nm_platform_ip_route_delete (platform, NMP_OBJECT_UP_CAST (r));
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 SignalData *
 add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCallback callback, int ifindex, const char *ifname)
 {
@@ -153,9 +240,9 @@ link_callback (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
-	GArray *links;
-	NMPlatformLink *cached;
-	int i;
+	NMPLookup lookup;
+	NMDedupMultiIter iter;
+	const NMPlatformLink *cached;
 
 	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_LINK);
 	g_assert (received);
@@ -185,19 +272,21 @@ link_callback (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink
 
 	/* Check the data */
 	g_assert (received->ifindex > 0);
-	links = nm_platform_link_get_all (NM_PLATFORM_GET, TRUE);
-	for (i = 0; i < links->len; i++) {
-		cached = &g_array_index (links, NMPlatformLink, i);
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+	nmp_cache_iter_for_each_link (&iter,
+	                              nm_platform_lookup (platform, &lookup),
+	                              &cached) {
+		if (!nmp_object_is_visible (NMP_OBJECT_UP_CAST (cached)))
+			continue;
 		if (cached->ifindex == received->ifindex) {
 			g_assert_cmpint (nm_platform_link_cmp (cached, received), ==, 0);
 			g_assert (!memcmp (cached, received, sizeof (*cached)));
 			if (data->change_type == NM_PLATFORM_SIGNAL_REMOVED)
 				g_error ("Deleted link still found in the local cache.");
-			g_array_unref (links);
 			return;
 		}
 	}
-	g_array_unref (links);
 
 	if (data->change_type != NM_PLATFORM_SIGNAL_REMOVED)
 		g_error ("Added/changed link not found in the local cache.");
@@ -205,113 +294,219 @@ link_callback (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink
 
 /*****************************************************************************/
 
-gboolean
-nmtstp_ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric)
+static const NMPlatformIP4Route *
+_ip4_route_get (NMPlatform *platform,
+                int ifindex,
+                guint32 network,
+                int plen,
+                guint32 metric,
+                guint8 tos,
+                guint *out_c_exists)
 {
-	gs_free char *arg_network = NULL;
-	const char *argv[] = {
-		NULL,
-		"route",
-		"list",
-		"dev",
-		ifname,
-		"exact",
-		NULL,
-		NULL,
-	};
-	int exit_status;
-	gs_free char *std_out = NULL, *std_err = NULL;
-	char *out;
-	gboolean success;
-	gs_free_error GError *error = NULL;
-	gs_free char *metric_pattern = NULL;
+	NMDedupMultiIter iter;
+	NMPLookup lookup;
+	const NMPObject *o = NULL;
+	guint c;
+	const NMPlatformIP4Route *r = NULL;
 
-	g_assert (ifname && nm_utils_is_valid_iface_name (ifname, NULL));
-	g_assert (!strstr (ifname, " metric "));
-	g_assert (plen >= 0 && plen <= 32);
+	_init_platform (&platform, FALSE);
 
-	if (!nmtstp_is_root_test ()) {
-		/* If we don't test against linux-platform, we don't actually configure any
-		 * routes in the system. */
-		return -1;
+	nmp_lookup_init_ip4_route_by_weak_id (&lookup,
+	                                      network,
+	                                      plen,
+	                                      metric,
+	                                      tos);
+
+	c = 0;
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup (platform, &lookup),
+	                         &o) {
+		if (   NMP_OBJECT_CAST_IP4_ROUTE (o)->ifindex != ifindex
+		    && ifindex > 0)
+			continue;
+		if (!r)
+			r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+		c++;
 	}
 
-	argv[0] = nm_utils_file_search_in_paths ("ip", NULL,
-	                                         (const char *[]) { "/sbin", "/usr/sbin", NULL },
-	                                         G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, NULL);
-	argv[6] = arg_network = g_strdup_printf ("%s/%d", nm_utils_inet4_ntop (network, NULL), plen);
+	NM_SET_OUT (out_c_exists, c);
+	return r;
+}
 
-	if (!argv[0]) {
-		/* Hm. There is no 'ip' binary. Return *unknown* */
-		return -1;
+const NMPlatformIP4Route *
+_nmtstp_assert_ip4_route_exists (const char *file,
+                                 guint line,
+                                 const char *func,
+                                 NMPlatform *platform,
+                                 int c_exists,
+                                 const char *ifname,
+                                 guint32 network,
+                                 int plen,
+                                 guint32 metric,
+                                 guint8 tos)
+{
+	int ifindex;
+	guint c;
+	const NMPlatformIP4Route *r = NULL;
+
+	_init_platform (&platform, FALSE);
+
+	ifindex = -1;
+	if (ifname) {
+		ifindex = nm_platform_link_get_ifindex (platform, ifname);
+		g_assert (ifindex > 0);
 	}
 
-	success = g_spawn_sync (NULL,
-	                        (char **) argv,
-	                        (char *[]) { NULL },
-	                        0,
-	                        NULL,
-	                        NULL,
-	                        &std_out,
-	                        &std_err,
-	                        &exit_status,
-	                        &error);
-	g_assert_no_error (error);
-	g_assert (success);
-	g_assert_cmpstr (std_err, ==, "");
-	g_assert (std_out);
-
-	metric_pattern = g_strdup_printf (" metric %u", metric);
-	out = std_out;
-	while (out) {
-		char *eol = strchr (out, '\n');
-		gs_free char *line = eol ? g_strndup (out, eol - out) : g_strdup (out);
-		const char *p;
-
-		out = eol ? &eol[1] : NULL;
-		if (!line[0])
-			continue;
+	r = _ip4_route_get (platform,
+	                    ifindex,
+	                    network,
+	                    plen,
+	                    metric,
+	                    tos,
+	                    &c);
 
-		if (metric == 0) {
-			if (!strstr (line, " metric "))
-				return TRUE;
-		}
-		p = strstr (line, metric_pattern);
-		if (p && NM_IN_SET (p[strlen (metric_pattern)], ' ', '\0'))
-			return TRUE;
+	if (c != c_exists && c_exists != -1) {
+		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but platform has it %u times",
+		         file, line, func,
+		         nm_utils_inet4_ntop (network, NULL), plen,
+		         metric,
+		         tos,
+		         c_exists,
+		         c);
 	}
-	return FALSE;
+
+	return r;
 }
 
-void
-_nmtstp_assert_ip4_route_exists (const char *file, guint line, const char *func, NMPlatform *platform, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric)
+const NMPlatformIP4Route *
+nmtstp_ip4_route_get (NMPlatform *platform,
+                      int ifindex,
+                      guint32 network,
+                      int plen,
+                      guint32 metric,
+                      guint8 tos)
+{
+	return _ip4_route_get (platform,
+	                       ifindex,
+	                       network,
+	                       plen,
+	                       metric,
+	                       tos,
+	                       NULL);
+}
+
+/*****************************************************************************/
+
+static const NMPlatformIP6Route *
+_ip6_route_get (NMPlatform *platform,
+                int ifindex,
+                const struct in6_addr *network,
+                guint plen,
+                guint32 metric,
+                const struct in6_addr *src,
+                guint8 src_plen,
+                guint *out_c_exists)
+{
+	NMDedupMultiIter iter;
+	NMPLookup lookup;
+	const NMPObject *o = NULL;
+	guint c;
+	const NMPlatformIP6Route *r = NULL;
+
+	_init_platform (&platform, FALSE);
+
+	nmp_lookup_init_ip6_route_by_weak_id (&lookup,
+	                                      network,
+	                                      plen,
+	                                      metric,
+	                                      src,
+	                                      src_plen);
+
+	c = 0;
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup (platform, &lookup),
+	                         &o) {
+		if (   NMP_OBJECT_CAST_IP6_ROUTE (o)->ifindex != ifindex
+		    && ifindex > 0)
+			continue;
+		if (!r)
+			r = NMP_OBJECT_CAST_IP6_ROUTE (o);
+		c++;
+	}
+
+	NM_SET_OUT (out_c_exists, c);
+	return r;
+}
+
+const NMPlatformIP6Route *
+_nmtstp_assert_ip6_route_exists (const char *file,
+                                 guint line,
+                                 const char *func,
+                                 NMPlatform *platform,
+                                 int c_exists,
+                                 const char *ifname,
+                                 const struct in6_addr *network,
+                                 guint plen,
+                                 guint32 metric,
+                                 const struct in6_addr *src,
+                                 guint8 src_plen)
 {
 	int ifindex;
-	gboolean exists_checked;
+	guint c;
+	const NMPlatformIP6Route *r = NULL;
 
 	_init_platform (&platform, FALSE);
 
-	/* Check for existance of the route by spawning iproute2. Do this because platform
-	 * code might be entirely borked, but we expect ip-route to give a correct result.
-	 * If the ip command cannot be found, we accept this as success. */
-	exists_checked = nmtstp_ip4_route_exists (ifname, network, plen, metric);
-	if (exists_checked != -1 && !exists_checked != !exists) {
-		g_error ("[%s:%u] %s(): We expect the ip4 route %s/%d metric %u %s, but it %s",
-		         file, line, func,
-		         nm_utils_inet4_ntop (network, NULL), plen, metric,
-		         exists ? "to exist" : "not to exist",
-		         exists ? "doesn't" : "does");
+	ifindex = -1;
+	if (ifname) {
+		ifindex = nm_platform_link_get_ifindex (platform, ifname);
+		g_assert (ifindex > 0);
 	}
 
-	ifindex = nm_platform_link_get_ifindex (platform, ifname);
-	g_assert (ifindex > 0);
-	if (!nm_platform_ip4_route_get (platform, ifindex, network, plen, metric) != !exists) {
-		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u %s, but platform thinks %s",
+	r = _ip6_route_get (platform,
+	                    ifindex,
+	                    network,
+	                    plen,
+	                    metric,
+	                    src,
+	                    src_plen,
+	                    &c);
+
+	if (c != c_exists && c_exists != -1) {
+		char s_src[NM_UTILS_INET_ADDRSTRLEN];
+		char s_network[NM_UTILS_INET_ADDRSTRLEN];
+
+		g_error ("[%s:%u] %s(): The ip6 route %s/%d metric %u src %s/%d shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet4_ntop (network, NULL), plen, metric,
-		         exists ? "exists" : "does not exist",
-		         exists ? "it doesn't" : "it does");
+		         nm_utils_inet6_ntop (network, s_network),
+		         plen,
+		         metric,
+		         nm_utils_inet6_ntop (src, s_src),
+		         src_plen,
+		         c_exists,
+		         c);
 	}
+
+	return r;
+}
+
+const NMPlatformIP6Route *
+nmtstp_ip6_route_get (NMPlatform *platform,
+                      int ifindex,
+                      const struct in6_addr *network,
+                      guint plen,
+                      guint32 metric,
+                      const struct in6_addr *src,
+                      guint8 src_plen)
+{
+	return _ip6_route_get (platform,
+	                       ifindex,
+	                       network,
+	                       plen,
+	                       metric,
+	                       src,
+	                       src_plen,
+	                       NULL);
 }
 
 /*****************************************************************************/
@@ -369,7 +564,7 @@ _wait_for_signal_timeout (gpointer user_data)
 }
 
 guint
-nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms)
+nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 {
 	WaitForSignalData data = { 0 };
 	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
@@ -384,8 +579,18 @@ nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms)
 	id_ip4_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 	id_ip6_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 
-	if (timeout_ms != 0)
-		data.id = g_timeout_add (timeout_ms, _wait_for_signal_timeout, &data);
+	/* if timeout_ms is negative, it means the wait-time already expired.
+	 * Maybe, we should do nothing and return right away, without even
+	 * processing events from platform. However, that inconsistency (of not
+	 * processing events from mainloop) is inconvenient.
+	 *
+	 * It's better that on the return of nmtstp_wait_for_signal(), we always
+	 * have no events pending. So, a negative timeout is treated the same as
+	 * a zero timeout: we check whether there are any events pending in platform,
+	 * and quite the mainloop immediately afterwards. But we always check. */
+
+	data.id = g_timeout_add (CLAMP (timeout_ms, 0, G_MAXUINT32),
+	                         _wait_for_signal_timeout, &data);
 
 	g_main_loop_run (data.loop);
 
@@ -414,14 +619,14 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 		if (until_ms < now)
 			return 0;
 
-		signal_counts = nmtstp_wait_for_signal (platform, MAX (1, until_ms - now));
+		signal_counts = nmtstp_wait_for_signal (platform, until_ms - now);
 		if (signal_counts)
 			return signal_counts;
 	}
 }
 
 const NMPlatformLink *
-nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms)
 {
 	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
 }
@@ -445,7 +650,7 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 		if (until_ms < now)
 			return NULL;
 
-		nmtstp_wait_for_signal (platform, MAX (1, until_ms - now));
+		nmtstp_wait_for_signal (platform, until_ms - now);
 	}
 }
 
@@ -717,7 +922,7 @@ _ip_address_add (NMPlatform *platform,
 			g_assert (label == NULL);
 			g_assert (flags == 0);
 
-			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6, plen);
+			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6);
 			if (   a
 			    && !memcmp (nm_platform_ip6_address_get_peer (a),
 			                (IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) || IN6_ARE_ADDR_EQUAL (&address->addr6, &peer_address->addr6))
@@ -804,7 +1009,7 @@ void nmtstp_ip4_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert (nm_platform_ip4_route_add (platform, &route));
+	g_assert_cmpint (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
 }
 
 void nmtstp_ip6_route_add (NMPlatform *platform,
@@ -828,7 +1033,7 @@ void nmtstp_ip6_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert (nm_platform_ip6_route_add (platform, &route));
+	g_assert_cmpint (nm_platform_ip6_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
 }
 
 /*****************************************************************************/
@@ -861,7 +1066,7 @@ _ip_address_del (NMPlatform *platform,
 		if (is_v4)
 			had_address = !!nm_platform_ip4_address_get (platform, ifindex, address->addr4, plen, peer_address->addr4);
 		else
-			had_address = !!nm_platform_ip6_address_get (platform, ifindex, address->addr6, plen);
+			had_address = !!nm_platform_ip6_address_get (platform, ifindex, address->addr6);
 
 		if (is_v4) {
 			success = nmtstp_run_command ("ip address delete %s%s%s/%d dev %s",
@@ -913,7 +1118,7 @@ _ip_address_del (NMPlatform *platform,
 		} else {
 			const NMPlatformIP6Address *a;
 
-			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6, plen);
+			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6);
 			if (!a)
 				break;
 		}
@@ -974,6 +1179,36 @@ nmtstp_ip6_address_del (NMPlatform *platform,
 	} G_STMT_END
 
 const NMPlatformLink *
+nmtstp_link_veth_add (NMPlatform *platform,
+                      gboolean external_command,
+                      const char *name,
+                      const char *peer)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+
+	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		success = !nmtstp_run_command ("ip link add dev %s type veth peer name %s",
+		                                name, peer);
+		if (success) {
+			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_VETH, 100);
+			nmtstp_assert_wait_for_link (platform, peer, NM_LINK_TYPE_VETH, 10);
+		}
+	} else
+		success = nm_platform_link_veth_add (platform, name, peer, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	g_assert (success);
+	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_VETH);
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_dummy_add (NMPlatform *platform,
                        gboolean external_command,
                        const char *name)
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index a52a5db5..4010aa2f 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -5,6 +5,7 @@
 #include <arpa/inet.h>
 
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "platform/nm-fake-platform.h"
 #include "platform/nm-linux-platform.h"
 
@@ -93,9 +94,9 @@ int nmtstp_run_command (const char *format, ...) _nm_printf (1, 2);
 
 /*****************************************************************************/
 
-guint nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms);
+guint nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms);
 guint nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms);
-const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms);
 const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
 #define nmtstp_assert_wait_for_signal(platform, timeout_ms) \
@@ -120,10 +121,45 @@ gboolean nmtstp_run_command_check_external (int external_command);
 
 /*****************************************************************************/
 
-gboolean nmtstp_ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric);
-
-void _nmtstp_assert_ip4_route_exists (const char *file, guint line, const char *func, NMPlatform *platform, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric);
-#define nmtstp_assert_ip4_route_exists(platform, exists, ifname, network, plen, metric) _nmtstp_assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, platform, exists, ifname, network, plen, metric)
+const NMPlatformIP4Route *_nmtstp_assert_ip4_route_exists (const char *file,
+                                                           guint line,
+                                                           const char *func,
+                                                           NMPlatform *platform,
+                                                           int c_exists,
+                                                           const char *ifname,
+                                                           guint32 network,
+                                                           int plen,
+                                                           guint32 metric,
+                                                           guint8 tos);
+#define nmtstp_assert_ip4_route_exists(platform, c_exists, ifname, network, plen, metric, tos) _nmtstp_assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, platform, c_exists, ifname, network, plen, metric, tos)
+
+const NMPlatformIP4Route *nmtstp_ip4_route_get (NMPlatform *platform,
+                                                int ifindex,
+                                                guint32 network,
+                                                int plen,
+                                                guint32 metric,
+                                                guint8 tos);
+
+const NMPlatformIP6Route *_nmtstp_assert_ip6_route_exists (const char *file,
+                                                           guint line,
+                                                           const char *func,
+                                                           NMPlatform *platform,
+                                                           int c_exists,
+                                                           const char *ifname,
+                                                           const struct in6_addr *network,
+                                                           guint plen,
+                                                           guint32 metric,
+                                                           const struct in6_addr *src,
+                                                           guint8 src_plen);
+#define nmtstp_assert_ip6_route_exists(platform, c_exists, ifname, network, plen, metric, src, src_plen) _nmtstp_assert_ip6_route_exists (__FILE__, __LINE__, G_STRFUNC, platform, c_exists, ifname, network, plen, metric, src, src_plen)
+
+const NMPlatformIP6Route *nmtstp_ip6_route_get (NMPlatform *platform,
+                                                int ifindex,
+                                                const struct in6_addr *network,
+                                                guint plen,
+                                                guint32 metric,
+                                                const struct in6_addr *src,
+                                                guint8 src_plen);
 
 /*****************************************************************************/
 
@@ -187,8 +223,36 @@ void nmtstp_ip6_route_add (NMPlatform *platform,
                            guint32 metric,
                            guint32 mss);
 
+static inline GPtrArray *
+nmtstp_ip4_route_get_all (NMPlatform *platform,
+                          int ifindex)
+{
+	return nm_platform_lookup_addrroute_clone (platform,
+	                                           NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                           ifindex,
+	                                           nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
+	                                           NULL);
+}
+
+static inline GPtrArray *
+nmtstp_ip6_route_get_all (NMPlatform *platform,
+                          int ifindex)
+{
+	return nm_platform_lookup_addrroute_clone (platform,
+	                                           NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                           ifindex,
+	                                           nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
+	                                           NULL);
+}
+
 /*****************************************************************************/
 
+GArray *nmtstp_platform_ip4_address_get_all (NMPlatform *self, int ifindex);
+GArray *nmtstp_platform_ip6_address_get_all (NMPlatform *self, int ifindex);
+
+gboolean nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
+gboolean nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
+
 const NMPlatformLink *nmtstp_link_get_typed (NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type);
 const NMPlatformLink *nmtstp_link_get (NMPlatform *platform, int ifindex, const char *name);
 
@@ -197,6 +261,10 @@ void nmtstp_link_set_updown (NMPlatform *platform,
                              int ifindex,
                              gboolean up);
 
+const NMPlatformLink *nmtstp_link_veth_add (NMPlatform *platform,
+                                            gboolean external_command,
+                                            const char *name,
+                                            const char *peer);
 const NMPlatformLink *nmtstp_link_dummy_add (NMPlatform *platform,
                                              gboolean external_command,
                                              const char *name);
@@ -231,6 +299,100 @@ void nmtstp_link_del (NMPlatform *platform,
                       int ifindex,
                       const char *name);
 
+/*****************************************************************************/
+
+extern int NMTSTP_ENV1_IFINDEX;
+extern int NMTSTP_ENV1_EX;
+
+static inline void
+_nmtstp_env1_wrapper_setup (const NmtstTestData *test_data)
+{
+	int *p_ifindex;
+	gpointer p_ifup;
+
+	nmtst_test_data_unpack (test_data, &p_ifindex, NULL, NULL, NULL, &p_ifup);
+
+	g_assert (p_ifindex && *p_ifindex == -1);
+
+	_LOGT ("TEST[%s]: setup", test_data->testpath);
+
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
+
+	*p_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (*p_ifindex, >, 0);
+	g_assert_cmpint (NMTSTP_ENV1_IFINDEX, ==, -1);
+
+	if (GPOINTER_TO_INT (p_ifup))
+		g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, *p_ifindex, NULL));
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	NMTSTP_ENV1_IFINDEX = *p_ifindex;
+	NMTSTP_ENV1_EX = nmtstp_run_command_check_external_global ();
+}
+
+static inline void
+_nmtstp_env1_wrapper_run (gconstpointer user_data)
+{
+	const NmtstTestData *test_data = user_data;
+	GTestDataFunc test_func_data;
+	GTestFunc test_func;
+	gconstpointer d;
+
+	nmtst_test_data_unpack (test_data, NULL, &test_func, &test_func_data, &d, NULL);
+
+	_LOGT ("TEST[%s]: run", test_data->testpath);
+	if (test_func)
+		test_func ();
+	else
+		test_func_data (d);
+}
+
+static inline void
+_nmtstp_env1_wrapper_teardown (const NmtstTestData *test_data)
+{
+	int *p_ifindex;
+
+	nmtst_test_data_unpack (test_data, &p_ifindex, NULL, NULL, NULL, NULL);
+
+	g_assert_cmpint (NMTSTP_ENV1_IFINDEX, ==, *p_ifindex);
+	NMTSTP_ENV1_IFINDEX = -1;
+
+	_LOGT ("TEST[%s]: teardown", test_data->testpath);
+
+	g_assert_cmpint (*p_ifindex, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, *p_ifindex));
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	_LOGT ("TEST[%s]: finished", test_data->testpath);
+
+	*p_ifindex = -1;
+}
+
+/* add test function, that set's up a particular environment, consisting
+ * of a dummy device with ifindex NMTSTP_ENV1_IFINDEX. */
+#define _nmtstp_env1_add_test_func_full(testpath, test_func, test_data_func, arg, ifup) \
+	nmtst_add_test_func_full (testpath, \
+	                          _nmtstp_env1_wrapper_run, \
+	                          _nmtstp_env1_wrapper_setup, \
+	                          _nmtstp_env1_wrapper_teardown, \
+	                          ({ static int _ifindex = -1; &_ifindex; }), \
+	                          ({ GTestFunc _test_func = (test_func); _test_func; }), \
+	                          ({ GTestDataFunc _test_func = (test_data_func); _test_func; }), \
+	                          (arg), \
+	                          ({ gboolean _ifup = (ifup); GINT_TO_POINTER (_ifup);}))
+
+#define nmtstp_env1_add_test_func_data(testpath, test_func, arg, ifup) \
+	_nmtstp_env1_add_test_func_full(testpath, NULL, test_func, arg, ifup)
+
+#define nmtstp_env1_add_test_func(testpath, test_func, ifup) \
+	_nmtstp_env1_add_test_func_full(testpath, test_func, NULL, NULL, ifup)
+
+/*****************************************************************************/
+
 typedef void (*NMTstpSetupFunc) (void);
 extern NMTstpSetupFunc const _nmtstp_setup_platform_func;
 
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index e772662c..342aa0d6 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -44,7 +44,7 @@ static void
 test_link_get_all (void)
 {
 	gs_unref_object NMPlatform *platform = NULL;
-	gs_unref_array GArray *links = NULL;
+	gs_unref_ptrarray GPtrArray *links = NULL;
 
 	platform = nm_linux_platform_new (TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT);
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index ed435567..9c72371c 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -76,7 +76,7 @@ test_bogus(void)
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
+	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU) != NM_PLATFORM_ERROR_SUCCESS);
 
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
@@ -264,7 +264,8 @@ test_slave (int master, int type, SignalData *master_changed)
 	}
 	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 (   nmtstp_is_root_test ()
+	    && 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
@@ -285,7 +286,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, master));
 	accept_signals (link_changed, 1, 3);
 	/* NM running, can cause additional change of addrgenmode */
-	accept_signals (master_changed, 1, 2);
+	accept_signals (master_changed, 0, 2);
 
 	/* Enslave again
 	 *
@@ -294,7 +295,7 @@ test_slave (int master, int type, SignalData *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);
+	accept_signals (master_changed, 0, 2);
 
 	/* Set slave option */
 	switch (type) {
@@ -327,7 +328,7 @@ test_slave (int master, int type, SignalData *master_changed)
 		ensure_no_signal (link_changed);
 		accept_signal (link_removed);
 	}
-	accept_signals (master_changed, 1, 2);
+	accept_signals (master_changed, 0, 2);
 
 	ensure_no_signal (master_changed);
 
@@ -511,7 +512,8 @@ test_bridge_addr (void)
 	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 	g_assert (plink);
 
-	if (nm_platform_check_support_user_ipv6ll (NM_PLATFORM_GET)) {
+	if (nm_platform_check_kernel_support (NM_PLATFORM_GET,
+	                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
@@ -601,7 +603,7 @@ test_internal (void)
 	accept_signal (link_changed);
 
 	/* Set MTU */
-	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU));
+	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU) == NM_PLATFORM_ERROR_SUCCESS);
 	g_assert_cmpint (nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex), ==, MTU);
 	accept_signal (link_changed);
 
@@ -790,7 +792,7 @@ test_software_detect (gconstpointer user_data)
 		 * namespaced, the creation can fail if a macvtap in another namespace
 		 * has the same index. Try to detect this situation and skip already
 		 * used indexes.
-		 * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=17af2bce88d31e65ed73d638bb752d2e13c66ced
+		 * The fix (17af2bce) is included kernel 4.7, dated 24 July, 2016.
 		 */
 		for (i = ifindex_parent + 1; i < ifindex_parent + 100; i++) {
 			snprintf (buf, sizeof (buf), "/sys/class/macvtap/tap%d", i);
@@ -1713,9 +1715,8 @@ test_nl_bugs_veth (void)
 	NMTstpNamespaceHandle *ns_handle = NULL;
 
 	/* create veth pair. */
-	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
-	ifindex_veth0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
-	ifindex_veth1 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+	ifindex_veth0 = nmtstp_link_veth_add (NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
+	ifindex_veth1 = nmtstp_link_get_typed (NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex;
 
 	/* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */
 	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth0, &i));
@@ -1728,8 +1729,8 @@ test_nl_bugs_veth (void)
 	pllink_veth0 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth0);
 	g_assert (pllink_veth0);
 	if (pllink_veth0->parent == 0) {
-		/* pre-4.1 kernels don't support exposing the veth peer as IFA_LINK. skip the remainder
-		 * of the test. */
+		/* Kernels prior to 4.1 dated 21 June, 2015 don't support exposing the veth peer
+		 * as IFA_LINK. skip the remainder of the test. */
 		goto out;
 	}
 	g_assert_cmpint (pllink_veth0->parent, ==, ifindex_veth1);
@@ -2023,8 +2024,8 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 	_sysctl_assert_eq (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6", NULL);
 	_sysctl_assert_eq (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6", NULL);
 
-	/* older kernels (Ubuntu 12.04) don't support ethtool -i for dummy devices. Work around that and
-	 * skip asserts that are known to fail. */
+	/* Kernels prior to 3.19 dated 8 February, 2015 don't support ethtool -i for dummy devices.
+	 * Work around that and skip asserts that are known to fail. */
 	ethtool_support = nmtstp_run_command ("ethtool -i dummy1_ > /dev/null") == 0;
 	if (ethtool_support) {
 		g_assert (nmp_utils_ethtool_get_driver_info (nmtstp_link_get_typed (platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, &driver_info));
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 42dfc572..a02388d2 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -33,6 +33,60 @@ struct {
 
 /*****************************************************************************/
 
+static void
+test_obj_base (void)
+{
+	static const union {
+		GObject g;
+		NMPObject k;
+	} x = { };
+	static const union {
+		GTypeClass k;
+		NMPClass c;
+	} l = { };
+	static const GObject *g = &x.g;
+	static const GTypeClass *k = &l.k;
+	static const NMPObject *o = &x.k;
+	static const NMPClass *c = &l.c;
+
+	NMObjBaseInst *obj;
+	gs_unref_object GCancellable *obj_cancellable = g_cancellable_new ();
+	nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link (10);
+
+#define STATIC_ASSERT(cond) \
+	G_STMT_START { \
+		G_STATIC_ASSERT (cond); \
+		G_STATIC_ASSERT_EXPR (cond); \
+		g_assert (cond); \
+	} G_STMT_END
+
+	STATIC_ASSERT (&g->g_type_instance              == (void *) &o->_class);
+	STATIC_ASSERT (&g->g_type_instance.g_class      == (void *) &o->_class);
+
+	STATIC_ASSERT (sizeof (o->parent.parent)        == sizeof (GTypeInstance));
+
+	STATIC_ASSERT (&c->parent                       == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type_class   == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type         == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type         == &k->g_type);
+
+	STATIC_ASSERT (sizeof (c->parent.parent)        == sizeof (GTypeClass));
+
+	STATIC_ASSERT (&o->parent                       == (void *) o);
+	STATIC_ASSERT (&o->parent.klass                 == (void *) &o->_class);
+
+	obj = (NMObjBaseInst *) obj_cancellable;
+	g_assert (!NMP_CLASS_IS_VALID ((NMPClass *) obj->klass));
+	g_assert (G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_CANCELLABLE));
+
+	obj = (NMObjBaseInst *) obj_link;
+	g_assert (NMP_CLASS_IS_VALID ((NMPClass *) obj->klass));
+	g_assert (!G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_CANCELLABLE));
+
+}
+
+/*****************************************************************************/
+
 static gboolean
 _nmp_object_id_equal (const NMPObject *a, const NMPObject *b)
 {
@@ -56,150 +110,144 @@ _nmp_object_equal (const NMPObject *a, const NMPObject *b)
 /*****************************************************************************/
 
 static void
-_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMPCacheId *cache_id, const NMPObject *obj, gboolean contains)
+_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMDedupMultiHeadEntry *head_entry, const NMPObject *obj, gboolean visible_only, gboolean contains)
 {
-	const NMPlatformObject *const *objects;
-	guint i, len;
+	NMDedupMultiIter iter;
 	gboolean found;
+	guint i, len;
+	const NMPObject *o;
 
-	g_assert (cache_id);
 	g_assert (NMP_OBJECT_IS_VALID (obj));
 
 	g_assert (nmp_cache_lookup_obj (cache, obj) == obj);
+	g_assert (!head_entry || (head_entry->len > 0 && c_list_length (&head_entry->lst_entries_head) == head_entry->len));
 
-	objects = nmp_cache_lookup_multi (cache, cache_id, &len);
-
-	g_assert ((len == 0 && !objects) || (len > 0 && objects && !objects[len]));
+	len = head_entry ? head_entry->len : 0;
 
 	found = FALSE;
-	for (i = 0; i < len; i++) {
-		NMPObject *o;
-
-		g_assert (objects[i]);
-		o = NMP_OBJECT_UP_CAST (objects[i]);
+	i = 0;
+	nmp_cache_iter_for_each (&iter,
+	                         head_entry,
+	                         &o) {
 		g_assert (NMP_OBJECT_IS_VALID (o));
-
 		if (obj == o) {
-			g_assert (!found);
-			found = TRUE;
+			if (   !visible_only
+			    || nmp_object_is_visible (o)) {
+				g_assert (!found);
+				found = TRUE;
+			}
 		}
+		i++;
 	}
 
+	g_assert (len == i);
 	g_assert (!!contains == found);
 }
 
-/*****************************************************************************/
+static void
+_assert_cache_multi_lookup_contains_link (const NMPCache *cache,
+                                          gboolean visible_only,
+                                          const NMPObject *obj,
+                                          gboolean contains)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	NMPLookup lookup;
 
-typedef struct {
-	NMPCache *cache;
-	NMPCacheOpsType expected_ops_type;
-	const NMPObject *obj_clone;
-	NMPObject *new_clone;
-	gboolean was_visible;
-	gboolean called;
-} _NMPCacheUpdateData;
+	g_assert (cache);
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+	head_entry = nmp_cache_lookup (cache, &lookup);
+	_assert_cache_multi_lookup_contains (cache, head_entry, obj, visible_only, contains);
+}
+
+/*****************************************************************************/
 
 static void
-_nmp_cache_update_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+ops_post_check (NMPCache *cache,
+                NMPCacheOpsType ops_type,
+                const NMPObject *obj_old,
+                const NMPObject *obj_new,
+                const NMPObject *obj_new_expected,
+                NMPCacheOpsType expected_ops_type)
 {
-	_NMPCacheUpdateData *data = user_data;
-
-	g_assert (data);
-	g_assert (!data->called);
-	g_assert (data->cache == cache);
+	g_assert (cache);
 
-	g_assert_cmpint (data->expected_ops_type, ==, ops_type);
+	g_assert_cmpint (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));
+		g_assert (!obj_old);
+		g_assert (NMP_OBJECT_IS_VALID (obj_new));
+		g_assert (nmp_object_is_alive (obj_new));
+		g_assert (nmp_object_id_equal (obj_new_expected, obj_new));
+		g_assert (nmp_object_equal (obj_new_expected, obj_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));
+		g_assert (obj_old != obj_new);
+		g_assert (NMP_OBJECT_IS_VALID (obj_old));
+		g_assert (NMP_OBJECT_IS_VALID (obj_new));
+		g_assert (nmp_object_is_alive (obj_old));
+		g_assert (nmp_object_is_alive (obj_new));
+		g_assert (nmp_object_id_equal (obj_new_expected, obj_new));
+		g_assert (nmp_object_id_equal (obj_new_expected, obj_old));
+		g_assert (nmp_object_id_equal (obj_old, obj_new));
+		g_assert (nmp_object_equal (obj_new_expected, obj_new));
+		g_assert (!nmp_object_equal (obj_new_expected, obj_old));
+		g_assert (!nmp_object_equal (obj_old, obj_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));
+		g_assert (!obj_new);
+		g_assert (NMP_OBJECT_IS_VALID (obj_old));
+		g_assert (nmp_object_is_alive (obj_old));
+		if (obj_new_expected)
+			g_assert (nmp_object_id_equal (obj_new_expected, obj_old));
+		break;
+	case NMP_CACHE_OPS_UNCHANGED:
+		g_assert (obj_old == obj_new);
+		if (obj_old) {
+			g_assert (NMP_OBJECT_IS_VALID (obj_old));
+			g_assert (nmp_object_is_alive (obj_old));
+			g_assert (nmp_object_equal (obj_old, obj_new));
+			g_assert (nmp_object_id_equal (obj_new_expected, obj_new));
+		} else
+			g_assert (!obj_new_expected);
 		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)
+_nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, const NMPObject **out_obj_old, const NMPObject **out_obj_new, NMPCacheOpsType expected_ops_type)
 {
 	NMPCacheOpsType ops_type;
-	NMPObject *obj2;
-	gboolean was_visible;
-	nm_auto_nmpobj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
-	nm_auto_nmpobj NMPObject *new_clone = NULL;
+	const NMPObject *obj_prev;
 	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 = udev_device_ref (obj_old->_link.udev.device);
-	_nmp_object_fixup_link_udev_fields (obj_clone, nmp_cache_use_udev_get (cache));
+	const NMPObject *obj_new;
+	nm_auto_nmpobj NMPObject *obj_new_expected = NULL;
 
 	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);
+	obj_prev = nmp_cache_lookup_link (cache, obj->object.ifindex);
+	obj_new_expected = nmp_object_clone (obj, FALSE);
+	if (obj_prev && obj_prev->_link.udev.device)
+		obj_new_expected->_link.udev.device = udev_device_ref (obj_prev->_link.udev.device);
+	_nmp_object_fixup_link_udev_fields (&obj_new_expected, NULL, nmp_cache_use_udev_get (cache));
 
-	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);
+	ops_type = nmp_cache_update_netlink (cache, obj, FALSE, &obj_old, &obj_new);
+	ops_post_check (cache, ops_type, obj_old, obj_new,
+	                nmp_object_is_alive (obj_new_expected) ? obj_new_expected : NULL,
+	                expected_ops_type);
 
-		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;
+	if (out_obj_new)
+		*out_obj_new = obj_new;
 	else
-		nmp_object_unref (obj2);
-	if (out_was_visible)
-		*out_was_visible = was_visible;
+		nmp_object_unref (obj_new);
+	if (out_obj_old)
+		*out_obj_old = obj_old;
+	else
+		nmp_object_unref (obj_old);
 }
 
 static const NMPlatformLink pl_link_2 = {
@@ -218,168 +266,189 @@ static void
 test_cache_link (void)
 {
 	NMPCache *cache;
-	NMPObject *obj1, *obj2;
+	NMPObject *objm1;
+	const NMPObject *obj_old, *obj_new;
 	NMPObject objs1;
-	gboolean was_visible;
-	NMPCacheId cache_id_storage;
 	struct udev_device *udev_device_2 = g_list_nth_data (global.udev_devices, 0);
 	struct udev_device *udev_device_3 = g_list_nth_data (global.udev_devices, 0);
 	NMPCacheOpsType ops_type;
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL;
+
+	multi_idx = nm_dedup_multi_index_new ();
 
-	cache = nmp_cache_new (nmtst_get_rand_int () % 2);
+	cache = nmp_cache_new (multi_idx, nmtst_get_rand_int () % 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);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	g_assert (NMP_OBJECT_UP_CAST (&objm1->object) == objm1);
+	g_assert (!nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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 (!obj_old);
+	g_assert (!obj_new);
+	g_assert (!nmp_cache_lookup_obj (cache, objm1));
 	g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
-	nmp_object_unref (obj1);
+	nmp_object_unref (objm1);
 
 	/* 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);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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);
+	g_assert (!obj_old);
+	g_assert (obj_new);
+	g_assert (objm1 == obj_new);
+	g_assert (nmp_object_equal (objm1, obj_new));
+	g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+	g_assert (nmp_object_is_visible (obj_new));
+	_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+	_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_new);
 
 	/* 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);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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);
+	g_assert (obj_old);
+	g_assert (obj_new);
+	g_assert (obj_new != objm1);
+	g_assert (nmp_object_equal (objm1, obj_new));
+	g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_new);
+	nmp_object_unref (obj_new);
 
 	/* 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);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	g_assert (!nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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 (obj_old);
+	g_assert (!obj_new);
+	g_assert (!nmp_cache_lookup_obj (cache, objm1));
 	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);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_old);
+	nmp_object_unref (obj_new);
 
 	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);
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_2.ifindex, udev_device_2, &obj_old, &obj_new);
 		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);
+		g_assert (!obj_old);
+		g_assert (obj_new);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+		g_assert (!nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, FALSE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		nmp_object_unref (obj_new);
 	}
 
 	/* 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);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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);
+	if (udev_device_2) {
+		g_assert (obj_old);
+		g_assert (!nmp_object_is_visible (obj_old));
+	} else
+		g_assert (!obj_old);
+	g_assert (nmp_object_equal (objm1, obj_new));
+	g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+	g_assert (nmp_object_is_visible (obj_new));
+	_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+	_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_old);
+	nmp_object_unref (obj_new);
 
 	/* 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);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = FALSE;
+	g_assert (!nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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 (obj_new == objm1);
+	else
+		g_assert (!obj_new);
+	g_assert (obj_old);
+	g_assert (nmp_object_is_alive (obj_old));
 	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);
+		g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+		g_assert (!nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, FALSE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
 	} else {
-		g_assert (nmp_cache_lookup_obj (cache, obj1) == NULL);
+		g_assert (nmp_cache_lookup_obj (cache, objm1) == 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));
+		g_assert (nmp_object_is_visible (obj_new));
 	}
-	nmp_object_unref (obj1);
-	nmp_object_unref (obj2);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_old);
+	nmp_object_unref (obj_new);
 
 	/* 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);
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, udev_device_3, &obj_old, &obj_new);
 		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);
+		g_assert (NMP_OBJECT_IS_VALID (obj_new));
+		g_assert (!obj_old);
+		g_assert (!nmp_object_is_visible (obj_new));
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj_new);
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, FALSE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		g_assert_cmpint (obj_new->_link.netlink.is_in_netlink, ==, FALSE);
+		g_assert_cmpint (obj_new->link.initialized, ==, FALSE);
+		nmp_object_unref (obj_new);
 
 		/* 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);
+		objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3);
+		objm1->_link.netlink.is_in_netlink = TRUE;
+		g_assert (nmp_object_is_alive (objm1));
+		_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, 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);
+		g_assert (obj_old);
+		g_assert (obj_new == objm1);
+		g_assert (nmp_object_equal (objm1, obj_new));
+		g_assert (!obj_old || !nmp_object_is_visible (obj_old));
+		g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj_new);
+		g_assert (nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		g_assert_cmpint (obj_new->_link.netlink.is_in_netlink, ==, TRUE);
+		g_assert_cmpint (obj_new->link.initialized, ==, TRUE);
+		nmp_object_unref (objm1);
+		nmp_object_unref (obj_old);
+		nmp_object_unref (obj_new);
 
 		/* remove UDEV. */
-		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj2, &was_visible, NULL, NULL);
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj_old, &obj_new);
 		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);
+		g_assert (obj_old && nmp_object_is_visible (obj_old));
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj_new);
+		g_assert (nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		g_assert_cmpint (obj_new->_link.netlink.is_in_netlink, ==, TRUE);
+		g_assert_cmpint (obj_new->link.initialized, ==, !nmp_cache_use_udev_get (cache));
+		nmp_object_unref (obj_new);
+		nmp_object_unref (obj_old);
 	}
 
 	nmp_cache_free (cache);
@@ -429,6 +498,7 @@ main (int argc, char **argv)
 		udev_enumerate_unref (enumerator);
 	}
 
+	g_test_add_func ("/nmp-object/obj-base", test_obj_base);
 	g_test_add_func ("/nmp-object/cache_link", test_cache_link);
 
 	result = g_test_run ();
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 9960d867..2c00fada 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -27,13 +27,50 @@
 
 #include "test-common.h"
 
-#define DEVICE_NAME "nm-test-device"
+#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX
+#define EX             NMTSTP_ENV1_EX
+
+static void
+_wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
+                                   gint64 timeout_ms,
+                                   int ifindex,
+                                   guint addr_n,
+                                   const struct in6_addr *addrs)
+{
+	guint i;
+
+	/* Wait that the addresses become non-tentative.  Dummy interfaces are NOARP
+	 * and thus don't do DAD, but the kernel sets the address as tentative for a
+	 * small amount of time, which prevents the immediate addition of the route
+	 * with RTA_PREFSRC */
+
+	NMTST_WAIT_ASSERT (400, {
+		gboolean should_wait = FALSE;
+		const NMPlatformIP6Address *plt_addr;
+
+		for (i = 0; i < addr_n; i++) {
+			plt_addr = nm_platform_ip6_address_get (platform, ifindex, addrs[i]);
+			if (   !plt_addr
+			    || NM_FLAGS_HAS (plt_addr->n_ifa_flags, IFA_F_TENTATIVE)) {
+				should_wait = TRUE;
+				break;
+			}
+		}
+		if (!should_wait)
+			return;
+		nmtstp_assert_wait_for_signal (platform,
+		                               (nmtst_wait_end_us - g_get_monotonic_time ()) / 1000);
+	});
+}
+
 
 static void
 ip4_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMPlatformIP4Route *received, int change_type_i, SignalData *data)
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMPObject o_id;
+	nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
 
 	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_IP4_ROUTE);
 	g_assert (received);
@@ -41,6 +78,11 @@ ip4_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMP
 	g_assert (data && data->name);
 	g_assert_cmpstr (data->name, ==, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED);
 
+	/* run code for initializing the ID only */
+	nmp_object_stackinit_id (&o_id, NMP_OBJECT_UP_CAST (received));
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), TRUE);
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), FALSE);
+
 	if (data->ifindex && data->ifindex != received->ifindex)
 		return;
 	if (data->change_type != change_type)
@@ -58,6 +100,8 @@ ip6_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMP
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMPObject o_id;
+	nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
 
 	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_IP6_ROUTE);
 	g_assert (received);
@@ -65,6 +109,11 @@ ip6_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMP
 	g_assert (data && data->name);
 	g_assert_cmpstr (data->name, ==, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED);
 
+	/* run code for initializing the ID only */
+	nmp_object_stackinit_id (&o_id, NMP_OBJECT_UP_CAST (received));
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), TRUE);
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), FALSE);
+
 	if (data->ifindex && data->ifindex != received->ifindex)
 		return;
 	if (data->change_type != change_type)
@@ -90,50 +139,50 @@ test_ip4_route_metric0 (void)
 	int mss = 1000;
 
 	/* No routes initially */
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 
 	/* add the first route */
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, metric, mss);
 	accept_signal (route_added);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Deleting route with metric 0 does nothing */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	ensure_no_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* add the second route */
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, 0, mss);
 	accept_signal (route_added);
 
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Delete route with metric 0 */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	accept_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Delete route with metric 0 again (we expect nothing to happen) */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	ensure_no_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Delete the other route */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	accept_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 
 	free_signal (route_added);
 	free_signal (route_changed);
@@ -147,7 +196,7 @@ test_ip4_route (void)
 	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);
-	GArray *routes;
+	GPtrArray *routes;
 	NMPlatformIP4Route rts[3];
 	in_addr_t network;
 	guint8 plen = 24;
@@ -164,9 +213,9 @@ test_ip4_route (void)
 	accept_signal (route_added);
 
 	/* Add route */
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 1, DEVICE_NAME, network, plen, metric, 0);
 	accept_signal (route_added);
 
 	/* Add route again */
@@ -174,9 +223,9 @@ test_ip4_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Add default route */
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, 0, 0, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, 0, 0, metric, 0);
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE, DEVICE_NAME, 0, 0, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 1, DEVICE_NAME, 0, 0, metric, 0);
 	accept_signal (route_added);
 
 	/* Add default route again */
@@ -184,7 +233,7 @@ test_ip4_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Test route listing */
-	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);
+	routes = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
 	memset (rts, 0, sizeof (rts));
 	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].network = gateway;
@@ -211,23 +260,23 @@ test_ip4_route (void)
 	rts[2].mss = mss;
 	rts[2].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE);
 	g_assert_cmpint (routes->len, ==, 3);
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len, TRUE);
-	g_array_unref (routes);
+	nmtst_platform_ip4_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
+	g_ptr_array_unref (routes);
 
 	/* Remove route */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 	accept_signal (route_removed);
 
 	/* Remove route again */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 
 	/* Remove default route */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, 0, 0, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, 0, 0, metric));
 	accept_signal (route_removed);
 
 	/* Remove route to gateway */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, gateway, 32, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, gateway, 32, metric));
 	accept_signal (route_removed);
 
 	free_signal (route_added);
@@ -242,7 +291,7 @@ test_ip6_route (void)
 	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);
-	GArray *routes;
+	GPtrArray *routes;
 	NMPlatformIP6Route rts[3];
 	struct in6_addr network;
 	guint8 plen = 64;
@@ -259,28 +308,16 @@ test_ip6_route (void)
 	                                       NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0));
 	accept_signals (route_added, 0, 1);
 
-	/* Wait that the address becomes non-tentative.  Dummy interfaces are NOARP
-	 * and thus don't do DAD, but the kernel sets the address as tentative for a
-	 * small amount of time, which prevents the immediate addition of the route
-	 * with RTA_PREFSRC */
-	NMTST_WAIT_ASSERT (200, {
-		const NMPlatformIP6Address *plt_addr;
-
-		nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
-		nm_platform_process_events (NM_PLATFORM_GET);
-		plt_addr = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, pref_src, 128);
-		if (plt_addr && !NM_FLAGS_HAS (plt_addr->n_ifa_flags, IFA_F_TENTATIVE))
-			break;
-	});
+	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 200, ifindex, 1, &pref_src);
 
 	/* Add route to gateway */
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 128, in6addr_any, in6addr_any, metric, mss);
 	accept_signal (route_added);
 
 	/* Add route */
-	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, pref_src, metric, mss);
-	g_assert (nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
 	accept_signal (route_added);
 
 	/* Add route again */
@@ -288,9 +325,9 @@ test_ip6_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Add default route */
-	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0));
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway, in6addr_any, metric, mss);
-	g_assert (nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0));
 	accept_signal (route_added);
 
 	/* Add default route again */
@@ -298,7 +335,7 @@ test_ip6_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Test route listing */
-	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);
+	routes = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, ifindex);
 	memset (rts, 0, sizeof (rts));
 	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].network = gateway;
@@ -325,23 +362,23 @@ test_ip6_route (void)
 	rts[2].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[2].mss = mss;
 	g_assert_cmpint (routes->len, ==, 3);
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len, TRUE);
-	g_array_unref (routes);
+	nmtst_platform_ip6_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
+	g_ptr_array_unref (routes);
 
 	/* Remove route */
-	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));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
 	accept_signal (route_removed);
 
 	/* Remove route again */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 
 	/* Remove default route */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
 	accept_signal (route_removed);
 
 	/* Remove route to gateway */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, gateway, 128, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, gateway, 128, metric));
 	accept_signal (route_removed);
 
 	free_signal (route_added);
@@ -352,6 +389,45 @@ test_ip6_route (void)
 /*****************************************************************************/
 
 static void
+test_ip_route_get (void)
+{
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	in_addr_t a;
+	NMPlatformError result;
+	nm_auto_nmpobj NMPObject *route = NULL;
+	const NMPlatformIP4Route *r;
+
+	nmtstp_run_command_check ("ip route add 1.2.3.0/24 dev %s", DEVICE_NAME);
+
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
+		if (nmtstp_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.0"), 24, 0, 0))
+			break;
+	});
+
+	a = nmtst_inet4_from_string ("1.2.3.1");
+	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
+	                                   AF_INET,
+	                                   &a,
+	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   &route);
+
+	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP4_ROUTE);
+	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
+	g_assert (route->parent._ref_count == 1);
+	r = NMP_OBJECT_CAST_IP4_ROUTE (route);
+	g_assert (r->rt_cloned);
+	g_assert (r->ifindex == ifindex);
+	g_assert (r->network == a);
+	g_assert (r->plen == 32);
+
+	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
+}
+
+static void
 test_ip4_zero_gateway (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
@@ -361,15 +437,14 @@ test_ip4_zero_gateway (void)
 
 	NMTST_WAIT_ASSERT (100, {
 		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
-		if (   nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0)
-		    && nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0))
+		if (   nmtstp_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0, 0)
+		    && nmtstp_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0, 0))
 			break;
 	});
 
 	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
 
 	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
-	nm_platform_process_events (NM_PLATFORM_GET);
 }
 
 static void
@@ -378,7 +453,7 @@ test_ip4_route_options (void)
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	NMPlatformIP4Route route = { };
 	in_addr_t network;
-	GArray *routes;
+	GPtrArray *routes;
 	NMPlatformIP4Route rts[1];
 
 	inet_pton (AF_INET, "172.16.1.0", &network);
@@ -396,12 +471,10 @@ test_ip4_route_options (void)
 	route.mtu = 1350;
 	route.lock_cwnd = TRUE;
 
-	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, &route));
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &route) == NM_PLATFORM_ERROR_SUCCESS);
 
 	/* Test route listing */
-	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);
+	routes = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
 	memset (rts, 0, sizeof (rts));
 	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK);
@@ -416,69 +489,268 @@ test_ip4_route_options (void)
 	rts[0].initrwnd = 50;
 	rts[0].mtu = 1350;
 	rts[0].lock_cwnd = TRUE;
-
 	g_assert_cmpint (routes->len, ==, 1);
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len, TRUE);
+	nmtst_platform_ip4_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
 
 	/* Remove route */
-	/* FIXME. Due to a bug, we cannot delete routes with non-zero TOS. See bgo#785004. */
-	//g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, 24, 20));
+	g_assert (nm_platform_ip_route_delete (NM_PLATFORM_GET, routes->pdata[0]));
 
-	g_array_unref (routes);
+	g_ptr_array_unref (routes);
 }
 
 
 static void
-test_ip6_route_options (void)
+test_ip6_route_options (gconstpointer test_data)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
-	NMPlatformIP6Route route = { };
-	struct in6_addr network;
-	GArray *routes;
-	NMPlatformIP6Route rts[3];
-
-	inet_pton (AF_INET6, "2001:db8:a:b:0:0:0:0", &network);
-
-	route.ifindex = ifindex;
-	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	route.network = network;
-	route.plen = 64;
-	route.gateway = in6addr_any;
-	route.metric = 1024;
-	route.window = 20000;
-	route.cwnd = 8;
-	route.initcwnd = 22;
-	route.initrwnd = 33;
-	route.mtu = 1300;
-	route.lock_mtu = TRUE;
-
-	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, &route));
-
-	/* Test route listing */
-	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].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	rts[0].network = network;
-	rts[0].plen = 64;
-	rts[0].ifindex = ifindex;
-	rts[0].gateway = in6addr_any;
-	rts[0].metric = 1024;
-	rts[0].window = 20000;
-	rts[0].cwnd = 8;
-	rts[0].initcwnd = 22;
-	rts[0].initrwnd = 33;
-	rts[0].mtu = 1300;
-	rts[0].lock_mtu = TRUE;
-
-	g_assert_cmpint (routes->len, ==, 1);
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len, TRUE);
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const int IFINDEX = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	GPtrArray *routes;
+#define RTS_MAX 3
+	NMPlatformIP6Route rts_add[RTS_MAX] = { };
+	NMPlatformIP6Route rts_cmp[RTS_MAX] = { };
+	NMPlatformIP6Address addr[1] = { };
+	struct in6_addr addr_in6[G_N_ELEMENTS (addr)] = { };
+	guint rts_n = 0;
+	guint addr_n = 0;
+	guint i;
+
+	switch (TEST_IDX) {
+	case 1:
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:a:b:0:0:0:0"),
+			.plen = 64,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.window = 20000,
+			.cwnd = 8,
+			.initcwnd = 22,
+			.initrwnd = 33,
+			.mtu = 1300,
+			.lock_mtu = TRUE,
+		});
+		break;
+	case 2:
+		addr[addr_n++] = ((NMPlatformIP6Address) {
+			.ifindex = IFINDEX,
+			.address = *nmtst_inet6_from_string ("2000::2"),
+			.plen = 128,
+			.peer_address = in6addr_any,
+			.lifetime = NM_PLATFORM_LIFETIME_PERMANENT,
+			.preferred = NM_PLATFORM_LIFETIME_PERMANENT,
+			.n_ifa_flags = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("1010::1"),
+			.plen = 128,
+			.gateway = in6addr_any,
+			.metric = 256,
+			.pref_src = *nmtst_inet6_from_string ("2000::2"),
+		});
+		break;
+	case 3:
+		addr[addr_n++] = ((NMPlatformIP6Address) {
+			.ifindex = IFINDEX,
+			.address = *nmtst_inet6_from_string ("2001:db8:8086::5"),
+			.plen = 128,
+			.peer_address = in6addr_any,
+			.lifetime = NM_PLATFORM_LIFETIME_PERMANENT,
+			.preferred = NM_PLATFORM_LIFETIME_PERMANENT,
+			.n_ifa_flags = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 110,
+			.metric = 10021,
+			.mss = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::1"),
+			.metric = 21,
+			.mss = 0,
+		});
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	for (i = 0; i < addr_n; i++) {
+		g_assert (addr[i].ifindex == IFINDEX);
+		addr_in6[i] = addr[i].address;
+		g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET,
+		                                       IFINDEX,
+		                                       addr[i].address,
+		                                       addr[i].plen,
+		                                       addr[i].peer_address,
+		                                       addr[i].lifetime,
+		                                       addr[i].preferred,
+		                                       addr[i].n_ifa_flags));
+	}
+
+	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6);
+
+	for (i = 0; i < rts_n; i++)
+		g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
+
+	routes = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, IFINDEX);
+	switch (TEST_IDX) {
+	case 1:
+	case 2:
+	case 3:
+		for (i = 0; i < rts_n; i++) {
+			rts_cmp[i] = rts_add[i];
+			rts_cmp[i].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
+		}
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	g_assert_cmpint (routes->len, ==, rts_n);
+	nmtst_platform_ip6_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts_cmp, routes->len, TRUE);
+	g_ptr_array_unref (routes);
+
+	for (i = 0; i < rts_n; i++) {
+		g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, IFINDEX,
+		                                            rts_add[i].network, rts_add[i].plen,
+		                                            rts_add[i].metric));
+	}
+
+	for (i = 0; i < addr_n; i++) {
+		nmtstp_ip6_address_del (NM_PLATFORM_GET,
+		                        EX,
+		                        IFINDEX,
+		                        rts_add[i].network,
+		                        rts_add[i].plen);
+	}
+}
 
-	/* Remove route */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, 64, 1024));
+/*****************************************************************************/
 
-	g_array_unref (routes);
+static void
+test_ip (gconstpointer test_data)
+{
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const int IFINDEX = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	guint i, j, k;
+	const NMPlatformLink *l;
+	char ifname[IFNAMSIZ];
+	char ifname2[IFNAMSIZ];
+	char s1[NM_UTILS_INET_ADDRSTRLEN];
+	NMPlatform *platform = NM_PLATFORM_GET;
+	const int EX_ = -1;
+	struct {
+		int ifindex;
+	} iface_data[10] = { 0 };
+	int order_idx[G_N_ELEMENTS (iface_data)] = { 0 };
+	guint order_len;
+	guint try;
+
+	for (i = 0; i < G_N_ELEMENTS (iface_data); i++) {
+		nm_sprintf_buf (ifname, "v%02u", i);
+		nm_sprintf_buf (ifname2, "w%02u", i);
+
+		g_assert (!nm_platform_link_get_by_ifname (platform, ifname));
+		g_assert (!nm_platform_link_get_by_ifname (platform, ifname2));
+		l = nmtstp_link_veth_add (platform, EX_, ifname, ifname2);
+		iface_data[i].ifindex = l->ifindex;
+
+		nmtstp_link_set_updown (platform, EX_, iface_data[i].ifindex, TRUE);
+		nmtstp_link_set_updown (platform, EX_, nmtstp_link_get (platform, -1, ifname2)->ifindex, TRUE);
+
+		nm_sprintf_buf (s1, "192.168.7.%d", 100 + i);
+		nmtstp_ip4_address_add (platform,
+		                        EX_,
+		                        iface_data[i].ifindex,
+		                        nmtst_inet4_from_string (s1),
+		                        24,
+		                        nmtst_inet4_from_string (s1),
+		                        3600,
+		                        3600,
+		                        0,
+		                        NULL);
+	}
+
+	order_len = 0;
+	for (try = 0; try < 5 * G_N_ELEMENTS (order_idx); try++) {
+		NMPObject o;
+		NMPlatformIP4Route *r;
+		guint idx;
+		const NMDedupMultiHeadEntry *head_entry;
+		NMPLookup lookup;
+
+		nmp_object_stackinit (&o, NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
+		r = NMP_OBJECT_CAST_IP4_ROUTE (&o);
+		r->network = nmtst_inet4_from_string ("192.168.9.0");
+		r->plen = 24;
+		r->metric = 109;
+
+		if (   order_len == 0
+		    || (   order_len < G_N_ELEMENTS (order_idx)
+		        && nmtst_get_rand_int () % 2)) {
+again_find_idx:
+			idx = nmtst_get_rand_int () % G_N_ELEMENTS (iface_data);
+			for (i = 0; i < order_len; i++) {
+				if (order_idx[i] == idx)
+					goto again_find_idx;
+			}
+			order_idx[order_len++] = idx;
+
+			r->ifindex = iface_data[idx].ifindex;
+			g_assert (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r) == NM_PLATFORM_ERROR_SUCCESS);
+		} else {
+			i = nmtst_get_rand_int () % order_len;
+			idx = order_idx[i];
+			for (i++; i < order_len; i++)
+				order_idx[i - 1] = order_idx[i];
+			order_len--;
+
+			r->ifindex = iface_data[idx].ifindex;
+			g_assert (nm_platform_ip_route_delete (platform, &o));
+		}
+
+		head_entry = nm_platform_lookup (platform,
+		                                 nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_IP4_ROUTE));
+		for (j = 0; j < G_N_ELEMENTS (iface_data); j++) {
+			gboolean has;
+			NMDedupMultiIter iter;
+			const NMPObject *o_cached;
+
+			has = FALSE;
+			for (k = 0; k < order_len; k++) {
+				if (order_idx[k] == j) {
+					g_assert (!has);
+					has = TRUE;
+				}
+			}
+
+			nmp_cache_iter_for_each (&iter, head_entry, &o_cached) {
+				const NMPlatformIP4Route *r_cached = NMP_OBJECT_CAST_IP4_ROUTE (o_cached);
+
+				if (   r_cached->ifindex != iface_data[j].ifindex
+				    || r_cached->metric != 109)
+					continue;
+
+				g_assert (has);
+				has = FALSE;
+			}
+			g_assert (!has);
+		}
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (iface_data); i++)
+		g_assert (nm_platform_link_delete (platform, iface_data[i].ifindex));
+
+	(void) TEST_IDX;
+	(void) IFINDEX;
 }
 
 /*****************************************************************************/
@@ -494,22 +766,19 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_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_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_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
-	accept_signal (link_added);
-	free_signal (link_added);
-
-	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
-
-	g_test_add_func ("/route/ip4", test_ip4_route);
-	g_test_add_func ("/route/ip6", test_ip6_route);
-	g_test_add_func ("/route/ip4_metric0", test_ip4_route_metric0);
-	g_test_add_func ("/route/ip4_options", test_ip4_route_options);
-	g_test_add_func ("/route/ip6_options", test_ip6_route_options);
-
-	if (nmtstp_is_root_test ())
-		g_test_add_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
+#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, TRUE)
+#define add_test_func_data(testpath, test_func, arg) nmtstp_env1_add_test_func_data(testpath, test_func, arg, TRUE)
+	add_test_func ("/route/ip4", test_ip4_route);
+	add_test_func ("/route/ip6", test_ip6_route);
+	add_test_func ("/route/ip4_metric0", test_ip4_route_metric0);
+	add_test_func ("/route/ip4_options", test_ip4_route_options);
+	add_test_func_data ("/route/ip6_options/1", test_ip6_route_options, GINT_TO_POINTER (1));
+	add_test_func_data ("/route/ip6_options/2", test_ip6_route_options, GINT_TO_POINTER (2));
+	add_test_func_data ("/route/ip6_options/3", test_ip6_route_options, GINT_TO_POINTER (3));
+
+	if (nmtstp_is_root_test ()) {
+		add_test_func_data ("/route/ip/1", test_ip, GINT_TO_POINTER (1));
+		add_test_func ("/route/ip_route_get", test_ip_route_get);
+		add_test_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
+	}
 }