platform: support switching partial namespaces
authorThomas Haller <thaller@redhat.com>
Mon, 14 Mar 2016 13:20:33 +0000 (14:20 +0100)
committerThomas Haller <thaller@redhat.com>
Tue, 15 Mar 2016 11:56:58 +0000 (12:56 +0100)
Previously, the push/pop API to switch between namespaces would always
switch both the net and mount namespace together.

There are situations, where we want to only switch one namespace.
For example, the function nmp_netns_bind_to_path() introduced next
only wants to switch the net namespace to get /proc/self/ns/net,
but must not switch the mount namespace as it bind-mounds in the
namespace of the caller.

src/platform/nmp-netns.c
src/platform/nmp-netns.h
src/platform/tests/test-link.c

index d24faaf..5b1d6ad 100644 (file)
 #define PROC_SELF_NS_MNT "/proc/self/ns/mnt"
 #define PROC_SELF_NS_NET "/proc/self/ns/net"
 
+#define _CLONE_NS_ALL    ((int) (CLONE_NEWNS | CLONE_NEWNET))
+#define _CLONE_NS_ALL_V          CLONE_NEWNS , CLONE_NEWNET
+
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_clone_ns_to_str, int,
+       NM_UTILS_FLAGS2STR (CLONE_NEWNS,  "mnt"),
+       NM_UTILS_FLAGS2STR (CLONE_NEWNET, "net"),
+);
+
+static const char *
+__ns_types_to_str (int ns_types, int ns_types_already_set, char *buf, gsize len)
+{
+       const char *b = buf;
+       char bb[200];
+
+       nm_utils_strbuf_append_c (&buf, &len, '[');
+       if (ns_types & ~ns_types_already_set) {
+               nm_utils_strbuf_append_str (&buf, &len,
+                                           _clone_ns_to_str (ns_types & ~ns_types_already_set, bb, sizeof (bb)));
+       }
+       if (ns_types & ns_types_already_set) {
+               if (ns_types & ~ns_types_already_set)
+                       nm_utils_strbuf_append_c (&buf, &len, '/');
+               nm_utils_strbuf_append_str (&buf, &len,
+                                           _clone_ns_to_str (ns_types & ns_types_already_set, bb, sizeof (bb)));
+       }
+       nm_utils_strbuf_append_c (&buf, &len, ']');
+       return b;
+}
+#define _ns_types_to_str(ns_types, ns_types_already_set, buf) \
+       __ns_types_to_str (ns_types, ns_types_already_set, buf, sizeof (buf))
+
 /*********************************************************************************************/
 
 #define _NMLOG_DOMAIN        LOGD_PLATFORM
@@ -67,9 +98,10 @@ struct _NMPNetnsPrivate {
 typedef struct {
        NMPNetns *netns;
        int count;
+       int ns_types;
 } NetnsInfo;
 
-static void _stack_push (NMPNetns *netns);
+static void _stack_push (NMPNetns *netns, int ns_types);
 static NMPNetns *_netns_new (GError **error);
 
 /*********************************************************************************************/
@@ -98,7 +130,7 @@ _stack_ensure_init_impl (void)
                return;
        }
 
-       _stack_push (netns);
+       _stack_push (netns, _CLONE_NS_ALL);
 
        /* we leak this instance inside netns_stack. It cannot be popped. */
        g_object_unref (netns);
@@ -110,23 +142,67 @@ _stack_ensure_init_impl (void)
                } \
        } G_STMT_END
 
-static NetnsInfo *
-_stack_peek (void)
+static NMPNetns *
+_stack_current_netns (int ns_types)
 {
-       nm_assert (netns_stack);
+       guint j;
 
-       if (netns_stack->len > 0)
-               return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
-       return NULL;
+       nm_assert (netns_stack && netns_stack->len > 0);
+
+       /* we search the stack top-down to find the netns that has
+        * all @ns_types set. */
+       for (j = netns_stack->len; ns_types && j >= 1; ) {
+               NetnsInfo *info;
+
+               info = &g_array_index (netns_stack, NetnsInfo, --j);
+
+               if (NM_FLAGS_ALL (info->ns_types, ns_types))
+                       return info->netns;
+       }
+
+       g_return_val_if_reached (NULL);
+}
+
+static int
+_stack_current_ns_types (NMPNetns *netns, int ns_types)
+{
+       const int ns_types_check[] = { _CLONE_NS_ALL_V };
+       guint i, j;
+       int res = 0;
+
+       nm_assert (netns);
+       nm_assert (netns_stack && netns_stack->len > 0);
+
+       /* we search the stack top-down to check which of @ns_types
+        * are already set to @netns. */
+       for (j = netns_stack->len; ns_types && j >= 1; ) {
+               NetnsInfo *info;
+
+               info = &g_array_index (netns_stack, NetnsInfo, --j);
+               if (info->netns != netns) {
+                       ns_types = NM_FLAGS_UNSET (ns_types, info->ns_types);
+                       continue;
+               }
+
+               for (i = 0; i < G_N_ELEMENTS (ns_types_check); i++) {
+                       if (   NM_FLAGS_HAS (ns_types, ns_types_check[i])
+                           && NM_FLAGS_HAS (info->ns_types, ns_types_check[i])) {
+                               res = NM_FLAGS_SET (res, ns_types_check[i]);
+                               ns_types = NM_FLAGS_UNSET (ns_types, ns_types_check[i]);
+                       }
+               }
+       }
+
+       return res;
 }
 
 static NetnsInfo *
