summary refs log tree commit diff
path: root/src/platform/tests
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
committerMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
commit494f296a3baab08522617b24b1f126d8f9a17502 (patch)
treec8ef32fb0dd1c4ff35a0b38e787abb58692de0cd /src/platform/tests
parent54f6333410ffd570e62717d9e77c5c987175e397 (diff)
Imported Upstream version 1.1.90 upstream/1.1.90
Diffstat (limited to 'src/platform/tests')
-rw-r--r--src/platform/tests/Makefile.am6
-rw-r--r--src/platform/tests/Makefile.in24
-rw-r--r--src/platform/tests/monitor.c44
-rw-r--r--src/platform/tests/test-address.c278
-rw-r--r--src/platform/tests/test-cleanup.c6
-rw-r--r--src/platform/tests/test-common.c1222
-rw-r--r--src/platform/tests/test-common.h131
-rw-r--r--src/platform/tests/test-general.c3
-rw-r--r--src/platform/tests/test-link.c1351
-rw-r--r--src/platform/tests/test-nmp-object.c6
-rw-r--r--src/platform/tests/test-route.c38
11 files changed, 2935 insertions, 174 deletions
diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am
index 085f56ca..b097fcf6 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -1,14 +1,14 @@
 AM_CPPFLAGS = \
 	-I${top_srcdir} \
-	-I${top_srcdir}/include \
-	-I${top_builddir}/include \
+	-I${top_srcdir}/shared \
+	-I${top_builddir}/shared \
 	-I${top_srcdir}/src \
 	-I${top_builddir}/src \
 	-I${top_srcdir}/libnm-core \
 	-I${top_builddir}/libnm-core \
 	-I${srcdir}/.. \
 	-DG_LOG_DOMAIN=\""NetworkManager"\" \
-	-DNETWORKMANAGER_COMPILATION \
+	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
 	$(GLIB_CFLAGS) \
 	$(GUDEV_CFLAGS) \
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index e21a3bb6..c04d6a1b 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -503,7 +503,6 @@ BLUEZ5_LIBS = @BLUEZ5_LIBS@
 CC = @CC@
 CCDEPMODE = @CCDEPMODE@
 CFLAGS = @CFLAGS@
-CKDB_PATH = @CKDB_PATH@
 CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
 CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
 CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
@@ -515,8 +514,6 @@ CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
 DBUS_CFLAGS = @DBUS_CFLAGS@
-DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
-DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
 DBUS_LIBS = @DBUS_LIBS@
 DBUS_SYS_DIR = @DBUS_SYS_DIR@
 DEFS = @DEFS@
@@ -526,6 +523,7 @@ DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
 DNSMASQ_PATH = @DNSMASQ_PATH@
+DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
 DUMPBIN = @DUMPBIN@
 ECHO_C = @ECHO_C@
@@ -580,16 +578,13 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
-IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@
-IWMX_SDK_LIBS = @IWMX_SDK_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
+LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
+LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
 LIBDL = @LIBDL@
-LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
-LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
-LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -626,6 +621,8 @@ NEWT_LIBS = @NEWT_LIBS@
 NM = @NM@
 NMEDIT = @NMEDIT@
 NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT = @NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT@
+NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT = @NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT@
+NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT = @NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT@
 NM_MAJOR_VERSION = @NM_MAJOR_VERSION@
 NM_MICRO_VERSION = @NM_MICRO_VERSION@
 NM_MINOR_VERSION = @NM_MINOR_VERSION@
@@ -654,7 +651,6 @@ POLKIT_LIBS = @POLKIT_LIBS@
 POSUB = @POSUB@
 PPPD_PATH = @PPPD_PATH@
 PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@
-PPPOE_PATH = @PPPOE_PATH@
 QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
@@ -669,6 +665,8 @@ SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@
 SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@
 SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@
 SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
 SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@
 SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@
 SYSTEM_CA_PATH = @SYSTEM_CA_PATH@
@@ -729,6 +727,7 @@ mkdir_p = @mkdir_p@
 nmbinary = @nmbinary@
 nmconfdir = @nmconfdir@
 nmdatadir = @nmdatadir@
+nmlibdir = @nmlibdir@
 nmrundir = @nmrundir@
 nmstatedir = @nmstatedir@
 oldincludedir = @oldincludedir@
@@ -736,6 +735,7 @@ pdfdir = @pdfdir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
@@ -751,12 +751,12 @@ with_dhcpcd = @with_dhcpcd@
 with_netconfig = @with_netconfig@
 with_resolvconf = @with_resolvconf@
 with_valgrind = @with_valgrind@
-AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/include \
-	-I${top_builddir}/include -I${top_srcdir}/src \
+AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/shared \
+	-I${top_builddir}/shared -I${top_srcdir}/src \
 	-I${top_builddir}/src -I${top_srcdir}/libnm-core \
 	-I${top_builddir}/libnm-core -I${srcdir}/.. \
 	-DG_LOG_DOMAIN=\""NetworkManager"\" \
-	-DNETWORKMANAGER_COMPILATION \
+	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE $(GLIB_CFLAGS) \
 	$(GUDEV_CFLAGS) $(LIBNL_CFLAGS) $(am__append_1)
 PLATFORM_SOURCES = \
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index f1058db8..5cc3114c 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -3,34 +3,66 @@
 #include <stdlib.h>
 #include <syslog.h>
 
+#include "nm-default.h"
 #include "nm-linux-platform.h"
-#include "nm-logging.h"
 
 #include "nm-test-utils.h"
 
 NMTST_DEFINE ();
 
+static struct {
+	gboolean persist;
+} global_opt = {
+	.persist = TRUE,
+};
+
+static gboolean
+read_argv (int *argc, char ***argv)
+{
+	GOptionContext *context;
+	GOptionEntry options[] = {
+		{ "no-persist", 'P', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &global_opt.persist, "Exit after processing netlink messages", NULL },
+		{ 0 },
+	};
+	gs_free_error GError *error = NULL;
+
+	context = g_option_context_new (NULL);
+	g_option_context_set_summary (context, "Monitor netlink events in NMPlatform.");
+	g_option_context_add_main_entries (context, options, NULL);
+
+	if (!g_option_context_parse (context, argc, argv, &error)) {
+		g_warning ("Error parsing command line arguments: %s", error->message);
+		g_option_context_free (context);
+		return FALSE;
+	}
+
+	g_option_context_free (context);
+	return TRUE;
+}
+
 int
 main (int argc, char **argv)
 {
 	GMainLoop *loop;
 
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
 	if (!g_getenv ("G_MESSAGES_DEBUG"))
 		g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
 
 	nmtst_init_with_logging (&argc, &argv, "DEBUG", "ALL");
 
+	if (!read_argv (&argc, &argv))
+		return 2;
+
 	nm_log_info (LOGD_PLATFORM, "platform monitor start");
 
 	loop = g_main_loop_new (NULL, FALSE);
 
 	nm_linux_platform_setup ();
 
-	g_main_loop_run (loop);
+	if (global_opt.persist)
+		g_main_loop_run (loop);
+
+	g_main_loop_unref (loop);
 
 	return EXIT_SUCCESS;
 }
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index d6f346b3..566bb479 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -4,12 +4,19 @@
 
 #define DEVICE_NAME "nm-test-device"
 #define IP4_ADDRESS "192.0.2.1"
+#define IP4_ADDRESS_PEER "192.0.2.2"
+#define IP4_ADDRESS_PEER2 "192.0.3.1"
 #define IP4_PLEN 24
 #define IP6_ADDRESS "2001:db8:a:b:1:2:3:4"
 #define IP6_PLEN 64
 
+static int DEVICE_IFINDEX = -1;
+static int EX = -1;
+
+/*****************************************************************************/
+
 static void
-ip4_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip4_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -25,11 +32,11 @@ ip4_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex,
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
-ip6_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip6_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -45,13 +52,15 @@ ip6_address_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex,
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
+/*****************************************************************************/
+
 static void
-test_ip4_address (void)
+test_ip4_address_general (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_address_callback, ifindex);
 	SignalData *address_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip4_address_callback, ifindex);
 	SignalData *address_removed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_address_callback, ifindex);
@@ -64,13 +73,13 @@ test_ip4_address (void)
 	inet_pton (AF_INET, IP4_ADDRESS, &addr);
 
 	/* Add address */
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime + 100, preferred + 50, NULL);
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
@@ -80,16 +89,17 @@ test_ip4_address (void)
 	address = &g_array_index (addresses, NMPlatformIP4Address, 0);
 	g_assert_cmpint (address->ifindex, ==, ifindex);
 	g_assert_cmphex (address->address, ==, addr);
+	g_assert_cmphex (address->peer_address, ==, addr);
 	g_assert_cmpint (address->plen, ==, IP4_PLEN);
 	g_array_unref (addresses);
 
 	/* Remove address */
-	g_assert (nm_platform_ip4_address_delete (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, 0));
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_removed);
 
 	/* Remove address again */
-	g_assert (nm_platform_ip4_address_delete (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, 0));
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
 
 	free_signal (address_added);
 	free_signal (address_changed);
@@ -97,9 +107,9 @@ test_ip4_address (void)
 }
 
 static void
-test_ip6_address (void)
+test_ip6_address_general (void)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip6_address_callback, ifindex);
 	SignalData *address_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip6_address_callback, ifindex);
 	SignalData *address_removed = add_signal_ifindex (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip6_address_callback, ifindex);
@@ -114,12 +124,12 @@ test_ip6_address (void)
 
 	/* Add address */
 	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
 	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
@@ -133,12 +143,15 @@ test_ip6_address (void)
 	g_array_unref (addresses);
 
 	/* Remove address */
-	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	nmtstp_ip6_address_del (EX, ifindex, addr, IP6_PLEN);
 	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 	accept_signal (address_removed);
 
 	/* Remove address again */
-	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	nmtstp_ip6_address_del (EX, ifindex, addr, IP6_PLEN);
+
+	/* ensure not pending signal. */
+	accept_signals (address_changed, 0, 1);
 
 	free_signal (address_added);
 	free_signal (address_changed);
@@ -146,11 +159,11 @@ test_ip6_address (void)
 }
 
 static void
-test_ip4_address_external (void)
+test_ip4_address_general_2 (void)
 {
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_address_callback);
 	SignalData *address_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_address_callback);
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	in_addr_t addr;
 	guint32 lifetime = 2000;
 	guint32 preferred = 1000;
@@ -161,38 +174,31 @@ test_ip4_address_external (void)
 	/* Looks like addresses are not announced by kerenl when the interface
 	 * is down. Link-local IPv6 address is automatically added.
 	 */
-	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, DEVICE_IFINDEX, NULL));
 
 	/* Add/delete notification */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP4_ADDRESS, IP4_PLEN, DEVICE_NAME, lifetime, preferred);
-	wait_signal (address_added);
-	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
-	run_command ("ip address delete %s/%d dev %s", IP4_ADDRESS, IP4_PLEN, DEVICE_NAME);
-	wait_signal (address_removed);
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	accept_signal (address_added);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
+	accept_signal (address_removed);
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 
 	/* Add/delete conflict */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP4_ADDRESS, IP4_PLEN, DEVICE_NAME, lifetime, preferred);
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr, 0, IP4_PLEN, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_added);
-	/*run_command ("ip address delete %s/%d dev %s", IP4_ADDRESS, IP4_PLEN, DEVICE_NAME);
-	g_assert (nm_platform_ip4_address_delete (ifindex, addr, IP4_PLEN, 0));
-	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN));
-	accept_signal (address_removed);*/
 
 	free_signal (address_added);
 	free_signal (address_removed);
 }
 
 static void
-test_ip6_address_external (void)
+test_ip6_address_general_2 (void)
 {
+	const int ifindex = DEVICE_IFINDEX;
 	SignalData *address_added = add_signal (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip6_address_callback);
 	SignalData *address_removed = add_signal (NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip6_address_callback);
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	struct in6_addr addr;
 	guint32 lifetime = 2000;
 	guint32 preferred = 1000;
@@ -201,51 +207,197 @@ test_ip6_address_external (void)
 	inet_pton (AF_INET6, IP6_ADDRESS, &addr);
 
 	/* Add/delete notification */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP6_ADDRESS, IP6_PLEN, DEVICE_NAME, lifetime, preferred);
-	wait_signal (address_added);
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
+	accept_signal (address_added);
 	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	run_command ("ip address delete %s/%d dev %s", IP6_ADDRESS, IP6_PLEN, DEVICE_NAME);
-	wait_signal (address_removed);
+
+	nmtstp_ip6_address_del (EX, ifindex, addr, IP6_PLEN);
+	accept_signal (address_removed);
 	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
 
 	/* Add/delete conflict */
-	run_command ("ip address add %s/%d dev %s valid_lft %d preferred_lft %d",
-			IP6_ADDRESS, IP6_PLEN, DEVICE_NAME, lifetime, preferred);
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr, in6addr_any, IP6_PLEN, lifetime, preferred, flags));
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
+	accept_signal (address_added);
 	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+
+	nmtstp_ip6_address_add (EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
+	ensure_no_signal (address_added);
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+
+	free_signal (address_added);
+	free_signal (address_removed);
+}
+
+/*****************************************************************************/
+
+static void
+test_ip4_address_peer (void)
+{
+	const int ifindex = DEVICE_IFINDEX;
+	SignalData *address_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_address_callback);
+	SignalData *address_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_address_callback);
+	in_addr_t addr, addr_peer, addr_peer2;
+	guint32 lifetime = 2000;
+	guint32 preferred = 1000;
+	const NMPlatformIP4Address *a;
+
+	inet_pton (AF_INET, IP4_ADDRESS, &addr);
+	inet_pton (AF_INET, IP4_ADDRESS_PEER, &addr_peer);
+	inet_pton (AF_INET, IP4_ADDRESS_PEER2, &addr_peer2);
+	g_assert (ifindex > 0);
+
+	g_assert (addr != addr_peer);
+
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+	accept_signals (address_removed, 0, G_MAXINT);
+	accept_signals (address_added, 0, G_MAXINT);
+
+	/* Add/delete notification */
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer, lifetime, preferred, NULL);
 	accept_signal (address_added);
