platform: support switching partial namespaces

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.
This commit is contained in:
Thomas Haller
2016-03-14 14:20:33 +01:00
parent a0cce2b195
commit 3428d8607d
3 changed files with 387 additions and 43 deletions

View File

@@ -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 *