-_stack_peek2 (void)
+_stack_peek (void)
 {
        nm_assert (netns_stack);
 
-       if (netns_stack->len > 1)
-               return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 2));
+       if (netns_stack->len > 0)
+               return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
        return NULL;
 }
 
@@ -141,17 +217,20 @@ _stack_bottom (void)
 }
 
 static void
-_stack_push (NMPNetns *netns)
+_stack_push (NMPNetns *netns, int ns_types)
 {
        NetnsInfo *info;
 
        nm_assert (netns_stack);
        nm_assert (NMP_IS_NETNS (netns));
+       nm_assert (NM_FLAGS_ANY (ns_types, _CLONE_NS_ALL));
+       nm_assert (!NM_FLAGS_ANY (ns_types, ~_CLONE_NS_ALL));
 
        g_array_set_size (netns_stack, netns_stack->len + 1);
 
        info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
        info->netns = g_object_ref (netns);
+       info->ns_types = ns_types;
        info->count = 1;
 }
 
@@ -225,25 +304,47 @@ _netns_new (GError **error)
        return self;
 }
 
+static int
+_setns (NMPNetns *self, int type)
+{
+       char buf[100];
+       int fd;
+
+       nm_assert (NM_IN_SET (type, _CLONE_NS_ALL_V));
+
+       fd = (type == CLONE_NEWNET) ? self->priv->fd_net : self->priv->fd_mnt;
+
+       _LOGt (self, "set netns(%s, %d)", _ns_types_to_str (type, 0, buf), fd);
+
+       return setns (fd, type);
+}
+
 static gboolean
-_netns_switch (NMPNetns *self, NMPNetns *netns_fail)
+_netns_switch_push (NMPNetns *self, int ns_types)
 {
        int errsv;
 
-       if (setns (self->priv->fd_net, CLONE_NEWNET) != 0) {
+       if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
+           && !_stack_current_ns_types (self, CLONE_NEWNET)
+           && _setns (self, CLONE_NEWNET) != 0) {
                errsv = errno;
                _LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
                return FALSE;
        }
-       if (setns (self->priv->fd_mnt, CLONE_NEWNS) != 0) {
+       if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
+           && !_stack_current_ns_types (self, CLONE_NEWNS)
+           && _setns (self, CLONE_NEWNS) != 0) {
                errsv = errno;
                _LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
 
                /* try to fix the mess by returning to the previous netns. */
-               if (netns_fail) {
-                       if (setns (netns_fail->priv->fd_net, CLONE_NEWNET) != 0) {
+               if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
+               && !_stack_current_ns_types (self, CLONE_NEWNET)) {
+                       self = _stack_current_netns (CLONE_NEWNET);
+                       if (   self
+                           && _setns (self, CLONE_NEWNET) != 0) {
                                errsv = errno;
-                               _LOGE (netns_fail, "failed to restore netns: %s", g_strerror (errsv));
+                               _LOGE (self, "failed to restore netns: %s", g_strerror (errsv));
                        }
                }
                return FALSE;
@@ -252,6 +353,41 @@ _netns_switch (NMPNetns *self, NMPNetns *netns_fail)
        return TRUE;
 }
 
+static gboolean
+_netns_switch_pop (NMPNetns *self, int ns_types)
+{
+       int errsv;
+       NMPNetns *current;
+       int success = TRUE;
+
+       if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
+           && (!self || !_stack_current_ns_types (self, CLONE_NEWNET))) {
+               current = _stack_current_netns (CLONE_NEWNET);
+               if (!current) {
+                       g_warn_if_reached ();
+                       success = FALSE;
+               } else if (_setns (current, CLONE_NEWNET) != 0) {
+                       errsv = errno;
+                       _LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+                       success = FALSE;
+               }
+       }
+       if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
+           && (!self || !_stack_current_ns_types (self, CLONE_NEWNS))) {
+               current = _stack_current_netns (CLONE_NEWNS);
+               if (!current) {
+                       g_warn_if_reached ();
+                       success = FALSE;
+               } else if (_setns (current, CLONE_NEWNS) != 0) {
+                       errsv = errno;
+                       _LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+                       success = FALSE;
+               }
+       }
+
+       return success;
+}
+
 /*********************************************************************************************/
 
 int