-	/*run_command ("ip address delete %s/%d dev %s", IP6_ADDRESS, IP6_PLEN, DEVICE_NAME);
-	g_assert (nm_platform_ip6_address_delete (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
-	wait_signal (address_removed);*/
+	g_assert ((a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer)));
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2));
+
+	nmtstp_ip_address_assert_lifetime ((NMPlatformIPAddress *) a, -1, lifetime, preferred);
+
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer2, lifetime, preferred, NULL);
+	accept_signal (address_added);
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer));
+	g_assert ((a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2)));
+
+	nmtstp_ip_address_assert_lifetime ((NMPlatformIPAddress *) a, -1, lifetime, preferred);
+
+	g_assert (addr != addr_peer);
+	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr_peer);
+	accept_signal (address_removed);
+	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer));
+	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2));
 
 	free_signal (address_added);
 	free_signal (address_removed);
 }
 
+/*****************************************************************************/
+
+static void
+test_ip4_address_peer_zero (void)
+{
+	const int ifindex = DEVICE_IFINDEX;
+	in_addr_t addr, addr_peer;
+	guint32 lifetime = 2000;
+	guint32 preferred = 1000;
+	const int plen = 24;
+	const char *label = NULL;
+	in_addr_t peers[3], r_peers[3];
+	int i;
+	GArray *addrs;
+
+	g_assert (ifindex > 0);
+
+	inet_pton (AF_INET, "192.168.5.2", &addr);
+	inet_pton (AF_INET, "192.168.6.2", &addr_peer);
+	peers[0] = addr;
+	peers[1] = addr_peer;
+	peers[2] = 0;
+
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+
+	nmtst_rand_perm (NULL, r_peers, peers, sizeof (peers[0]), G_N_ELEMENTS (peers));
+	for (i = 0; i < G_N_ELEMENTS (peers); i++) {
+		g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i]));
+
+		nmtstp_ip4_address_add (EX, ifindex, addr, plen, r_peers[i], lifetime, preferred, label);
+
+		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		g_assert (addrs);
+		g_assert_cmpint (addrs->len, ==, i + 1);
+		g_array_unref (addrs);
+	}
+
+	if (nmtst_is_debug () && nmtstp_is_root_test ())
+		nmtstp_run_command_check ("ip address show dev %s", DEVICE_NAME);
+
+	nmtst_rand_perm (NULL, r_peers, peers, sizeof (peers[0]), G_N_ELEMENTS (peers));
+	for (i = 0; i < G_N_ELEMENTS (peers); i++) {
+		g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i]));
+
+		nmtstp_ip4_address_del (EX, ifindex, addr, plen, r_peers[i]);
+
+		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		g_assert (addrs);
+		g_assert_cmpint (addrs->len, ==, G_N_ELEMENTS (peers) - i - 1);
+		g_array_unref (addrs);
+	}
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
 	nmtst_init_with_logging (argc, argv, NULL, "ALL");
 }
 
-void
-setup_tests (void)
+/*****************************************************************************
+ * SETUP TESTS
+ *****************************************************************************/
+
+typedef struct {
+	const char *testpath;
+	GTestFunc test_func;
+} TestSetup;
+
+static void
+_g_test_run (gconstpointer user_data)
 {
-	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
+	const TestSetup *s = user_data;
+	int ifindex;
+
+	_LOGT ("TEST: start %s", s->testpath);
 
 	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
-	accept_signal (link_added);
-	free_signal (link_added);
+	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
 
-	g_test_add_func ("/address/internal/ip4", test_ip4_address);
-	g_test_add_func ("/address/internal/ip6", test_ip6_address);
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (ifindex, >, 0);
+	g_assert_cmpint (DEVICE_IFINDEX, ==, -1);
 
-	if (strcmp (g_type_name (G_TYPE_FROM_INSTANCE (nm_platform_get ())), "NMFakePlatform")) {
-		g_test_add_func ("/address/external/ip4", test_ip4_address_external);
-		g_test_add_func ("/address/external/ip6", test_ip6_address_external);
-	}
+	DEVICE_IFINDEX = ifindex;
+	EX = nmtstp_run_command_check_external_global ();
+
+	s->test_func ();
+
+	g_assert_cmpint (DEVICE_IFINDEX, ==, ifindex);
+	DEVICE_IFINDEX = -1;
+
+	g_assert_cmpint (ifindex, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	_LOGT ("TEST: finished %s", s->testpath);
+}
+
+static void
+_g_test_add_func (const char *testpath,
+                  GTestFunc   test_func)
+{
+	TestSetup *s;
+
+	s = g_new0 (TestSetup, 1);
+	s->testpath = testpath;
+	s->test_func = test_func;
+
+	g_test_add_data_func_full (testpath, s, _g_test_run, g_free);
+}
+
+void
+setup_tests (void)
+{
+	_g_test_add_func ("/address/ipv4/general", test_ip4_address_general);
+	_g_test_add_func ("/address/ipv6/general", test_ip6_address_general);
+
+	_g_test_add_func ("/address/ipv4/general-2", test_ip4_address_general_2);
+	_g_test_add_func ("/address/ipv6/general-2", test_ip6_address_general_2);
+
+	_g_test_add_func ("/address/ipv4/peer", test_ip4_address_peer);
+	_g_test_add_func ("/address/ipv4/peer/zero", test_ip4_address_peer_zero);
 }
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 0b825114..9e624a02 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -35,7 +35,7 @@ test_cleanup_internal (void)
 	inet_pton (AF_INET6, "2001:db8:e:f:1:2:3:4", &gateway6);
 
 	/* Create and set up device */
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
 	accept_signal (link_added);
 	free_signal (link_added);
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
@@ -43,8 +43,8 @@ test_cleanup_internal (void)
 	g_assert (ifindex > 0);
 
 	/* Add routes and addresses */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, 0, plen4, lifetime, preferred, NULL));
-	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, in6addr_any, plen6, lifetime, preferred, flags));
+	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, lifetime, preferred, NULL));
+	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, plen6, in6addr_any, lifetime, preferred, flags));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway4, 32, INADDR_ANY, 0, metric, mss));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network4, plen4, gateway4, 0, metric, mss));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway4, 0, metric, mss));
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index b0eae63b..09a57f87 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -2,6 +2,8 @@
 
 #include <sys/mount.h>
 #include <sched.h>
+#include <sys/wait.h>
+#include <fcntl.h>
 
 #include "test-common.h"
 
@@ -10,9 +12,8 @@
 #define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)"
 #define SIGNAL_DATA_ARG(data) (data)->name, nm_platform_signal_change_type_to_string ((data)->change_type), (data)->ifindex, (data)->ifname ? " ifname '" : "", (data)->ifname ? (data)->ifname : "", (data)->ifname ? "'" : "", (data)->received_count
 
-
 gboolean
-nmtst_platform_is_root_test (void)
+nmtstp_is_root_test (void)
 {
 	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
 	return (SETUP == nm_linux_platform_setup);
@@ -20,9 +21,9 @@ nmtst_platform_is_root_test (void)
 }
 
 gboolean
-nmtst_platform_is_sysfs_writable (void)
+nmtstp_is_sysfs_writable (void)
 {
-	return    !nmtst_platform_is_root_test ()
+	return    !nmtstp_is_root_test ()
 	       || (access ("/sys/devices", W_OK) == 0);
 }
 
@@ -34,7 +35,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall
 	data->name = name;
 	data->change_type = change_type;
 	data->received_count = 0;
-	data->handler_id = g_signal_connect (nm_platform_get (), name, callback, data);
+	data->handler_id = g_signal_connect (NM_PLATFORM_GET, name, callback, data);
 	data->ifindex = ifindex;
 	data->ifname = ifname;
 
@@ -46,7 +47,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall
 void
 _accept_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count != 1)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	data->received_count = 0;
@@ -55,7 +56,7 @@ _accept_signal (const char *file, int line, const char *func, SignalData *data)
 void
 _accept_signals (const char *file, int line, const char *func, SignalData *data, int min, int max)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data));
 	if (data->received_count < min || data->received_count > max)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data));
 	data->received_count = 0;
@@ -64,15 +65,28 @@ _accept_signals (const char *file, int line, const char *func, SignalData *data,
 void
 _ensure_no_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count > 0)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 }
 
 void
+_accept_or_wait_signal (const char *file, int line, const char *func, SignalData *data)
+{
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): accept-or-wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	if (data->received_count == 0) {
+		data->loop = g_main_loop_new (NULL, FALSE);
+		g_main_loop_run (data->loop);
+		g_clear_pointer (&data->loop, g_main_loop_unref);
+	}
+
+	_accept_signal (file, line, func, data);
+}
+
+void
 _wait_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to wait for signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 
