summary refs log tree commit diff
path: root/src/platform/tests/test-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/tests/test-common.c')
-rw-r--r--src/platform/tests/test-common.c1222
1 files changed, 1194 insertions, 28 deletions
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;
 }