@@ -272,37 +408,58 @@ nmp_netns_get_fd_mnt (NMPNetns *self)
 
 /*********************************************************************************************/
 
-gboolean
-nmp_netns_push (NMPNetns *self)
+static gboolean
+_nmp_netns_push_type (NMPNetns *self, int ns_types)
 {
        NetnsInfo *info;
-
-       g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+       char sbuf[100];
 
        _stack_ensure_init ();
 
        info = _stack_peek ();
        g_return_val_if_fail (info, FALSE);
 
-       if (info->netns == self) {
+       if (info->netns == self && info->ns_types == ns_types) {
                info->count++;
-               _LOGt (self, "push (increase count to %d)", info->count);
+               _LOGt (self, "push#%u* %s (increase count to %d)",
+                      _stack_size () - 1,
+                      _ns_types_to_str (ns_types, ns_types, sbuf), info->count);
                return TRUE;
        }
 
-       _LOGD (self, "push (was %p)", info->netns);
+       _LOGD (self, "push#%u %s",
+              _stack_size (),
+              _ns_types_to_str (ns_types,
+                                _stack_current_ns_types (self, ns_types),
+                                sbuf));
 
-       if (!_netns_switch (self, info->netns))
+       if (!_netns_switch_push (self, ns_types))
                return FALSE;
 
-       _stack_push (self);
+       _stack_push (self, ns_types);
        return TRUE;
 }
 
+gboolean
+nmp_netns_push (NMPNetns *self)
+{
+       g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+
+       return _nmp_netns_push_type (self, _CLONE_NS_ALL);
+}
+
+gboolean
+nmp_netns_push_type (NMPNetns *self, int ns_types)
+{
+       g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+       g_return_val_if_fail (!NM_FLAGS_ANY (ns_types, ~_CLONE_NS_ALL), FALSE);
+
+       return _nmp_netns_push_type (self, ns_types == 0 ? _CLONE_NS_ALL : ns_types);
+}
+
 NMPNetns *
 nmp_netns_new (void)
 {
-       NetnsInfo *info;
        NMPNetns *self;
        int errsv;
        GError *error = NULL;
@@ -315,7 +472,7 @@ nmp_netns_new (void)
                return NULL;
        }
 
-       if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) {
+       if (unshare (_CLONE_NS_ALL) != 0) {
                errsv = errno;
                _LOGE (NULL, "failed to create new net and mnt namespace: %s", g_strerror (errsv));
                return NULL;
@@ -346,12 +503,11 @@ nmp_netns_new (void)
                goto err_out;
        }
 
-       _stack_push (self);
+       _stack_push (self, _CLONE_NS_ALL);
 
        return self;
 err_out:
-       info = _stack_peek ();
-       _netns_switch (info->netns, NULL);
+       _netns_switch_pop (NULL, _CLONE_NS_ALL);
        return NULL;
 }
 
@@ -359,6 +515,7 @@ gboolean
 nmp_netns_pop (NMPNetns *self)
 {
        NetnsInfo *info;
+       int ns_types;
 
        g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
 
@@ -371,7 +528,8 @@ nmp_netns_pop (NMPNetns *self)
 
        if (info->count > 1) {
                info->count--;
-               _LOGt (self, "pop (decrease count to %d)", info->count);
+               _LOGt (self, "pop#%u* (decrease count to %d)",
+                      _stack_size () - 1, info->count);
                return TRUE;
        }
        g_return_val_if_fail (info->count == 1, FALSE);
@@ -379,14 +537,13 @@ nmp_netns_pop (NMPNetns *self)
        /* cannot pop the original netns. */
        g_return_val_if_fail (_stack_size () > 1, FALSE);
 
-       _LOGD (self, "pop (restore %p)", _stack_peek2 ());
+       _LOGD (self, "pop#%u", _stack_size () - 1);
 
-       _stack_pop ();
-       info = _stack_peek ();
+       ns_types = info->ns_types;
 
-       nm_assert (info);
+       _stack_pop ();
 
-       return _netns_switch (info->netns, NULL);
+       return _netns_switch_pop (self, ns_types);
 }
 
 NMPNetns *
