#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
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);
/*********************************************************************************************/
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);
} \
} 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;
}
}
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;
}
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;
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
/*********************************************************************************************/
-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;
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;
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;
}
nmp_netns_pop (NMPNetns *self)
{
NetnsInfo *info;
+ int ns_types;
g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
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);
/* 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 *
_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;
/*****************************************************************************/
+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)
{
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);
}
}