diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c index d24faafc9f..5b1d6ad7e5 100644 --- a/src/platform/nmp-netns.c +++ b/src/platform/nmp-netns.c @@ -30,6 +30,37 @@ #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,6 +142,60 @@ _stack_ensure_init_impl (void) } \ } G_STMT_END +static NMPNetns * +_stack_current_netns (int ns_types) +{ + guint j; + + 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_peek (void) { @@ -120,16 +206,6 @@ _stack_peek (void) return NULL; } -static NetnsInfo * -_stack_peek2 (void) -{ - nm_assert (netns_stack); - - if (netns_stack->len > 1) - return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 2)); - return NULL; -} - static NetnsInfo * _stack_bottom (void) { @@ -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); + + ns_types = info->ns_types; _stack_pop (); - info = _stack_peek (); - nm_assert (info); - - return _netns_switch (info->netns, NULL); + return _netns_switch_pop (self, ns_types); } NMPNetns * diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h index bdd797daca..1625116f2e 100644 --- a/src/platform/nmp-netns.h +++ b/src/platform/nmp-netns.h @@ -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); diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c index 9da2404ddc..f7b9f6fb64 100644 --- a/src/platform/tests/test-link.c +++ b/src/platform/tests/test-link.c @@ -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); } }