index bdd797d..1625116 100644 (file)
@@ -49,6 +49,7 @@ GType nmp_netns_get_type (void);
 NMPNetns *nmp_netns_new (void);
 
 gboolean nmp_netns_push (NMPNetns *self);
+gboolean nmp_netns_push_type (NMPNetns *self, int ns_types);
 gboolean nmp_netns_pop (NMPNetns *self);
 
 NMPNetns *nmp_netns_get_current (void);
index 9da2404..f7b9f6f 100644 (file)
@@ -1951,13 +1951,13 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
                        _ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i));
        }
 
-       g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_1, "dummy1_")->ifindex));
-       g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_1, "dummy2a")->ifindex));
+       g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
+       g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex));
        g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, NULL);
 
-       g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_2, "dummy1_")->ifindex));
+       g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
        g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, NULL);
-       g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_2, "dummy2b")->ifindex));
+       g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex));
 
        for (i = 0; i < 10; i++) {
                NMPlatform *pl;
@@ -2051,6 +2051,191 @@ test_netns_set_netns (gpointer fixture, gconstpointer test_data)
 
 /*****************************************************************************/
 
+static char *
+_get_current_namespace_id (int ns_type)
+{
+       const char *p;
+       GError *error = NULL;
+       char *id;
+
+       switch (ns_type) {
+       case CLONE_NEWNET:
+               p = "/proc/self/ns/net";
+               break;
+       case CLONE_NEWNS:
+               p = "/proc/self/ns/mnt";
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       id = g_file_read_link (p, &error);
+       g_assert_no_error (error);
+       g_assert (id);
+       return id;
+}
+
+static char *
+_get_sysctl_value (const char *path)
+{
+       char *data = NULL;
+       gs_free_error GError *error = NULL;
+
+       if (!g_file_get_contents (path, &data, NULL, &error)) {
+               nmtst_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, NULL);
+               g_assert (!data);
+       } else {
+               g_assert_no_error (error);
+               g_assert (data);
+               g_strstrip (data);
+       }
+       return data;
+}
+
+static void
+test_netns_push (gpointer fixture, gconstpointer test_data)
+{
+       gs_unref_object NMPlatform *platform_0 = NULL;
+       gs_unref_object NMPlatform *platform_1 = NULL;
+       gs_unref_object NMPlatform *platform_2 = NULL;
+       nm_auto_pop_netns NMPNetns *netns_pop = NULL;
+       gs_unref_ptrarray GPtrArray *device_names = g_ptr_array_new_with_free_func (g_free);
+       int i, j;
+       const int ns_types_list[] = { CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWNET | CLONE_NEWNS };
+       const int ns_types_test[] = { CLONE_NEWNET, CLONE_NEWNS };
+       typedef struct {
+               NMPlatform *platform;
+               const char *device_name;
+               const char *sysctl_path;
+               const char *sysctl_value;
+               const char *ns_net;
+               const char *ns_mnt;
+       } PlatformData;
+       PlatformData pl[3] = { };
+       PlatformData *pl_base;
+       struct {
+               PlatformData *pl;
+               int ns_types;
+       } stack[6] = { };
+       int nstack;
+
+       if (_test_netns_check_skip ())
+               return;
+
+       pl[0].platform = platform_0 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
+       pl[1].platform = platform_1 = _test_netns_create_platform ();
+       pl[2].platform = platform_2 = _test_netns_create_platform ();
+
+       pl_base = &pl[0];
+       i = nmtst_get_rand_int () % (G_N_ELEMENTS (pl) + 1);
+       if (i < G_N_ELEMENTS (pl)) {
+               pl_base = &pl[i];
+               g_assert (nm_platform_netns_push (pl[i].platform, &netns_pop));
+       }
+
+       for (i = 0; i < G_N_ELEMENTS (pl); i++) {
+               nm_auto_pop_netns NMPNetns *netns_free = NULL;
+               char *tmp;
+
+               g_assert (nm_platform_netns_push (pl[i].platform, &netns_free));
+
+               tmp = g_strdup_printf ("nmtst-dev-%d", i);
+               g_ptr_array_add (device_names, tmp);
+               pl[i].device_name = tmp;
+
+               tmp = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/disable_ipv6", pl[i].device_name);
+               g_ptr_array_add (device_names, tmp);
+               pl[i].sysctl_path = tmp;
+
+               pl[i].sysctl_value = nmtst_get_rand_int () % 2 ? "1" : "0";
+
+               _ADD_DUMMY (pl[i].platform, pl[i].device_name);
+
+               g_assert (nm_platform_sysctl_set (pl[i].platform, pl[i].sysctl_path, pl[i].sysctl_value));
+
+               tmp = _get_current_namespace_id (CLONE_NEWNET);
+               g_ptr_array_add (device_names, tmp);
+               pl[i].ns_net = tmp;
+
+               tmp = _get_current_namespace_id (CLONE_NEWNS);
+               g_ptr_array_add (device_names, tmp);
+               pl[i].ns_mnt = tmp;
+       }
+
+       nstack = nmtst_get_rand_int () % (G_N_ELEMENTS (stack) + 1);
+       for (i = 0; i < nstack; i++) {
+               stack[i].pl = &pl[nmtst_get_rand_int () % G_N_ELEMENTS (pl)];
+               stack[i].ns_types = ns_types_list[nmtst_get_rand_int () % G_N_ELEMENTS (ns_types_list)];
+
+               nmp_netns_push_type (nm_platform_netns_get (stack[i].pl->platform), stack[i].ns_types);
+       }
+
+       /* pop some again. */
+       for (i = nmtst_get_rand_int () % (nstack + 1); i > 0; i--) {
+               g_assert (nstack > 0);
+               nstack--;
+               nmp_netns_pop (nm_platform_netns_get (stack[nstack].pl->platform));
+       }
+
+       for (i = 0; i < G_N_ELEMENTS (ns_types_test); i++) {
+               int ns_type = ns_types_test[i];
+               PlatformData *p;
+               gs_free char *current_namespace_id = NULL;
+
+               p = pl_base;
+               for (j = nstack; j >= 1; ) {
+                       j--;
+                       if (NM_FLAGS_HAS (stack[j].ns_types, ns_type)) {
+                               p = stack[j].pl;
+                               break;
+                       }
+               }
+
+               current_namespace_id = _get_current_namespace_id (ns_type);
+
+               if (ns_type == CLONE_NEWNET) {
+                       g_assert_cmpstr (current_namespace_id, ==, p->ns_net);
+                       for (j = 0; j < G_N_ELEMENTS (pl); j++) {
+                               gs_free char *data = NULL;
+
+                               if (p == &pl[j])
+                                       g_assert_cmpint (nmtstp_run_command ("ip link show %s 1>/dev/null", pl[j].device_name), ==, 0);
+                               else
+                                       g_assert_cmpint (nmtstp_run_command ("ip link show %s 2>/dev/null", pl[j].device_name), !=, 0);
+
+                               data = _get_sysctl_value (pl[j].sysctl_path);
+                               if (p == &pl[j])
+                                       g_assert_cmpstr (data, ==, pl[j].sysctl_value);
+                               else
+                                       g_assert (!data);
+                       }
+               } else if (ns_type == CLONE_NEWNS) {
+                       g_assert_cmpstr (current_namespace_id, ==, p->ns_mnt);
+                       for (j = 0; j < G_N_ELEMENTS (pl); j++) {
+                               char path[600];
+                               gs_free char *data = NULL;
+
+                               nm_sprintf_buf (path, "/sys/devices/virtual/net/%s/ifindex", pl[j].device_name);
+
+                               data = _get_sysctl_value (path);
+                               if (p == &pl[j])
+                                       g_assert_cmpstr (data, ==, nm_sprintf_buf (path, "%d", nmtstp_link_get_typed (p->platform, 0, p->device_name, NM_LINK_TYPE_DUMMY)->ifindex));
+                               else
+                                       g_assert (!data);
+                       }
+               } else
+                       g_assert_not_reached ();
+       }
+
+
+       for (i = nstack; i >= 1; ) {
+               i--;
+               nmp_netns_pop (nm_platform_netns_get (stack[i].pl->platform));
+       }
+}
+
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
@@ -2100,5 +2285,6 @@ setup_tests (void)
 
                g_test_add_vtable ("/general/netns/general", 0, NULL, _test_netns_setup, test_netns_general, _test_netns_teardown);
                g_test_add_vtable ("/general/netns/set-netns", 0, NULL, _test_netns_setup, test_netns_set_netns, _test_netns_teardown);
+               g_test_add_vtable ("/general/netns/push", 0, NULL, _test_netns_setup, test_netns_push, _test_netns_teardown);
        }
 }