@@ -86,16 +100,16 @@ _wait_signal (const char *file, int line, const char *func, SignalData *data)
 void
 _free_signal (const char *file, int line, const char *func, SignalData *data)
 {
-	debug ("NMPlatformSignalAssert: %s:%d, %s(): free signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
+	_LOGD ("NMPlatformSignalAssert: %s:%d, %s(): free signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 	if (data->received_count != 0)
 		g_error ("NMPlatformSignalAssert: %s:%d, %s(): failure to free non-accepted signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (data));
 
-	g_signal_handler_disconnect (nm_platform_get (), data->handler_id);
+	g_signal_handler_disconnect (NM_PLATFORM_GET, data->handler_id);
 	g_free (data);
 }
 
 void
-link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	GArray *links;
 	NMPlatformLink *cached;
@@ -114,12 +128,12 @@ link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlat
 		return;
 
 	if (data->loop) {
-		debug ("Quitting main loop.");
+		_LOGD ("Quitting main loop.");
 		g_main_loop_quit (data->loop);
 	}
 
 	data->received_count++;
-	debug ("Received signal '%s-%s' ifindex %d ifname '%s' %dth time.", data->name, nm_platform_signal_change_type_to_string (data->change_type), ifindex, received->name, data->received_count);
+	_LOGD ("Received signal '%s-%s' ifindex %d ifname '%s' %dth time.", data->name, nm_platform_signal_change_type_to_string (data->change_type), ifindex, received->name, data->received_count);
 
 	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
 		g_assert (!nm_platform_link_get_name (NM_PLATFORM_GET, ifindex));
@@ -253,21 +267,1176 @@ _assert_ip4_route_exists (const char *file, guint line, const char *func, gboole
 	}
 }
 
-void
-run_command (const char *format, ...)
+int
+nmtstp_run_command (const char *format, ...)
 {
-	char *command;
+	int result;
+	gs_free char *command = NULL;
 	va_list ap;
 
 	va_start (ap, format);
 	command = g_strdup_vprintf (format, ap);
 	va_end (ap);
-	debug ("Running command: %s", command);
-	g_assert (!system (command));
-	debug ("Command finished.");
-	g_free (command);
+
+	_LOGD ("Running command: %s", command);
+	result = system (command);
+	_LOGD ("Command finished: result=%d", result);
+
+	return result;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	GMainLoop *loop;
+	gboolean timeout;
+	guint id;
+} WaitForSignalData;
+
+static void
+_wait_for_signal_cb (NMPlatform *platform,
+                     NMPObjectType obj_type,
+                     int ifindex,
+                     NMPlatformLink *plink,
+                     NMPlatformSignalChangeType change_type,
+                     gpointer user_data)
+{
+	WaitForSignalData *data = user_data;
+
+	g_main_loop_quit (data->loop);
+}
+
+static gboolean
+_wait_for_signal_timeout (gpointer user_data)
+{
+	WaitForSignalData *data = user_data;
+
+	data->timeout = TRUE;
+	data->id = 0;
+	g_main_loop_quit (data->loop);
+	return G_SOURCE_REMOVE;
+}
+
+gboolean
+nmtstp_wait_for_signal (guint timeout_ms)
+{
+	WaitForSignalData data = { 0 };
+
+	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
+
+	data.loop = g_main_loop_new (NULL, FALSE);
+
+	id_link        = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip4_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip6_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip4_route   = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip6_route   = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+
+	if (timeout_ms != 0)
+		data.id = g_timeout_add (timeout_ms, _wait_for_signal_timeout, &data);
+
+	g_main_loop_run (data.loop);
+
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_link));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_address));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_address));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_route));
+	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_route));
+
+	if (nm_clear_g_source (&data.id))
+		g_assert (timeout_ms != 0 && !data.timeout);
+
+	g_clear_pointer (&data.loop, g_main_loop_unref);
+
+	return !data.timeout;
+}
+
+gboolean
+nmtstp_wait_for_signal_until (gint64 until_ms)
+{
+	gint64 now;
+
+	while (TRUE) {
+		now = nm_utils_get_monotonic_timestamp_ms ();
+
+		if (until_ms < now)
+			return FALSE;
+
+		if (nmtstp_wait_for_signal (MAX (1, until_ms - now)))
+			return TRUE;
+	}
+}
+
+const NMPlatformLink *
+nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+{
+	return nmtstp_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+}
+
+const NMPlatformLink *
+nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+{
+	const NMPlatformLink *plink;
+	gint64 now;
+
+	while (TRUE) {
+		now = nm_utils_get_monotonic_timestamp_ms ();
+
+		plink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname);
+		if (   plink
+		    && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type))
+			return plink;
+
+		if (until_ms < now)
+			return NULL;
+
+		nmtstp_wait_for_signal (MAX (1, until_ms - now));
+	}
+}
+
+const NMPlatformLink *
+nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+{
+	return nmtstp_assert_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+}
+
+const NMPlatformLink *
+nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+{
+	const NMPlatformLink *plink;
+
+	plink = nmtstp_wait_for_link_until (ifname, expected_link_type, until_ms);
+	g_assert (plink);
+	return plink;
+}
+
+int
+nmtstp_run_command_check_external_global (void)
+{
+	if (!nmtstp_is_root_test ())
+		return FALSE;
+	switch (nmtst_get_rand_int () % 3) {
+	case 0:
+		return -1;
+	case 1:
+		return FALSE;
+	default:
+		return TRUE;
+	}
+}
+
+gboolean
+nmtstp_run_command_check_external (int external_command)
+{
+	if (external_command != -1) {
+		g_assert (NM_IN_SET (external_command, FALSE, TRUE));
+		g_assert (!external_command || nmtstp_is_root_test ());
+		return !!external_command;
+	}
+	if (!nmtstp_is_root_test ())
+		return FALSE;
+	return (nmtst_get_rand_int () % 2) == 0;
+}
+
+#define CHECK_LIFETIME_MAX_DIFF    2
+
+gboolean
+nmtstp_ip_address_check_lifetime (const NMPlatformIPAddress *addr,
+                                  gint64 now,
+                                  guint32 expected_lifetime,
+                                  guint32 expected_preferred)
+{
+	gint64 offset;
+	int i;
+
+	g_assert (addr);
+
+	if (now == -1)
+		now = nm_utils_get_monotonic_timestamp_s ();
+	g_assert (now > 0);
+
+	g_assert (expected_preferred <= expected_lifetime);
+
+	if (   expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    && expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT) {
+		return    addr->timestamp == 0
+		       && addr->lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+		       && addr->preferred == NM_PLATFORM_LIFETIME_PERMANENT;
+	}
+
+	if (addr->timestamp == 0)
+		return FALSE;
+
+	offset = (gint64) now - addr->timestamp;
+
+	for (i = 0; i < 2; i++) {
+		guint32 lft = i ? expected_lifetime : expected_preferred;
+		guint32 adr = i ? addr->lifetime : addr->preferred;
+
+		if (lft == NM_PLATFORM_LIFETIME_PERMANENT) {
+			if (adr != NM_PLATFORM_LIFETIME_PERMANENT)
+				return FALSE;
+		} else {
+			if (   adr - offset <= lft - CHECK_LIFETIME_MAX_DIFF
+			    || adr - offset >= lft + CHECK_LIFETIME_MAX_DIFF)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void
+nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
+                                   gint64 now,
+                                   guint32 expected_lifetime,
+                                   guint32 expected_preferred)
+{
+	gint64 n = now;
+	gint64 offset;
+	int i;
+
+	g_assert (addr);
+
+	if (now == -1)
+		now = nm_utils_get_monotonic_timestamp_s ();
+	g_assert (now > 0);
+
+	g_assert (expected_preferred <= expected_lifetime);
+
+	if (   expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    && expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT) {
+		g_assert_cmpint (addr->timestamp, ==, 0);
+		g_assert_cmpint (addr->lifetime, ==, NM_PLATFORM_LIFETIME_PERMANENT);
+		g_assert_cmpint (addr->preferred, ==, NM_PLATFORM_LIFETIME_PERMANENT);
+		return;
+	}
+
+	g_assert_cmpint (addr->timestamp, >, 0);
+	g_assert_cmpint (addr->timestamp, <=, now);
+
+	offset = (gint64) now - addr->timestamp;
+	g_assert_cmpint (offset, >=, 0);
+
+	for (i = 0; i < 2; i++) {
+		guint32 lft = i ? expected_lifetime : expected_preferred;
+		guint32 adr = i ? addr->lifetime : addr->preferred;
+
+		if (lft == NM_PLATFORM_LIFETIME_PERMANENT)
+			g_assert_cmpint (adr, ==, NM_PLATFORM_LIFETIME_PERMANENT);
+		else {
+			g_assert_cmpint (adr, <=, lft);
+			g_assert_cmpint (offset, <=, adr);
+			g_assert_cmpint (adr - offset, <=, lft + CHECK_LIFETIME_MAX_DIFF);
+			g_assert_cmpint (adr - offset, >=, lft - CHECK_LIFETIME_MAX_DIFF);
+		}
+	}
+
+	g_assert (nmtstp_ip_address_check_lifetime (addr, n, expected_lifetime, expected_preferred));
+}
+
+static void
+_ip_address_add (gboolean external_command,
+                 gboolean is_v4,
+                 int ifindex,
+                 const NMIPAddr *address,
+                 int plen,
+                 const NMIPAddr *peer_address,
+                 guint32 lifetime,
+                 guint32 preferred,
+                 const char *label,
+                 guint flags)
+{
+	gint64 end_time;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *ifname;
+		gs_free char *s_valid = NULL;
+		gs_free char *s_preferred = NULL;
+		gs_free char *s_label = NULL;
+		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		g_assert (ifname);
+
+		if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT)
+			s_valid = g_strdup_printf (" valid_lft %d", lifetime);
+		if (preferred != NM_PLATFORM_LIFETIME_PERMANENT)
+			s_preferred = g_strdup_printf (" preferred_lft %d", preferred);
+		if (label)
+			s_label = g_strdup_printf ("%s:%s", ifname, label);
+
+		if (is_v4) {
+			char s_peer[100];
+
+			g_assert (flags == 0);
+
+			if (   peer_address->addr4 != address->addr4
+			    || nmtst_get_rand_int () % 2) {
+				/* If the peer is the same as the local address, we can omit it. The result should be identical */
+				g_snprintf (s_peer, sizeof (s_peer), " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
+			} else
+				s_peer[0] = '\0';
+
+			nmtstp_run_command_check ("ip address change %s%s/%d dev %s%s%s%s",
+			                          nm_utils_inet4_ntop (address->addr4, b1),
+			                          s_peer,
+			                          plen,
+			                          ifname,
+			                          s_valid ?: "",
+			                          s_preferred ?: "",
+			                          s_label ?: "");
+		} else {
+			g_assert (label == NULL);
+
+			/* flags not implemented (yet) */
+			g_assert (flags == 0);
+			nmtstp_run_command_check ("ip address change %s%s%s/%d dev %s%s%s%s",
+			                          nm_utils_inet6_ntop (&address->addr6, b1),
+			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? " peer " : "",
+			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? nm_utils_inet6_ntop (&peer_address->addr6, b2) : "",
+			                          plen,
+			                          ifname,
+			                          s_valid ?: "",
+			                          s_preferred ?: "",
+			                          s_label ?: "");
+		}
+	} else {
+		gboolean success;
+
+		if (is_v4) {
+			g_assert (flags == 0);
+			success = nm_platform_ip4_address_add (NM_PLATFORM_GET,
+			                                       ifindex,
+			                                       address->addr4,
+			                                       plen,
+			                                       peer_address->addr4,
+			                                       lifetime,
+			                                       preferred,
+			                                       label);
+		} else {
+			g_assert (label == NULL);
+			success = nm_platform_ip6_address_add (NM_PLATFORM_GET,
+			                                       ifindex,
+			                                       address->addr6,
+			                                       plen,
+			                                       peer_address->addr6,
+			                                       lifetime,
+			                                       preferred,
+			                                       flags);
+		}
+		g_assert (success);
+	}
+
+	/* Let's wait until we see the address. */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		/* let's wait until we see the address as we added it. */
+		if (is_v4) {
+			const NMPlatformIP4Address *a;
+
+			g_assert (flags == 0);
+			a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4);
+			if (   a
+			    && a->peer_address == peer_address->addr4
+			    && nmtstp_ip_address_check_lifetime ((NMPlatformIPAddress*) a, -1, lifetime, preferred)
+			    && strcmp (a->label, label ?: "") == 0)
+				break;
+		} else {
+			const NMPlatformIP6Address *a;
+
+			g_assert (label == NULL);
+			g_assert (flags == 0);
+
+			a = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen);
+			if (   a
+			    && !memcmp (nm_platform_ip6_address_get_peer (a),
+			                (IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) || IN6_ARE_ADDR_EQUAL (&address->addr6, &peer_address->addr6))
+			                    ? &address->addr6 : &peer_address->addr6,
+			                sizeof (struct in6_addr))
+			    && nmtstp_ip_address_check_lifetime ((NMPlatformIPAddress*) a, -1, lifetime, preferred))
+				break;
+		}
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
+}
+
+#define _assert_pllink(success, pllink, name, type) \
+	G_STMT_START { \
+		const NMPlatformLink *_pllink = (pllink); \
+		\
+		if ((success)) { \
+			g_assert (_pllink); \
+			g_assert (_pllink == nmtstp_link_get_typed (_pllink->ifindex, (name), (type))); \
+		} else { \
+			g_assert (!_pllink); \
+			g_assert (!nmtstp_link_get (0, (name))); \
+		} \
+	} G_STMT_END
+
+const NMPlatformLink *
+nmtstp_link_dummy_add (gboolean external_command,
+                       const char *name)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		success = !nmtstp_run_command ("ip link add %s type dummy",
+		                                name);
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_DUMMY, 100);
+	} else
+		success = nm_platform_link_dummy_add (NM_PLATFORM_GET, name, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	g_assert (success);
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_DUMMY);
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_gre_add (gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkGre *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		success = !nmtstp_run_command ("ip tunnel add %s mode gre %s local %s remote %s ttl %u tos %02x %s",
+		                                name,
+		                                dev ? dev : "",
+		                                nm_utils_inet4_ntop (lnk->local, NULL),
+		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                lnk->ttl,
+		                                lnk->tos,
+		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_GRE, 100);
+	} else
+		success = nm_platform_link_gre_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_GRE);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_ip6tnl_add (gboolean external_command,
+                        const char *name,
+                        const NMPlatformLnkIp6Tnl *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET6_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+		const char *mode;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		switch (lnk->proto) {
+		case IPPROTO_IPIP:
+			mode = "ipip6";
+			break;
+		case IPPROTO_IPV6:
+			mode = "ip6ip6";
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+
+		success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %02x encaplimit %u flowlabel %x",
+		                                name,
+		                                mode,
+		                                dev,
+		                                nm_utils_inet6_ntop (&lnk->local, NULL),
+		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                lnk->ttl,
+		                                lnk->tclass,
+		                                lnk->encap_limit,
+		                                lnk->flow_label);
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IP6TNL, 100);
+	} else
+		success = nm_platform_link_ip6tnl_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_IP6TNL);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_ipip_add (gboolean external_command,
+                      const char *name,
+                      const NMPlatformLnkIpIp *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		success = !nmtstp_run_command ("ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s",
+		                                name,
+		                                dev,
+		                                nm_utils_inet4_ntop (lnk->local, NULL),
+		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                lnk->ttl,
+		                                lnk->tos,
+		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IPIP, 100);
+	} else
+		success = nm_platform_link_ipip_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_IPIP);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_macvlan_add (gboolean external_command,
+                         const char *name,
+                         int parent,
+                         const NMPlatformLnkMacvlan *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	NMLinkType link_type;
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	link_type = lnk->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
+
+	if (external_command) {
+		const char *dev;
+		char *modes[] = {
+				[MACVLAN_MODE_BRIDGE]   = "bridge",
+				[MACVLAN_MODE_VEPA]     = "vepa",
+				[MACVLAN_MODE_PRIVATE]  = "private",
+				[MACVLAN_MODE_PASSTHRU] = "passthru",
+		};
+
+		dev = nm_platform_link_get_name (NM_PLATFORM_GET, parent);
+		g_assert (dev);
+		g_assert_cmpint (lnk->mode, <, G_N_ELEMENTS (modes));
+
+		success = !nmtstp_run_command ("ip link add name %s link %s type %s mode %s %s",
+		                                name,
+		                                dev,
+		                                lnk->tap ? "macvtap" : "macvlan",
+		                                modes[lnk->mode],
+		                                lnk->no_promisc ? "nopromisc" : "");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, link_type, 100);
+	} else
+		success = nm_platform_link_macvlan_add (NM_PLATFORM_GET, name, parent, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, link_type);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_sit_add (gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkSit *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET_ADDRSTRLEN];
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *dev = "";
+
+		if (lnk->parent_ifindex) {
+			const char *parent_name;
+
+			parent_name = nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex);
+			g_assert (parent_name);
+			dev = nm_sprintf_bufa (100, " dev %s", parent_name);
+		}
+
+		success = !nmtstp_run_command ("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s",
+		                                name,
+		                                dev,
+		                                nm_utils_inet4_ntop (lnk->local, NULL),
+		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                lnk->ttl,
+		                                lnk->tos,
+		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
+		if (success)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_SIT, 100);
+	} else
+		success = nm_platform_link_sit_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (success, pllink, name, NM_LINK_TYPE_SIT);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_vxlan_add (gboolean external_command,
+                       const char *name,
+                       const NMPlatformLnkVxlan *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	NMPlatformError plerr;
+	int err;
+
+	g_assert (nm_utils_iface_valid_name (name));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+		gs_free char *local = NULL, *remote = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex));
+
+		if (lnk->local)
+			local = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->local, NULL));
+		else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any)))
+			local = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->local6, NULL));
+
+		if (lnk->group)
+			remote = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->group, NULL));
+		else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any)))
+			remote = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->group6, NULL));
+
+		err = nmtstp_run_command ("ip link add %s type vxlan id %u %s local %s group %s ttl %u tos %02x dstport %u srcport %u %u ageing %u",
+		                          name,
+		                          lnk->id,
+		                          dev ? dev : "",
+		                          local,
+		                          remote,
+		                          lnk->ttl,
+		                          lnk->tos,
+		                          lnk->dst_port,
+		                          lnk->src_port_min, lnk->src_port_max,
+		                          lnk->ageing);
+		/* Older versions of iproute2 don't support adding vxlan devices.
+		 * On failure, fallback to using platform code. */
+		if (err == 0)
+			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_VXLAN, 100);
+		else
+			_LOGI ("Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task.");
+	}
+	if (!pllink) {
+		plerr = nm_platform_link_vxlan_add (NM_PLATFORM_GET, name, lnk, &pllink);
+		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (pllink);
+	}
+
+	g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_VXLAN);
+	g_assert_cmpstr (pllink->name, ==, name);
+	return pllink;
+}
+
+void
+nmtstp_ip4_address_add (gboolean external_command,
+                        int ifindex,
+                        in_addr_t address,
+                        int plen,
+                        in_addr_t peer_address,
+                        guint32 lifetime,
+                        guint32 preferred,
+                        const char *label)
+{
+	_ip_address_add (external_command,
+	                 TRUE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 (NMIPAddr *) &peer_address,
+	                 lifetime,
+	                 preferred,
+	                 label,
+	                 0);
+}
+
+void
+nmtstp_ip6_address_add (gboolean external_command,
+                        int ifindex,
+                        struct in6_addr address,
+                        int plen,
+                        struct in6_addr peer_address,
+                        guint32 lifetime,
+                        guint32 preferred,
+                        guint flags)
+{
+	_ip_address_add (external_command,
+	                 FALSE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 (NMIPAddr *) &peer_address,
+	                 lifetime,
+	                 preferred,
+	                 NULL,
+	                 flags);
+}
+
+static void
+_ip_address_del (gboolean external_command,
+                 gboolean is_v4,
+                 int ifindex,
+                 const NMIPAddr *address,
+                 int plen,
+                 const NMIPAddr *peer_address)
+{
+	gint64 end_time;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *ifname;
+		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+		int success;
+		gboolean had_address;
+
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		g_assert (ifname);
+
+		/* let's wait until we see the address as we added it. */
+		if (is_v4)
+			had_address = !!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4);
+		else
+			had_address = !!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen);
+
+		if (is_v4) {
+			success = nmtstp_run_command ("ip address delete %s%s%s/%d dev %s",
+			                              nm_utils_inet4_ntop (address->addr4, b1),
+			                              peer_address->addr4 != address->addr4 ? " peer " : "",
+			                              peer_address->addr4 != address->addr4 ? nm_utils_inet4_ntop (peer_address->addr4, b2) : "",
+			                              plen,
+			                              ifname);
+		} else {
+			g_assert (!peer_address);
+			success = nmtstp_run_command ("ip address delete %s/%d dev %s",
+			                              nm_utils_inet6_ntop (&address->addr6, b1),
+			                              plen,
+			                              ifname);
+		}
+		g_assert (success == 0 || !had_address);
+	} else {
+		gboolean success;
+
+		if (is_v4) {
+			success = nm_platform_ip4_address_delete (NM_PLATFORM_GET,
+			                                          ifindex,
+			                                          address->addr4,
+			                                          plen,
+			                                          peer_address->addr4);
+		} else {
+			g_assert (!peer_address);
+			success = nm_platform_ip6_address_delete (NM_PLATFORM_GET,
+			                                          ifindex,
+			                                          address->addr6,
+			                                          plen);
+		}
+		g_assert (success);
+	}
+
+	/* Let's wait until we get the result */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		/* let's wait until we see the address as we added it. */
+		if (is_v4) {
+			const NMPlatformIP4Address *a;
+
+			a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4);
+			if (!a)
+				break;
+		} else {
+			const NMPlatformIP6Address *a;
+
+			a = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen);
+			if (!a)
+				break;
+		}
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
+}
+
+void
+nmtstp_ip4_address_del (gboolean external_command,
+                        int ifindex,
+                        in_addr_t address,
+                        int plen,
+                        in_addr_t peer_address)
+{
+	_ip_address_del (external_command,
+	                 TRUE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 (NMIPAddr *) &peer_address);
+}
+
+void
+nmtstp_ip6_address_del (gboolean external_command,
+                        int ifindex,
+                        struct in6_addr address,
+                        int plen)
+{
+	_ip_address_del (external_command,
+	                 FALSE,
+	                 ifindex,
+	                 (NMIPAddr *) &address,
+	                 plen,
+	                 NULL);
+}
+
+const NMPlatformLink *
+nmtstp_link_get_typed (int ifindex,
+                       const char *name,
+                       NMLinkType link_type)
+{
+	const NMPlatformLink *pllink = NULL;
+
+	if (ifindex > 0) {
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+
+		if (pllink) {
+			g_assert_cmpint (pllink->ifindex, ==, ifindex);
+			if (name)
+				g_assert_cmpstr (name, ==, pllink->name);
+		} else {
+			if (name)
+				g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name));
+		}
+	} else {
+		g_assert (name);
+
+		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+
+		if (pllink)
+			g_assert_cmpstr (name, ==, pllink->name);
+	}
+
+	g_assert (!name || nm_utils_iface_valid_name (name));
+
+	if (pllink && link_type != NM_LINK_TYPE_NONE)
+		g_assert_cmpint (pllink->type, ==, link_type);
+
+	return pllink;
+}
+
+const NMPlatformLink *
+nmtstp_link_get (int ifindex,
+                 const char *name)
+{
+	return nmtstp_link_get_typed (ifindex, name, NM_LINK_TYPE_NONE);
+}
+
+void
+nmtstp_link_del (gboolean external_command,
+                 int ifindex,
+                 const char *name)
+{
+	gint64 end_time;
+	const NMPlatformLink *pllink;
+	gboolean success;
+	gs_free char *name_copy = NULL;
+
+	pllink = nmtstp_link_get (ifindex, name);
+
+	g_assert (pllink);
+
+	name = name_copy = g_strdup (pllink->name);
+	ifindex = pllink->ifindex;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		nmtstp_run_command_check ("ip link delete %s", name);
+	} else {
+		success = nm_platform_link_delete (NM_PLATFORM_GET, ifindex);
+		g_assert (success);
+	}
+
+	/* Let's wait until we get the result */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		if (!nm_platform_link_get (NM_PLATFORM_GET, ifindex)) {
+			g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name));
+			break;
+		}
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
 }
 
