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:
@@ -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 *
|
||||
|
Reference in New Issue
Block a user