+void
+nmtstp_link_set_updown (gboolean external_command,
+                        int ifindex,
+                        gboolean up)
+{
+	const NMPlatformLink *plink;
+	gint64 end_time;
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	if (external_command) {
+		const char *ifname;
+
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		g_assert (ifname);
+
+		nmtstp_run_command_check ("ip link set %s %s",
+		                          ifname,
+		                          up ? "up" : "down");
+	} else {
+		if (up)
+			g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
+		else
+			g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex));
+	}
+
+	/* Let's wait until we get the result */
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	do {
+		if (external_command)
+			nm_platform_process_events (NM_PLATFORM_GET);
+
+		/* let's wait until we see the address as we added it. */
+		plink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+		g_assert (plink);
+
+		if (NM_FLAGS_HAS (plink->flags, IFF_UP) == !!up)
+			break;
+
+		/* for internal command, we expect not to reach this line.*/
+		g_assert (external_command);
+
+		g_assert (nmtstp_wait_for_signal_until (end_time));
+	} while (TRUE);
+}
+
+/*****************************************************************************/
+
+struct _NMTstpNamespaceHandle {
+	pid_t pid;
+	int pipe_fd;
+};
+
+NMTstpNamespaceHandle *
+nmtstp_namespace_create (int unshare_flags, GError **error)
+{
+	NMTstpNamespaceHandle *ns_handle;
+	int e;
+	int errsv;
+	pid_t pid, pid2;
+	int pipefd_c2p[2];
+	int pipefd_p2c[2];
+	ssize_t r;
+
+	e = pipe (pipefd_c2p);
+	if (e != 0) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		return FALSE;
+	}
+
+	e = pipe (pipefd_p2c);
+	if (e != 0) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		close (pipefd_c2p[0]);
+		close (pipefd_c2p[1]);
+		return FALSE;
+	}
+
+	pid = fork ();
+	if (pid < 0) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "fork() failed with %d (%s)", errsv, strerror (errsv));
+		close (pipefd_c2p[0]);
+		close (pipefd_c2p[1]);
+		close (pipefd_p2c[0]);
+		close (pipefd_p2c[1]);
+		return FALSE;
+	}
+
+	if (pid == 0) {
+		char read_buf[1];
+
+		close (pipefd_c2p[0]); /* close read-end */
+		close (pipefd_p2c[1]); /* close write-end */
+
+		if (unshare (unshare_flags) != 0) {
+			errsv = errno;
+			if (errsv == 0)
+				errsv = -1;
+		} else
+			errsv = 0;
+
+		/* sync with parent process and send result. */
+		do {
+			r = write (pipefd_c2p[1], &errsv, sizeof (errsv));
+		} while (r < 0 && errno == EINTR);
+		if (r != sizeof (errsv)) {
+			errsv = errno;
+			if (errsv == 0)
+				errsv = -2;
+		}
+		close (pipefd_c2p[1]);
+
+		/* wait until parent process terminates (or kills us). */
+		if (errsv == 0) {
+			do {
+				r = read (pipefd_p2c[0], read_buf, sizeof (read_buf));
+			} while (r < 0 && errno == EINTR);
+		}
+		close (pipefd_p2c[0]);
+		_exit (0);
+	}
+
+	close (pipefd_c2p[1]); /* close write-end */
+	close (pipefd_p2c[0]); /* close read-end */
+
+	/* sync with child process. */
+	do {
+		r = read (pipefd_c2p[0], &errsv, sizeof (errsv));
+	} while (r < 0 && errno == EINTR);
+
+	close (pipefd_c2p[0]);
+
+	if (   r != sizeof (errsv)
+	    || errsv != 0) {
+		int status;
+
+		if (r != sizeof (errsv)) {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "child process failed for unknown reason");
+		} else {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "child process signaled failure %d (%s)", errsv, strerror (errsv));
+		}
+		close (pipefd_p2c[1]);
+		kill (pid, SIGKILL);
+		do {
+			pid2 = waitpid (pid, &status, 0);
+		} while (pid2 == -1 && errno == EINTR);
+		return FALSE;
+	}
+
+	ns_handle = g_new0 (NMTstpNamespaceHandle, 1);
+	ns_handle->pid = pid;
+	ns_handle->pipe_fd = pipefd_p2c[1];
+	return ns_handle;
+}
+
+pid_t
+nmtstp_namespace_handle_get_pid (NMTstpNamespaceHandle *ns_handle)
+{
+	g_return_val_if_fail (ns_handle, 0);
+	g_return_val_if_fail (ns_handle->pid > 0, 0);
+
+	return ns_handle->pid;
+}
+
+void
+nmtstp_namespace_handle_release (NMTstpNamespaceHandle *ns_handle)
+{
+	pid_t pid;
+	int status;
+
+	if (!ns_handle)
+		return;
+
+	g_return_if_fail (ns_handle->pid > 0);
+
+	close (ns_handle->pipe_fd);
+	ns_handle->pipe_fd = 0;
+
+	kill (ns_handle->pid, SIGKILL);
+
+	do {
+		pid = waitpid (ns_handle->pid, &status, 0);
+	} while (pid == -1 && errno == EINTR);
+	ns_handle->pid = 0;
+
+	g_free (ns_handle);
+}
+
+int
+nmtstp_namespace_get_fd_for_process (pid_t pid, const char *ns_name)
+{
+	char p[1000];
+
+	g_return_val_if_fail (pid > 0, 0);
+	g_return_val_if_fail (ns_name && ns_name[0] && strlen (ns_name) < 50, 0);
+
+	nm_sprintf_buf (p, "/proc/%lu/ns/%s", (long unsigned) pid, ns_name);
+
+	return open(p, O_RDONLY);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 static gboolean
@@ -318,7 +1487,7 @@ main (int argc, char **argv)
 
 	init_tests (&argc, &argv);
 
-	if (   nmtst_platform_is_root_test ()
+	if (   nmtstp_is_root_test ()
 	    && (geteuid () != 0 || getegid () != 0)) {
 		if (   g_getenv ("NMTST_FORCE_REAL_ROOT")
 		    || !unshare_user ()) {
@@ -335,7 +1504,7 @@ main (int argc, char **argv)
 		}
 	}
 
-	if (nmtst_platform_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) {
+	if (nmtstp_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) {
 		int errsv;
 
 		if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) {
@@ -345,10 +1514,7 @@ main (int argc, char **argv)
 
 		/* Mount our /sys instance, so that gudev sees only our devices.
 		 * Needs to be read-only, because we don't run udev. */
-		if (mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL) != 0) {
-			errsv = errno;
-			g_error ("mount(\"/\", MS_SLAVE) failed with %s (%d)", strerror (errsv), errsv);
-		}
+		mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL);
 		if (mount ("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) {
 			errsv = errno;
 			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
@@ -383,6 +1549,6 @@ main (int argc, char **argv)
 
 	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
-	g_object_unref (nm_platform_get ());
+	g_object_unref (NM_PLATFORM_GET);
 	return result;
 }
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 3d647543..0281d3c5 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -4,7 +4,7 @@
 #include <string.h>
 #include <arpa/inet.h>
 
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "nm-platform.h"
 #include "nm-fake-platform.h"
 #include "nm-linux-platform.h"
@@ -13,10 +13,33 @@
 
 #define DEVICE_NAME "nm-test-device"
 
-#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
+/*********************************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "platform-test"
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN, __VA_ARGS__)
+
+#define _LOG(level, domain, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            gint64 _ts = nm_utils_get_monotonic_timestamp_ns (); \
+            \
+            _nm_log (__level, __domain, 0, \
+                     "%s[%ld.%09ld]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (long) (_ts / NM_UTILS_NS_PER_SECOND), \
+                     (long) (_ts % NM_UTILS_NS_PER_SECOND) \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*********************************************************************************************/
 
 typedef struct {
-	int handler_id;
+	gulong handler_id;
 	const char *name;
 	NMPlatformSignalChangeType change_type;
 	gint received_count;
@@ -25,8 +48,21 @@ typedef struct {
 	const char *ifname;
 } SignalData;
 
-gboolean nmtst_platform_is_root_test (void);
-gboolean nmtst_platform_is_sysfs_writable (void);
+gboolean nmtstp_is_root_test (void);
+gboolean nmtstp_is_sysfs_writable (void);
+
+/******************************************************************************/
+
+typedef struct _NMTstpNamespaceHandle NMTstpNamespaceHandle;
+
+NMTstpNamespaceHandle *nmtstp_namespace_create (int flags, GError **error);
+
+void nmtstp_namespace_handle_release (NMTstpNamespaceHandle *handle);
+pid_t nmtstp_namespace_handle_get_pid (NMTstpNamespaceHandle *handle);
+
+int nmtstp_namespace_get_fd_for_process (pid_t pid, const char *ns_name);
+
+/******************************************************************************/
 
 SignalData *add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCallback callback, int ifindex, const char *ifname);
 #define add_signal(name, change_type, callback) add_signal_full (name, change_type, (GCallback) callback, 0, NULL)
@@ -35,11 +71,13 @@ SignalData *add_signal_full (const char *name, NMPlatformSignalChangeType change
 void _accept_signal (const char *file, int line, const char *func, SignalData *data);
 void _accept_signals (const char *file, int line, const char *func, SignalData *data, int min, int max);
 void _wait_signal (const char *file, int line, const char *func, SignalData *data);
+void _accept_or_wait_signal (const char *file, int line, const char *func, SignalData *data);
 void _ensure_no_signal (const char *file, int line, const char *func, SignalData *data);
 void _free_signal (const char *file, int line, const char *func, SignalData *data);
 #define accept_signal(data) _accept_signal(__FILE__, __LINE__, G_STRFUNC, data)
 #define accept_signals(data, min, max) _accept_signals(__FILE__, __LINE__, G_STRFUNC, data, min, max)
 #define wait_signal(data) _wait_signal(__FILE__, __LINE__, G_STRFUNC, data)
+#define accept_or_wait_signal(data) _accept_or_wait_signal(__FILE__, __LINE__, G_STRFUNC, data)
 #define ensure_no_signal(data) _ensure_no_signal(__FILE__, __LINE__, G_STRFUNC, data)
 #define free_signal(data) _free_signal(__FILE__, __LINE__, G_STRFUNC, data)
 
@@ -48,9 +86,88 @@ gboolean ip4_route_exists (const char *ifname, guint32 network, int plen, guint3
 void _assert_ip4_route_exists (const char *file, guint line, const char *func, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric);
 #define assert_ip4_route_exists(exists, ifname, network, plen, metric) _assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, exists, ifname, network, plen, metric)
 
-void link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data);
+void link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, SignalData *data);
+
+int nmtstp_run_command (const char *format, ...) __attribute__((__format__ (__printf__, 1, 2)));
+#define nmtstp_run_command_check(...) do { g_assert_cmpint (nmtstp_run_command (__VA_ARGS__), ==, 0); } while (0)
+
+gboolean nmtstp_wait_for_signal (guint timeout_ms);
+gboolean nmtstp_wait_for_signal_until (gint64 until_ms);
+const NMPlatformLink *nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+
+const NMPlatformLink *nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+
+int nmtstp_run_command_check_external_global (void);
+gboolean nmtstp_run_command_check_external (int external_command);
+
+gboolean nmtstp_ip_address_check_lifetime (const NMPlatformIPAddress *addr,
+                                           gint64 now,
+                                           guint32 expected_lifetime,
+                                           guint32 expected_preferred);
+void nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
+                                        gint64 now,
+                                        guint32 expected_lifetime,
+                                        guint32 expected_preferred);
+void nmtstp_ip4_address_add (gboolean external_command,
+                             int ifindex,
+                             in_addr_t address,
+                             int plen,
+                             in_addr_t peer_address,
+                             guint32 lifetime,
+                             guint32 preferred,
+                             const char *label);
+void nmtstp_ip6_address_add (gboolean external_command,
+                             int ifindex,
+                             struct in6_addr address,
+                             int plen,
+                             struct in6_addr peer_address,
+                             guint32 lifetime,
+                             guint32 preferred,
+                             guint flags);
+void nmtstp_ip4_address_del (gboolean external_command,
+                             int ifindex,
+                             in_addr_t address,
+                             int plen,
+                             in_addr_t peer_address);
+void nmtstp_ip6_address_del (gboolean external_command,
+                             int ifindex,
+                             struct in6_addr address,
+                             int plen);
+
+const NMPlatformLink *nmtstp_link_get_typed (int ifindex, const char *name, NMLinkType link_type);
+const NMPlatformLink *nmtstp_link_get (int ifindex, const char *name);
+
+void nmtstp_link_set_updown (gboolean external_command,
+                             int ifindex,
+                             gboolean up);
+
+const NMPlatformLink *nmtstp_link_dummy_add (gboolean external_command,
+                                             const char *name);
+const NMPlatformLink *nmtstp_link_gre_add (gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkGre *lnk);
+const NMPlatformLink *nmtstp_link_ip6tnl_add (gboolean external_command,
+                                              const char *name,
+                                              const NMPlatformLnkIp6Tnl *lnk);
+const NMPlatformLink *nmtstp_link_ipip_add (gboolean external_command,
+                                            const char *name,
+                                            const NMPlatformLnkIpIp *lnk);
+const NMPlatformLink *nmtstp_link_macvlan_add (gboolean external_command,
+                                               const char *name,
+                                               int parent,
+                                               const NMPlatformLnkMacvlan *lnk);
+const NMPlatformLink *nmtstp_link_sit_add (gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkSit *lnk);
+const NMPlatformLink *nmtstp_link_vxlan_add (gboolean external_command,
+                                             const char *name,
+                                             const NMPlatformLnkVxlan *lnk);
 
-void run_command (const char *format, ...);
+void nmtstp_link_del (gboolean external_command,
+                      int ifindex,
+                      const char *name);
 
 void init_tests (int *argc, char ***argv);
 void setup_tests (void);
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index 0a397b37..f342e565 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -23,7 +23,7 @@
 #include <linux/rtnetlink.h>
 
 #include "nm-linux-platform.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 
 #include "nm-test-utils.h"
 
@@ -65,3 +65,4 @@ main (int argc, char **argv)
 
 	return g_test_run ();
 }
+
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 47cbcec1..8b496262 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1,5 +1,9 @@
 #include "config.h"
 
+#include <sched.h>
+
+#include "nmp-object.h"
+
 #include "test-common.h"
 #include "nm-test-utils.h"
 
@@ -28,10 +32,18 @@ test_bogus(void)
 	g_assert (!nm_platform_link_get_type (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_get_type_name (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_up (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_down (NM_PLATFORM_GET, BOGUS_IFINDEX));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_noarp (NM_PLATFORM_GET, BOGUS_IFINDEX));
+
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -39,15 +51,18 @@ test_bogus(void)
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, &addrlen));
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
+
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
 	g_assert (!nm_platform_link_supports_carrier_detect (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_supports_vlans (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_assert (!nm_platform_vlan_get_info (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL, NULL));
-	g_assert (!nm_platform_vlan_set_ingress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
-	g_assert (!nm_platform_vlan_set_egress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
+	g_assert (!nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+	g_assert (!nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
+	g_assert (!nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0));
 }
 
 static void
@@ -68,15 +83,15 @@ software_add (NMLinkType link_type, const char *name)
 {
 	switch (link_type) {
 	case NM_LINK_TYPE_DUMMY:
-		return nm_platform_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return nm_platform_link_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_BRIDGE:
-		return nm_platform_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return nm_platform_link_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_BOND:
 		{
 			gboolean bond0_exists = !!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0");
 			NMPlatformError plerr;
 
-			plerr = nm_platform_bond_add (NM_PLATFORM_GET, name, NULL);
+			plerr = nm_platform_link_bond_add (NM_PLATFORM_GET, name, NULL);
 
 			/* Check that bond0 is *not* automatically created. */
 			if (!bond0_exists)
@@ -84,14 +99,14 @@ software_add (NMLinkType link_type, const char *name)
 			return plerr == NM_PLATFORM_ERROR_SUCCESS;
 		}
 	case NM_LINK_TYPE_TEAM:
-		return nm_platform_team_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return nm_platform_link_team_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 	case NM_LINK_TYPE_VLAN: {
 		SignalData *parent_added;
 		SignalData *parent_changed;
 
 		/* Don't call link_callback for the bridge interface */
 		parent_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, PARENT_NAME);
-		if (nm_platform_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
+		if (nm_platform_link_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
 			accept_signal (parent_added);
 		free_signal (parent_added);
 
@@ -108,7 +123,7 @@ software_add (NMLinkType link_type, const char *name)
 				accept_signal (parent_changed);
 			free_signal (parent_changed);
 
-			return nm_platform_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+			return nm_platform_link_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 		}
 	}
 	default:
@@ -123,7 +138,6 @@ test_link_changed_signal_cb (NMPlatform *platform,
                              int ifindex,
                              const NMPlatformIP4Route *route,
                              NMPlatformSignalChangeType change_type,
-                             NMPlatformReason reason,
                              gboolean *p_test_link_changed_signal_arg)
 {
 	/* test invocation of platform signals with multiple listeners
@@ -146,9 +160,6 @@ test_link_changed_signal_cb (NMPlatform *platform,
 	g_assert_cmpint ((gint64) change_type, !=, (gint64) 0);
 	g_assert_cmpint (change_type, !=, NM_PLATFORM_SIGNAL_NONE);
 
-	g_assert_cmpint ((gint64) reason, !=, (gint64) 0);
-	g_assert_cmpint (reason, !=, NM_PLATFORM_REASON_NONE);
-
 	*p_test_link_changed_signal_arg = TRUE;
 }
 
@@ -262,9 +273,9 @@ test_slave (int master, int type, SignalData *master_changed)
 	/* Set slave option */
 	switch (type) {
 	case NM_LINK_TYPE_BRIDGE:
-		if (nmtst_platform_is_sysfs_writable ()) {
-			g_assert (nm_platform_slave_set_option (NM_PLATFORM_GET, ifindex, "priority", "789"));
-			value = nm_platform_slave_get_option (NM_PLATFORM_GET, ifindex, "priority");
+		if (nmtstp_is_sysfs_writable ()) {
+			g_assert (nm_platform_sysctl_slave_set_option (NM_PLATFORM_GET, ifindex, "priority", "789"));
+			value = nm_platform_sysctl_slave_get_option (NM_PLATFORM_GET, ifindex, "priority");
 			g_assert_cmpstr (value, ==, "789");
 			g_free (value);
 		}
@@ -296,7 +307,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	ensure_no_signal (link_added);
 	ensure_no_signal (link_changed);
 	ensure_no_signal (link_removed);
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	nmtstp_link_del (-1, ifindex, NULL);
 	accept_signals (master_changed, 0, 1);
 	accept_signals (link_changed, 0, 1);
 	accept_signal (link_removed);
@@ -311,7 +322,7 @@ test_software (NMLinkType link_type, const char *link_typename)
 {
 	int ifindex;
 	char *value;
-	int vlan_parent, vlan_id;
+	int vlan_parent = -1, vlan_id;
 
 	SignalData *link_added, *link_changed, *link_removed;
 
@@ -327,7 +338,15 @@ test_software (NMLinkType link_type, const char *link_typename)
 	link_changed = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, link_callback, ifindex);
 	link_removed = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, link_callback, ifindex);
 	if (link_type == NM_LINK_TYPE_VLAN) {
-		g_assert (nm_platform_vlan_get_info (NM_PLATFORM_GET, ifindex, &vlan_parent, &vlan_id));
+		const NMPlatformLink *plink;
+		const NMPlatformLnkVlan *plnk;
+
+		plnk = nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, &plink);
+		g_assert (plnk);
+		g_assert (plink);
+
+		vlan_parent = plink->parent;
+		vlan_id = plnk->id;
 		g_assert_cmpint (vlan_parent, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME));
 		g_assert_cmpint (vlan_id, ==, VLAN_ID);
 	}
@@ -347,17 +366,17 @@ test_software (NMLinkType link_type, const char *link_typename)
 	/* Set master option */
 	switch (link_type) {
 	case NM_LINK_TYPE_BRIDGE:
-		if (nmtst_platform_is_sysfs_writable ()) {
-			g_assert (nm_platform_master_set_option (NM_PLATFORM_GET, ifindex, "forward_delay", "789"));
-			value = nm_platform_master_get_option (NM_PLATFORM_GET, ifindex, "forward_delay");
+		if (nmtstp_is_sysfs_writable ()) {
+			g_assert (nm_platform_sysctl_master_set_option (NM_PLATFORM_GET, ifindex, "forward_delay", "789"));
+			value = nm_platform_sysctl_master_get_option (NM_PLATFORM_GET, ifindex, "forward_delay");
 			g_assert_cmpstr (value, ==, "789");
 			g_free (value);
 		}
 		break;
 	case NM_LINK_TYPE_BOND:
-		if (nmtst_platform_is_sysfs_writable ()) {
-			g_assert (nm_platform_master_set_option (NM_PLATFORM_GET, ifindex, "mode", "active-backup"));
-			value = nm_platform_master_get_option (NM_PLATFORM_GET, ifindex, "mode");
+		if (nmtstp_is_sysfs_writable ()) {
+			g_assert (nm_platform_sysctl_master_set_option (NM_PLATFORM_GET, ifindex, "mode", "active-backup"));
+			value = nm_platform_sysctl_master_get_option (NM_PLATFORM_GET, ifindex, "mode");
 			/* When reading back, the output looks slightly different. */
 			g_assert (g_str_has_prefix (value, "active-backup"));
 			g_free (value);
@@ -382,20 +401,18 @@ test_software (NMLinkType link_type, const char *link_typename)
 	free_signal (link_changed);
 
 	/* Delete */
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert_cmpint (nm_platform_link_get_type (NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_NONE);
-	g_assert (!nm_platform_link_get_type (NM_PLATFORM_GET, ifindex));
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
 	accept_signal (link_removed);
 
 	/* Delete again */
 	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME)));
+	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
 	/* VLAN: Delete parent */
 	if (link_type == NM_LINK_TYPE_VLAN) {
 		SignalData *link_removed_parent = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, link_callback, vlan_parent);
 
-		g_assert (nm_platform_link_delete (NM_PLATFORM_GET, vlan_parent));
+		nmtstp_link_del (-1, vlan_parent, NULL);
 		accept_signal (link_removed_parent);
 		free_signal (link_removed_parent);
 	}
@@ -414,7 +431,7 @@ test_bridge (void)
 static void
 test_bond (void)
 {
-	if (nmtst_platform_is_root_test () &&
+	if (nmtstp_is_root_test () &&
 	    !g_file_test ("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) &&
 	    system("modprobe --show bonding") != 0) {
 		g_test_skip ("Skipping test for bonding: bonding module not available");
@@ -436,6 +453,53 @@ test_vlan (void)
 	test_software (NM_LINK_TYPE_VLAN, "vlan");
 }
 
+/*****************************************************************************/
+
+static void
+test_bridge_addr (void)
+{
+	char addr[ETH_ALEN];
+	NMPlatformLink link;
+	const NMPlatformLink *plink = NULL;
+
+	nm_utils_hwaddr_aton ("de:ad:be:ef:00:11", addr, sizeof (addr));
+
+	g_assert_cmpint (nm_platform_link_bridge_add (NM_PLATFORM_GET, DEVICE_NAME, addr, sizeof (addr), &plink), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (plink);
+	link = *plink;
+	g_assert_cmpstr (link.name, ==, DEVICE_NAME);
+
+	g_assert_cmpint (link.addr.len, ==, sizeof (addr));
+	g_assert (!memcmp (link.addr.data, addr, sizeof (addr)));
+
+	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
+	g_assert (plink);
+
+	if (nm_platform_check_support_user_ipv6ll (NM_PLATFORM_GET)) {
+		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
+		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
+
+		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE));
+		g_assert (nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
+		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
+		g_assert (plink);
+		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_NONE);
+
+		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE));
+		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
+		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
+		g_assert (plink);
+		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
+	}
+
+	g_assert_cmpint (plink->addr.len, ==, sizeof (addr));
+	g_assert (!memcmp (plink->addr.data, addr, sizeof (addr)));
+
+	nmtstp_link_del (-1, link.ifindex, link.name);
+}
+
+/*****************************************************************************/
+
 static void
 test_internal (void)
 {
@@ -451,11 +515,11 @@ test_internal (void)
 	g_assert (!nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
 	/* Add device */
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
 	accept_signal (link_added);
 
 	/* Try to add again */
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_EXISTS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_EXISTS);
 
 	/* Check device index, name and type */
 	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
@@ -506,7 +570,7 @@ test_internal (void)
 	accept_signal (link_changed);
 
 	/* Delete device */
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
 	accept_signal (link_removed);
 
 	/* Try to delete again */
@@ -517,15 +581,18 @@ test_internal (void)
 	free_signal (link_removed);
 }
 
+/*****************************************************************************/
+
 static void
 test_external (void)
 {
 	const NMPlatformLink *pllink;
-	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
-	SignalData *link_changed, *link_removed;
+	SignalData *link_added, *link_changed, *link_removed;
 	int ifindex;
 
-	run_command ("ip link add %s type %s", DEVICE_NAME, "dummy");
+	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
+
+	nmtstp_run_command_check ("ip link add %s type %s", DEVICE_NAME, "dummy");
 	wait_signal (link_added);
 
 	g_assert (nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
@@ -549,24 +616,24 @@ test_external (void)
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 
-	run_command ("ip link set %s up", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s up", DEVICE_NAME);
 	wait_signal (link_changed);
 
 	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
-	run_command ("ip link set %s down", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s down", DEVICE_NAME);
 	wait_signal (link_changed);
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 
-	run_command ("ip link set %s arp on", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s arp on", DEVICE_NAME);
 	wait_signal (link_changed);
 	g_assert (nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
-	run_command ("ip link set %s arp off", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link set %s arp off", DEVICE_NAME);
 	wait_signal (link_changed);
 	g_assert (!nm_platform_link_uses_arp (NM_PLATFORM_GET, ifindex));
 
-	run_command ("ip link del %s", DEVICE_NAME);
+	nmtstp_run_command_check ("ip link del %s", DEVICE_NAME);
 	wait_signal (link_removed);
 	accept_signals (link_changed, 0, 1);
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
@@ -576,6 +643,1183 @@ test_external (void)
 	free_signal (link_removed);
 }
 
+/*****************************************************************************/
+
+typedef struct {
+	NMLinkType link_type;
+	int test_mode;
+	gboolean external_command;
+} TestAddSoftwareDetectData;
+
+static void
+test_software_detect (gconstpointer user_data)
+{
+	const TestAddSoftwareDetectData *test_data = user_data;
+	int ifindex, ifindex_parent;
+	const NMPlatformLink *plink;
+	const NMPObject *lnk;
+	guint i_step;
+	const gboolean ext = test_data->external_command;
+
+	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
+	ifindex_parent = nmtstp_assert_wait_for_link (PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	switch (test_data->link_type) {
+	case NM_LINK_TYPE_GRE: {
+		NMPlatformLnkGre lnk_gre = { };
+		gboolean gracefully_skip = FALSE;
+
+		lnk_gre.local = nmtst_inet4_from_string ("192.168.233.204");
+		lnk_gre.remote = nmtst_inet4_from_string ("172.168.10.25");
+		lnk_gre.parent_ifindex = ifindex_parent;
+		lnk_gre.ttl = 174;
+		lnk_gre.tos = 37;
+		lnk_gre.path_mtu_discovery = TRUE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "gre0")) {
+			/* Seems that the ip_gre module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip_gre", NULL) != 0;
+		}
+
+		if (!nmtstp_link_gre_add (ext, DEVICE_NAME, &lnk_gre)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create gre tunnel because of missing ip_gre module (modprobe ip_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding GRE tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_IPIP: {
+		NMPlatformLnkIpIp lnk_ipip = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "tunl0")) {
+			/* Seems that the ipip module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ipip", NULL) != 0;
+		}
+
+		lnk_ipip.local = nmtst_inet4_from_string ("1.2.3.4");
+		lnk_ipip.remote = nmtst_inet4_from_string ("5.6.7.8");
+		lnk_ipip.parent_ifindex = ifindex_parent;
+		lnk_ipip.tos = 32;
+		lnk_ipip.path_mtu_discovery = FALSE;
+
+		if (!nmtstp_link_ipip_add (ext, DEVICE_NAME, &lnk_ipip)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ipip tunnel because of missing ipip module (modprobe ipip)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IPIP tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_IP6TNL: {
+		NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "ip6tnl0")) {
+			/* Seems that the ip6_tunnel module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_tunnel", NULL) != 0;
+		}
+
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.tclass = 20;
+		lnk_ip6tnl.encap_limit = 6;
+		lnk_ip6tnl.flow_label = 1337;
+		lnk_ip6tnl.proto = IPPROTO_IPV6;
+
+		if (!nmtstp_link_ip6tnl_add (ext, DEVICE_NAME, &lnk_ip6tnl)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ip6tnl tunnel because of missing ip6_tunnel module (modprobe ip6_tunnel)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IP6TNL tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_MACVLAN: {
+		NMPlatformLnkMacvlan lnk_macvlan = { };
+
+		lnk_macvlan.mode = MACVLAN_MODE_BRIDGE;
+		lnk_macvlan.no_promisc = FALSE;
+		lnk_macvlan.tap = FALSE;
+
+		if (!nmtstp_link_macvlan_add (ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan))
+			g_error ("Failed adding MACVLAN interface");
+		break;
+	}
+	case NM_LINK_TYPE_MACVTAP: {
+		NMPlatformLnkMacvtap lnk_macvtap = { };
+
+		lnk_macvtap.mode = MACVLAN_MODE_PRIVATE;
+		lnk_macvtap.no_promisc = FALSE;
+		lnk_macvtap.tap = TRUE;
+
+		if (!nmtstp_link_macvlan_add (ext, DEVICE_NAME, ifindex_parent, &lnk_macvtap))
+			g_error ("Failed adding MACVTAP interface");
+		break;
+	}
+	case NM_LINK_TYPE_SIT: {
+		NMPlatformLnkSit lnk_sit = { };
+		gboolean gracefully_skip = FALSE;
+
+		lnk_sit.local = nmtst_inet4_from_string ("192.168.200.1");
+		lnk_sit.remote = nmtst_inet4_from_string ("172.25.100.14");
+		lnk_sit.parent_ifindex = ifindex_parent;
+		lnk_sit.ttl = 0;
+		lnk_sit.tos = 31;
+		lnk_sit.path_mtu_discovery = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "sit0")) {
+			/* Seems that the sit module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "sit", NULL) != 0;
+		}
+
+		if (!nmtstp_link_sit_add (ext, DEVICE_NAME, &lnk_sit)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create sit tunnel because of missing sit module (modprobe sit)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding SIT tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_VLAN:
+		nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1242", DEVICE_NAME, PARENT_NAME);
+		break;
+	case NM_LINK_TYPE_VXLAN: {
+		NMPlatformLnkVxlan lnk_vxlan = { };
+
+		switch (test_data->test_mode) {
+		case 0:
+			lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME);
+			lnk_vxlan.id = 42;
+			lnk_vxlan.local = nmtst_inet4_from_string ("23.1.2.164");
+			lnk_vxlan.group = nmtst_inet4_from_string ("239.1.2.134");
+			lnk_vxlan.dst_port = 4789;
+			lnk_vxlan.learning = TRUE;
+			lnk_vxlan.ageing = 1245;
+			break;
+		case 1:
+			lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME);
+			lnk_vxlan.id = 11214423;
+			lnk_vxlan.local6 = *nmtst_inet6_from_string ("1:2:3:4:334:23::23");
+			lnk_vxlan.group6 = *nmtst_inet6_from_string ("ff0e::115");
+			lnk_vxlan.ttl = 32;
+			lnk_vxlan.dst_port = 57412;
+			lnk_vxlan.src_port_min = 1000;
+			lnk_vxlan.src_port_max = 1003;
+			lnk_vxlan.learning = TRUE;
+			lnk_vxlan.ageing = 3245;
+			break;
+		}
+
+		g_assert (nmtstp_link_vxlan_add (ext, DEVICE_NAME, &lnk_vxlan));
+		break;
+	}
+	default:
+		g_assert_not_reached ();
+	}
+
+	ifindex = nmtstp_assert_wait_for_link (DEVICE_NAME, test_data->link_type, 100)->ifindex;
+
+	nmtstp_link_set_updown (-1, ifindex_parent, TRUE);
+
+	for (i_step = 0; i_step < 5; i_step++) {
+
+		_LOGD ("test-software-detect: step %u", i_step);
+		if (nmtst_is_debug ())
+			nmtstp_run_command_check ("ip -d link show %s", DEVICE_NAME);
+
+		if (i_step > 0) {
+			gboolean set_up = (i_step % 2) == 1;
+
+			if (   test_data->link_type == NM_LINK_TYPE_VXLAN
+			    && set_up) {
+				/* On RHEL-7, we need to add a tiny sleep here, otherwise,
+				 * upping the vxlan device fails with EADDRINUSE.
+				 * https://bugzilla.redhat.com/show_bug.cgi?id=1277131 */
+				g_usleep (1);
+			}
+			nmtstp_link_set_updown (-1, ifindex, set_up);
+		}
+
+		lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, test_data->link_type, &plink);
+		g_assert (plink);
+		g_assert_cmpint (plink->ifindex, ==, ifindex);
+		g_assert (lnk);
+
+		switch (test_data->link_type) {
+		case NM_LINK_TYPE_GRE: {
+			const NMPlatformLnkGre *plnk = &lnk->lnk_gre;
+
+			g_assert (plnk == nm_platform_link_get_lnk_gre (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			g_assert_cmpint (plnk->input_flags, ==, 0);
+			g_assert_cmpint (plnk->output_flags, ==, 0);
+			g_assert_cmpint (plnk->input_key, ==, 0);
+			g_assert_cmpint (plnk->output_key, ==, 0);
+			nmtst_assert_ip4_address (plnk->local, "192.168.233.204");
+			nmtst_assert_ip4_address (plnk->remote, "172.168.10.25");
+			g_assert_cmpint (plnk->ttl, ==, 174);
+			g_assert_cmpint (plnk->tos, ==, 37);
+			g_assert_cmpint (plnk->path_mtu_discovery, ==, TRUE);
+			break;
+		}
+		case NM_LINK_TYPE_IP6TNL: {
+			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fd01::15");
+			nmtst_assert_ip6_address (&plnk->remote, "fd01::16");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tclass, ==, 20);
+			g_assert_cmpint (plnk->encap_limit, ==, 6);
+			g_assert_cmpint (plnk->flow_label, ==, 1337);
+			g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
+			break;
+		}
+		case NM_LINK_TYPE_IPIP: {
+			const NMPlatformLnkIpIp *plnk = &lnk->lnk_ipip;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ipip (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip4_address (plnk->local, "1.2.3.4");
+			nmtst_assert_ip4_address (plnk->remote, "5.6.7.8");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tos, ==, 32);
+			g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE);
+			break;
+		}
+		case NM_LINK_TYPE_MACVLAN: {
+			const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_macvlan (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->no_promisc, ==, FALSE);
+			g_assert_cmpint (plnk->mode, ==, MACVLAN_MODE_BRIDGE);
+			break;
+		}
+		case NM_LINK_TYPE_MACVTAP: {
+			const NMPlatformLnkMacvtap *plnk = &lnk->lnk_macvlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_macvtap (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->no_promisc, ==, FALSE);
+			g_assert_cmpint (plnk->mode, ==, MACVLAN_MODE_PRIVATE);
+			break;
+		}
+		case NM_LINK_TYPE_SIT: {
+			const NMPlatformLnkSit *plnk = &lnk->lnk_sit;
+
+			g_assert (plnk == nm_platform_link_get_lnk_sit (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip4_address (plnk->local, "192.168.200.1");
+			nmtst_assert_ip4_address (plnk->remote, "172.25.100.14");
+			g_assert_cmpint (plnk->ttl, ==, 0);
+			g_assert_cmpint (plnk->tos, ==, 31);
+			g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE);
+			break;
+		}
+		case NM_LINK_TYPE_VLAN: {
+			const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->id, ==, 1242);
+			break;
+		}
+		case NM_LINK_TYPE_VXLAN: {
+			const NMPlatformLnkVxlan *plnk = &lnk->lnk_vxlan;
+
+			g_assert (plnk == nm_platform_link_get_lnk_vxlan (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, !=, 0);
+			g_assert_cmpint (plnk->tos, ==, 0);
+			g_assert_cmpint (plnk->learning, ==, TRUE);
+			g_assert_cmpint (plnk->limit, ==, 0);
+			g_assert_cmpint (plnk->proxy, ==, FALSE);
+			g_assert_cmpint (plnk->rsc, ==, FALSE);
+			g_assert_cmpint (plnk->l2miss, ==, FALSE);
+			g_assert_cmpint (plnk->l3miss, ==, FALSE);
+
+			switch (test_data->test_mode) {
+			case 0:
+				g_assert_cmpint (plnk->id, ==, 42);
+				nmtst_assert_ip4_address (plnk->local, "23.1.2.164");
+				nmtst_assert_ip4_address (plnk->group, "239.1.2.134");
+				nmtst_assert_ip6_address (&plnk->group6, "::");
+				nmtst_assert_ip6_address (&plnk->local6, "::");
+				g_assert_cmpint (plnk->ttl, ==, 0);
+				g_assert_cmpint (plnk->ageing, ==, 1245);
+				g_assert_cmpint (plnk->dst_port, ==, 4789);
+				if (   plnk->src_port_min != 0
+				    || plnk->src_port_max != 0) {
+					/* on some kernels, omiting the port range results in setting
+					 * following default port range. */
+					g_assert_cmpint (plnk->src_port_min, ==, 32768);
+					g_assert_cmpint (plnk->src_port_max, ==, 61000);
+				}
+				break;
+			case 1:
+				g_assert_cmpint (plnk->id, ==, 11214423);
+				nmtst_assert_ip4_address (plnk->local, "0.0.0.0");
+				nmtst_assert_ip4_address (plnk->group, "0.0.0.0");
+				nmtst_assert_ip6_address (&plnk->group6, "ff0e::115");
+				nmtst_assert_ip6_address (&plnk->local6, "1:2:3:4:334:23::23");
+				g_assert_cmpint (plnk->ageing, ==, 3245);
+				g_assert_cmpint (plnk->dst_port, ==, 57412);
+				g_assert_cmpint (plnk->ttl, ==, 32);
+				g_assert_cmpint (plnk->src_port_min, ==, 1000);
+				g_assert_cmpint (plnk->src_port_max, ==, 1003);
+				break;
+			}
+			break;
+		}
+		default:
+			g_assert_not_reached ();
+		}
+	}
+
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
+out_delete_parent:
+	nmtstp_link_del (-1, ifindex_parent, PARENT_NAME);
+}
+
+static void
+test_software_detect_add (const char *testpath,
+                          NMLinkType link_type,
+                          int test_mode)
+{
+	TestAddSoftwareDetectData *test_data;
+	char *path;
+
+	test_data = g_new0 (TestAddSoftwareDetectData, 1);
+	test_data->link_type = link_type;
+	test_data->test_mode = test_mode;
+	test_data->external_command = TRUE;
+
+	path = g_strdup_printf ("%s/external", testpath);
+	g_test_add_data_func_full (path, test_data, test_software_detect, g_free);
+	g_free (path);
+
+	test_data = g_new0 (TestAddSoftwareDetectData, 1);
+	test_data->link_type = link_type;
+	test_data->test_mode = test_mode;
+	test_data->external_command = FALSE;
+
+	path = g_strdup_printf ("%s/platform", testpath);
+	g_test_add_data_func_full (path, test_data, test_software_detect, g_free);
+	g_free (path);
+
+	test_data = g_new0 (TestAddSoftwareDetectData, 1);
+	test_data->link_type = link_type;
+	test_data->test_mode = test_mode;
+	test_data->external_command = -1;
+
+	path = g_strdup_printf ("%s/random", testpath);
+	g_test_add_data_func_full (path, test_data, test_software_detect, g_free);
+	g_free (path);
+}
+
+/*****************************************************************************/
+
+static void
+_assert_xgress_qos_mappings_impl (int ifindex,
+                                  gboolean is_ingress_map ,
+                                  int n_entries,
+                                  int n,
+                                  ...)
+{
+	const NMPlatformLink *plink;
+	const NMPObject *lnk;
+	guint n_map;
+	const NMVlanQosMapping *map;
+	va_list ap;
+	guint i;
+
+	lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, NM_LINK_TYPE_VLAN, &plink);
+
+	g_assert (plink);
+	g_assert_cmpint (plink->ifindex, ==, ifindex);
+	g_assert (lnk);
+	g_assert (&lnk->lnk_vlan == nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, NULL));
+
+	if (nmtst_is_debug ())
+		nmtstp_run_command_check ("ip -d link show %s", plink->name);
+
+	if (is_ingress_map) {
+		map = lnk->_lnk_vlan.ingress_qos_map;
+		n_map = lnk->_lnk_vlan.n_ingress_qos_map;
+	} else {
+		map = lnk->_lnk_vlan.egress_qos_map;
+		n_map = lnk->_lnk_vlan.n_egress_qos_map;
+	}
+
+	if (n_entries != -1)
+		g_assert_cmpint (n_map, ==, n_entries);
+
+	for (i = 0; i < n_map; i++) {
+		if (is_ingress_map) {
+			g_assert_cmpint (map[i].from, >=, 0);
+			g_assert_cmpint (map[i].from, <=, 7);
+		}
+		if (i > 0)
+			g_assert_cmpint (map[i - 1].from, <, map[i].from);
+	}
+
+	va_start (ap, n);
+	for (; n > 0; n--) {
+		gboolean found = FALSE;
+		guint from = va_arg (ap, guint);
+		guint to = va_arg (ap, guint);
+
+		for (i = 0; i < n_map; i++) {
+			if (map[i].from == from) {
+				g_assert (!found);
+				found = TRUE;
+
+				g_assert (map[i].to == to);
+			}
+		}
+		g_assert (found);
+	}
+	va_end (ap);
+}
+#define _assert_xgress_qos_mappings(ifindex, is_ingress_map, n_entries, ...) \
+	_assert_xgress_qos_mappings_impl ((ifindex), (is_ingress_map), (n_entries), \
+	                                  (G_STATIC_ASSERT_EXPR ((NM_NARG (__VA_ARGS__) % 2) == 0), NM_NARG (__VA_ARGS__) / 2), \
+	                                  __VA_ARGS__)
+#define _assert_ingress_qos_mappings(ifindex, n_entries, ...) _assert_xgress_qos_mappings (ifindex, TRUE, n_entries, __VA_ARGS__)
+#define _assert_egress_qos_mappings(ifindex, n_entries, ...)  _assert_xgress_qos_mappings (ifindex, FALSE, n_entries, __VA_ARGS__)
+
+static void
+_assert_vlan_flags (int ifindex, NMVlanFlags flags)
+{
+	const NMPlatformLnkVlan *plnk;
+
+	plnk = nm_platform_link_get_lnk_vlan (NM_PLATFORM_GET, ifindex, NULL);
+	g_assert (plnk);
+	g_assert_cmpint (plnk->flags, ==, flags);
+}
+
+static void
+test_vlan_set_xgress (void)
+{
+	int ifindex, ifindex_parent;
+
+	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
+	ifindex_parent = nmtstp_assert_wait_for_link (PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1245", DEVICE_NAME, PARENT_NAME);
+	ifindex = nmtstp_assert_wait_for_link (DEVICE_NAME, NM_LINK_TYPE_VLAN, 100)->ifindex;
+
+	/* ingress-qos-map */
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 4, 5));
+	_assert_ingress_qos_mappings (ifindex, 1,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 3, 7));
+	_assert_ingress_qos_mappings (ifindex, 2,
+	                              3, 7,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 3, 8));
+	_assert_ingress_qos_mappings (ifindex, 2,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, 4));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 4,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, G_MAXUINT32,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32 - 1));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, G_MAXUINT32 - 1,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, 5));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 0, 5));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	/* Set invalid values: */
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 8, 3));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	g_assert (nm_platform_link_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, 9, 4));
+	_assert_ingress_qos_mappings (ifindex, 3,
+	                              0, 5,
+	                              3, 8,
+	                              4, 5);
+
+	/* egress-qos-map */
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 7, 3));
+	_assert_egress_qos_mappings (ifindex, 1,
+	                             7, 3);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 8, 4));
+	_assert_egress_qos_mappings (ifindex, 2,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 0, 4));
+	_assert_egress_qos_mappings (ifindex, 3,
+	                             0, 4,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 1, 4));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             0, 4,
+	                             1, 4,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 1, 5));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             8, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 9, 5));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             8, 4,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 8, 5));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             8, 5,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 8, 0));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             0, 4,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 0, 0));
+	_assert_egress_qos_mappings (ifindex, 3,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 100, 4));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, G_MAXUINT32, 4));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4,
+	                             G_MAXUINT32, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, G_MAXUINT32, 8));
+	_assert_egress_qos_mappings (ifindex, 5,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4,
+	                             G_MAXUINT32, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, G_MAXUINT32, 0));
+	_assert_egress_qos_mappings (ifindex, 4,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5,
+	                             100, 4);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 100, 0));
+	_assert_egress_qos_mappings (ifindex, 3,
+	                             1, 5,
+	                             7, 3,
+	                             9, 5);
+
+	g_assert (nm_platform_link_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, 1, 0));
+	_assert_egress_qos_mappings (ifindex, 2,
+	                             7, 3,
+	                             9, 5);
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 1, .to = 5 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 1,
+		                              1, 5);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 3, .to = 5 },
+			{ .from = 7, .to = 1655 },
+			{ .from = 7, .to = 17655 },
+			{ .from = 5, .to = 754 },
+			{ .from = 4, .to = 12 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 4,
+		                              3, 5,
+		                              4, 12,
+		                              7, 17655,
+		                              5, 754);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 3, .to = 18 },
+			{ .from = 6, .to = 121 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 5,
+		                              3, 18,
+		                              4, 12,
+		                              6, 121,
+		                              7, 17655,
+		                              5, 754);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 3, .to = 0 },
+			{ .from = 6, .to = 7 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 1,
+		                              6, 7);
+	}
+
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 1, .to = 5 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        NULL,
+		                                        0));
+		_assert_ingress_qos_mappings (ifindex, 1,
+		                              1, 5);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 5, .to = 1 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 1,
+		                             5, 1);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 5, .to = 3 },
+			{ .from = 1655, .to = 5 },
+			{ .from = 1655, .to = 7 },
+			{ .from = G_MAXUINT32, .to = 6 },
+			{ .from = G_MAXUINT32, .to = 8 },
+			{ .from = 754, .to = 4 },
+			{ .from = 3, .to = 2 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 5,
+		                             3, 2,
+		                             5, 3,
+		                             754, 4,
+		                             1655, 7,
+		                             G_MAXUINT32, 6);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 754, .to = 3 },
+			{ .from = 755, .to = 8 },
+			{ .from = 1655, .to = 0 },
+			{ .from = 6, .to = 1 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        FALSE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 5,
+		                             3, 2,
+		                             5, 3,
+		                             6, 1,
+		                             754, 3,
+		                             G_MAXUINT32, 6);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 6, .to = 0 },
+			{ .from = 3, .to = 4 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 1,
+		                             3, 4);
+	}
+
+	{
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 1, .to = 5 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        0,
+		                                        0,
+		                                        FALSE,
+		                                        NULL,
+		                                        0,
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_egress_qos_mappings (ifindex, 1,
+		                             1, 5);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 6, .to = 145 },
+			{ .from = 4, .to = 1 },
+			{ .from = 6, .to = 12 },
+		};
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 1, .to = 5 },
+			{ .from = 3232, .to = 7 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP,
+		                                        NM_VLAN_FLAG_REORDER_HEADERS,
+		                                        TRUE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        TRUE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_ingress_qos_mappings (ifindex, 2,
+		                             4, 1,
+		                             6, 12);
+		_assert_egress_qos_mappings (ifindex, 2,
+		                             1, 5,
+		                             3232, 7);
+		_assert_vlan_flags (ifindex, NM_VLAN_FLAG_REORDER_HEADERS);
+	}
+
+	{
+		const NMVlanQosMapping ingress_map[] = {
+			{ .from = 6, .to = 145 },
+			{ .from = 4, .to = 1 },
+			{ .from = 6, .to = 12 },
+		};
+		const NMVlanQosMapping egress_map[] = {
+			{ .from = 1, .to = 7 },
+			{ .from = 64, .to = 10 },
+			{ .from = 64, .to = 10 },
+			{ .from = 64, .to = 10 },
+			{ .from = 64, .to = 10 },
+			{ .from = 3232, .to = 0 },
+			{ .from = 64, .to = 4 },
+		};
+
+		g_assert (nm_platform_link_vlan_change (NM_PLATFORM_GET,
+		                                        ifindex,
+		                                        NM_VLAN_FLAG_GVRP,
+		                                        NM_VLAN_FLAG_GVRP,
+		                                        FALSE,
+		                                        ingress_map,
+		                                        G_N_ELEMENTS (ingress_map),
+		                                        FALSE,
+		                                        egress_map,
+		                                        G_N_ELEMENTS (egress_map)));
+		_assert_ingress_qos_mappings (ifindex, 2,
+		                             4, 1,
+		                             6, 12);
+		_assert_egress_qos_mappings (ifindex, 2,
+		                             1, 7,
+		                             64, 4);
+		_assert_vlan_flags (ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP);
+	}
+
+	nmtstp_link_del (-1, ifindex, DEVICE_NAME);
+	nmtstp_link_del (-1, ifindex_parent, PARENT_NAME);
+}
+
+/*****************************************************************************/
+
+static void
+test_create_many_links_do (guint n_devices)
+{
+	gint64 time, start_time = nm_utils_get_monotonic_timestamp_ns ();
+	guint i;
+	char name[64];
+	const NMPlatformLink *pllink;
+	gs_unref_array GArray *ifindexes = g_array_sized_new (FALSE, FALSE, sizeof (int), n_devices);
+	const gint EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
+
+	g_assert (EX >= -1 && EX <= 2);
+
+	_LOGI (">>> create devices (EX=%d)...", EX);
+
+	for (i = 0; i < n_devices; i++) {
+		nm_sprintf_buf (name, "t-%05u", i);
+		if (EX == 2) {
+			/* This mode is different from letting nmtstp_link_dummy_add()
+			 * because in this case we don't process any platform events
+			 * while adding all the links. */
+			nmtstp_run_command_check ("ip link add %s type dummy", name);
+		} else
+			nmtstp_link_dummy_add (EX, name);
+	}
+
+	_LOGI (">>> process events after creating devices...");
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	_LOGI (">>> check devices...");
+
+	for (i = 0; i < n_devices; i++) {
+		nm_sprintf_buf (name, "t-%05u", i);
+
+		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+		g_assert (pllink);
+		g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_DUMMY);
+		g_assert_cmpstr (pllink->name, ==, name);
+
+		g_array_append_val (ifindexes, pllink->ifindex);
+	}
+
+	_LOGI (">>> delete devices...");
+
+	g_assert_cmpint (ifindexes->len, ==, n_devices);
+	for (i = 0; i < n_devices; i++) {
+		nm_sprintf_buf (name, "t-%05u", i);
+
+		if (EX == 2)
+			nmtstp_run_command_check ("ip link delete %s", name);
+		else
+			nmtstp_link_del (EX, g_array_index (ifindexes, int, i), name);
+	}
+
+	_LOGI (">>> process events after deleting devices...");
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	time = nm_utils_get_monotonic_timestamp_ns () - start_time;
+	_LOGI (">>> finished in %ld.%09ld seconds", (long) (time / NM_UTILS_NS_PER_SECOND), (long) (time % NM_UTILS_NS_PER_SECOND));
+}
+
+static void
+test_create_many_links (gconstpointer user_data)
+{
+	guint n_devices = GPOINTER_TO_UINT (user_data);
+
+	if (n_devices > 100 && nmtst_test_quick ()) {
+		g_print ("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", str_if_set (g_get_prgname (), "test-link-linux"));
+		g_test_skip ("Skip long running test");
+		return;
+	}
+
+	test_create_many_links_do (n_devices);
+}
+
+/*****************************************************************************/
+
+static void
+test_nl_bugs_veth (void)
+{
+	const char *IFACE_VETH0 = "nm-test-veth0";
+	const char *IFACE_VETH1 = "nm-test-veth1";
+	int ifindex_veth0, ifindex_veth1;
+	int i;
+	const NMPlatformLink *pllink_veth0, *pllink_veth1;
+	gs_free_error GError *error = NULL;
+	NMTstpNamespaceHandle *ns_handle = NULL;
+
+	/* create veth pair. */
+	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
+	ifindex_veth0 = nmtstp_assert_wait_for_link (IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
+	ifindex_veth1 = nmtstp_assert_wait_for_link (IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+
+	/* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */
+	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth0, &i));
+	g_assert_cmpint (i, ==, ifindex_veth1);
+
+	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth1, &i));
+	g_assert_cmpint (i, ==, ifindex_veth0);
+
+	/* assert that NMPlatformLink.parent is the peer-ifindex. */
+	pllink_veth0 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth0);
+	g_assert (pllink_veth0);
+	if (pllink_veth0->parent == 0) {
+		/* pre-4.1 kernels don't support exposing the veth peer as IFA_LINK. skip the remainder
+		 * of the test. */
+		goto out;
+	}
+	g_assert_cmpint (pllink_veth0->parent, ==, ifindex_veth1);
+
+
+	/* The following tests whether we have a workaround for kernel bug
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=1285827 in place. */
+	pllink_veth1 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth1);
+	g_assert (pllink_veth1);
+	g_assert_cmpint (pllink_veth1->parent, ==, ifindex_veth0);
+
+
+	/* move one veth peer to another namespace and check that the
+	 * parent/IFLA_LINK of the remaining peer properly updates
+	 * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */
+	ns_handle = nmtstp_namespace_create (CLONE_NEWNET, &error);
+	g_assert_no_error (error);
+	g_assert (ns_handle);
+
+	nmtstp_run_command_check ("ip link set %s netns %ld", IFACE_VETH1, (long) nmtstp_namespace_handle_get_pid (ns_handle));
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (50);
+		nm_platform_process_events (NM_PLATFORM_GET);
+
+		pllink_veth1 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth1);
+		pllink_veth0 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth0);
+		if (   !pllink_veth1
+		    && pllink_veth0
+		    && pllink_veth0->parent == NM_PLATFORM_LINK_OTHER_NETNS) {
+			break;
+		}
+	});
+
+out:
+	nmtstp_link_del (-1, ifindex_veth0, IFACE_VETH0);
+	g_assert (!nmtstp_link_get (ifindex_veth0, IFACE_VETH0));
+	g_assert (!nmtstp_link_get (ifindex_veth1, IFACE_VETH1));
+	nmtstp_namespace_handle_release (ns_handle);
+}
+
+/*****************************************************************************/
+
+static void
+test_nl_bugs_spuroius_newlink (void)
+{
+	const char *IFACE_BOND0 = "nm-test-bond0";
+	const char *IFACE_DUMMY0 = "nm-test-dummy0";
+	int ifindex_bond0, ifindex_dummy0;
+	const NMPlatformLink *pllink;
+	gboolean wait_for_settle;
+
+	/* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
+
+	nmtstp_run_command_check ("ip link add %s type dummy", IFACE_DUMMY0);
+	ifindex_dummy0 = nmtstp_assert_wait_for_link (IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	nmtstp_run_command_check ("ip link add %s type bond", IFACE_BOND0);
+	ifindex_bond0 = nmtstp_assert_wait_for_link (IFACE_BOND0, NM_LINK_TYPE_BOND, 100)->ifindex;
+
+	nmtstp_link_set_updown (-1, ifindex_bond0, TRUE);
+
+	nmtstp_run_command_check ("ip link set %s master %s", IFACE_DUMMY0, IFACE_BOND0);
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (50);
+
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
+		g_assert (pllink);
+		if (pllink->master == ifindex_bond0)
+			break;
+	});
+
+	nmtstp_run_command_check ("ip link del %s",  IFACE_BOND0);
+
+	wait_for_settle = TRUE;
+	nmtstp_wait_for_signal (50);
+again:
+	nm_platform_process_events (NM_PLATFORM_GET);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_bond0);
+	g_assert (!pllink);
+
+	if (wait_for_settle) {
+		wait_for_settle = FALSE;
+		NMTST_WAIT (300, { nmtstp_wait_for_signal (50); });
+		goto again;
+	}
+
+	g_assert (!nmtstp_link_get (ifindex_bond0, IFACE_BOND0));
+	nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
+}
+
+/*****************************************************************************/
+
+static void
+test_nl_bugs_spuroius_dellink (void)
+{
+	const char *IFACE_BRIDGE0 = "nm-test-bridge0";
+	const char *IFACE_DUMMY0 = "nm-test-dummy0";
+	int ifindex_bridge0, ifindex_dummy0;
+	const NMPlatformLink *pllink;
+	gboolean wait_for_settle;
+
+	/* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
+
+	nmtstp_run_command_check ("ip link add %s type dummy", IFACE_DUMMY0);
+	ifindex_dummy0 = nmtstp_assert_wait_for_link (IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+
+	nmtstp_run_command_check ("ip link add %s type bridge", IFACE_BRIDGE0);
+	ifindex_bridge0 = nmtstp_assert_wait_for_link (IFACE_BRIDGE0, NM_LINK_TYPE_BRIDGE, 100)->ifindex;
+
+	nmtstp_link_set_updown (-1, ifindex_bridge0, TRUE);
+
+	nmtstp_run_command_check ("ip link set %s master %s", IFACE_DUMMY0, IFACE_BRIDGE0);
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (50);
+
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
+		g_assert (pllink);
+		if (pllink->master == ifindex_bridge0)
+			break;
+	});
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	nmtstp_run_command_check ("ip link set %s nomaster",  IFACE_DUMMY0);
+
+	wait_for_settle = TRUE;
+	nmtstp_wait_for_signal (50);
+again:
+	nm_platform_process_events (NM_PLATFORM_GET);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_bridge0);
+	g_assert (pllink);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
+	g_assert (pllink);
+	g_assert_cmpint (pllink->parent, ==, 0);
+
+	if (wait_for_settle) {
+		wait_for_settle = FALSE;
+		NMTST_WAIT (300, { nmtstp_wait_for_signal (50); });
+		goto again;
+	}
+
+	nmtstp_link_del (-1, ifindex_bridge0, IFACE_BRIDGE0);
+	nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
@@ -599,7 +1843,28 @@ setup_tests (void)
 	g_test_add_func ("/link/software/bond", test_bond);
 	g_test_add_func ("/link/software/team", test_team);
 	g_test_add_func ("/link/software/vlan", test_vlan);
+	g_test_add_func ("/link/software/bridge/addr", test_bridge_addr);
 
-	if (strcmp (g_type_name (G_TYPE_FROM_INSTANCE (nm_platform_get ())), "NMFakePlatform"))
+	if (nmtstp_is_root_test ()) {
 		g_test_add_func ("/link/external", test_external);
+
+		test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0);
+		test_software_detect_add ("/link/software/detect/ip6tnl", NM_LINK_TYPE_IP6TNL, 0);
+		test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
+		test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
+		test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
+		test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0);
+		test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0);
+		test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0);
+		test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1);
+
+		g_test_add_func ("/link/software/vlan/set-xgress", test_vlan_set_xgress);
+
+		g_test_add_data_func ("/link/create-many-links/20", GUINT_TO_POINTER (20), test_create_many_links);
+		g_test_add_data_func ("/link/create-many-links/1000", GUINT_TO_POINTER (1000), test_create_many_links);
+
+		g_test_add_func ("/link/nl-bugs/veth", test_nl_bugs_veth);
+		g_test_add_func ("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink);
+		g_test_add_func ("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink);
+	}
 }
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 88bb0707..eba2383b 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -20,7 +20,7 @@
 
 #include "nmp-object.h"
 
-#include "nm-logging.h"
+#include "nm-default.h"
 
 #include "nm-test-utils.h"
 
@@ -148,8 +148,8 @@ _nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj,
 	NMPCacheOpsType ops_type;
 	NMPObject *obj2;
 	gboolean was_visible;
-	auto_nmp_obj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
-	auto_nmp_obj NMPObject *new_clone = NULL;
+	nm_auto_nmpobj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
+	nm_auto_nmpobj NMPObject *new_clone = NULL;
 	const NMPObject *obj_old;
 	_NMPCacheUpdateData data = {
 		.cache = cache,
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index cf322dec..60a0c15c 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -9,7 +9,7 @@
 #define DEVICE_NAME "nm-test-device"
 
 static void
-ip4_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP4Route *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip4_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP4Route *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -25,11 +25,11 @@ ip4_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, c
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
-ip6_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP6Route *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data)
+ip6_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, const NMPlatformIP6Route *received, NMPlatformSignalChangeType change_type, SignalData *data)
 {
 	g_assert (received);
 	g_assert_cmpint (received->ifindex, ==, ifindex);
@@ -45,7 +45,7 @@ ip6_route_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, c
 		g_main_loop_quit (data->loop);
 
 	data->received_count++;
-	debug ("Received signal '%s' %dth time.", data->name, data->received_count);
+	_LOGD ("Received signal '%s' %dth time.", data->name, data->received_count);
 }
 
 static void
@@ -282,6 +282,31 @@ test_ip6_route (void)
 	free_signal (route_removed);
 }
 
+/*****************************************************************************/
+
+static void
+test_ip4_zero_gateway (void)
+{
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+
+	nmtstp_run_command_check ("ip route add 1.2.3.1/32 via 0.0.0.0 dev %s", DEVICE_NAME);
+	nmtstp_run_command_check ("ip route add 1.2.3.2/32 dev %s", DEVICE_NAME);
+
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (10);
+		if (   nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0)
+		    && nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0))
+			break;
+	});
+
+	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (50);
+	nm_platform_process_events (NM_PLATFORM_GET);
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
@@ -295,7 +320,7 @@ setup_tests (void)
 
 	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
 	accept_signal (link_added);
 	free_signal (link_added);
 
@@ -304,4 +329,7 @@ setup_tests (void)
 	g_test_add_func ("/route/ip4", test_ip4_route);
 	g_test_add_func ("/route/ip6", test_ip6_route);
 	g_test_add_func ("/route/ip4_metric0", test_ip4_route_metric0);
+
+	if (nmtstp_is_root_test ())
+		g_test_add_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
 }