diff options
Diffstat (limited to 'src/platform/nmp-netns-utils.c')
-rw-r--r-- | src/platform/nmp-netns-utils.c | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/src/platform/nmp-netns-utils.c b/src/platform/nmp-netns-utils.c new file mode 100644 index 0000000000..1a2243bb2c --- /dev/null +++ b/src/platform/nmp-netns-utils.c @@ -0,0 +1,484 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-platform.c - Handle runtime kernel networking configuration + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" +#include "nmp-netns-utils.h" + +#include <fcntl.h> +#include <errno.h> +#include <sys/mount.h> + +#include "NetworkManagerUtils.h" + +/*********************************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "netns" +#define _NMLOG(level, netns, ...) \ + G_STMT_START { \ + NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \ + NMPNetns *_netns = (netns); \ + char _sbuf[20]; \ + \ + _nm_log (_level, _NMLOG_DOMAIN, 0, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_netns ? nm_sprintf_buf (_sbuf, "[%p]", _netns) : "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } G_STMT_END + +/*********************************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE ( + PROP_FD_NET, + PROP_FD_MNT, +); + +typedef struct _NMPNetnsPrivate NMPNetnsPrivate; + +struct _NMPNetnsPrivate { + int fd_net; + int fd_mnt; +}; + +typedef struct { + NMPNetns *netns; + int count; +} NetnsInfo; + +static void _stack_push (NMPNetns *netns); +static NMPNetns *_netns_new (GError **error); + +/*********************************************************************************************/ + +static GArray *netns_stack = NULL; + +static void +_stack_ensure_init (void) +{ + if (G_UNLIKELY (!netns_stack)) { + NMPNetns *netns; + GError *error = NULL; + + netns_stack = g_array_new (FALSE, FALSE, sizeof (NetnsInfo)); + + /* at the bottom of the stack we must try to create a netns instance + * that we never pop. It's the base to which we need to return. */ + + netns = _netns_new (&error); + + if (!netns) { + /* don't know how to recover from this error. Netns are not supported. */ + _LOGE (NULL, "failed to create initial netns: %s", error->message); + g_clear_error (&error); + return; + } + + _stack_push (netns); + + /* we leak this instance. */ + nmp_netns_unref (netns); + } +} + +static NetnsInfo * +_stack_peek (void) +{ + nm_assert (netns_stack); + + if (netns_stack->len > 0) + return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1)); + 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) +{ + nm_assert (netns_stack); + + if (netns_stack->len > 0) + return &g_array_index (netns_stack, NetnsInfo, 0); + return NULL; +} + +static void +_stack_push (NMPNetns *netns) +{ + NetnsInfo *info; + + nm_assert (netns_stack); + nm_assert (NMP_IS_NETNS (netns)); + + g_array_set_size (netns_stack, netns_stack->len + 1); + + info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1)); + info->netns = nmp_netns_ref (netns); + info->count = 1; +} + +static void +_stack_pop (void) +{ + NetnsInfo *info; + + nm_assert (netns_stack); + nm_assert (netns_stack->len > 1); + + info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1)); + + nm_assert (NMP_IS_NETNS (info->netns)); + nm_assert (info->count == 1); + + nmp_netns_unref (info->netns); + + g_array_set_size (netns_stack, netns_stack->len - 1); +} + +static guint +_stack_size (void) +{ + nm_assert (netns_stack); + + return netns_stack->len; +} + +/*********************************************************************************************/ + +G_DEFINE_TYPE (NMPNetns, nmp_netns, G_TYPE_OBJECT); + +#define NMP_NETNS_GET_PRIVATE(o) ((o)->priv) + +/*********************************************************************************************/ + +NMPNetns * +nmp_netns_ref (NMPNetns *self) +{ + g_return_val_if_fail (NMP_IS_NETNS (self), NULL); + + g_object_ref (self); + return self; +} + +NMPNetns * +nmp_netns_unref (NMPNetns *self) +{ + if (self) { + g_return_val_if_fail (NMP_IS_NETNS (self), NULL); + + g_object_unref (self); + } + return NULL; +} + +/*********************************************************************************************/ + +static NMPNetns * +_netns_new (GError **error) +{ + NMPNetns *self; + int fd_net, fd_mnt; + int errsv; + + fd_net = open ("/proc/self/ns/net", O_RDONLY); + if (fd_net == -1) { + errsv = errno; + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Failed opening netns: %s", + g_strerror (errsv)); + return NULL; + } + + fd_mnt = open ("/proc/self/ns/mnt", O_RDONLY); + if (fd_mnt == -1) { + errsv = errno; + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Failed opening mntns: %s", + g_strerror (errsv)); + close (fd_net); + return NULL; + } + + self = g_object_new (NMP_TYPE_NETNS, + NMP_NETNS_FD_NET, fd_net, + NMP_NETNS_FD_MNT, fd_mnt, + NULL); + + _LOGD (self, "new netns (net:%d, mnt:%d)", fd_net, fd_mnt); + + return self; +} + +static gboolean +_netns_switch (NMPNetns *self, NMPNetns *netns_fail) +{ + int errsv; + + if (setns (self->priv->fd_net, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE (self, "failed to switch netns: %s", g_strerror (errsv)); + return FALSE; + } + /* try to fix the mess by returning to the previous netns. */ + if (setns (self->priv->fd_mnt, CLONE_NEWNS) != 0) { + errsv = errno; + _LOGE (self, "failed to switch mntns: %s", g_strerror (errsv)); + + if (netns_fail) { + if (setns (netns_fail->priv->fd_net, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE (netns_fail, "failed to restore netns: %s", g_strerror (errsv)); + } + } + return FALSE; + } + + return TRUE; +} + +/*********************************************************************************************/ + +gboolean +nmp_netns_push (NMPNetns *self) +{ + NetnsInfo *info; + + g_return_val_if_fail (NMP_IS_NETNS (self), FALSE); + + _stack_ensure_init (); + + info = _stack_peek (); + g_return_val_if_fail (info, FALSE); + + if (info->netns == self) { + info->count++; + _LOGt (self, "push (increase count to %d)", info->count); + return TRUE; + } + + _LOGD (self, "push (was %p)", info->netns); + + if (!_netns_switch (self, info->netns)) + return FALSE; + + _stack_push (self); + return TRUE; +} + +NMPNetns * +nmp_netns_new (void) +{ + NetnsInfo *info; + NMPNetns *self; + int errsv; + GError *error = NULL; + + _stack_ensure_init (); + + if (!_stack_peek ()) { + /* there are no netns instances. We cannot create a new one + * (because after unshare we couldn't return to the original one). */ + return NULL; + } + + if (unshare (CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE (NULL, "failed to create netns: %s", g_strerror (errsv)); + return NULL; + } + + if (unshare (CLONE_NEWNS) != 0) { + errsv = errno; + _LOGE (NULL, "failed to create ns: %s", g_strerror (errsv)); + goto err_out; + } + + if (mount ("", "/", "none", MS_SLAVE | MS_REC, NULL)) { + _LOGE (NULL, "failed mount --make-rslave: %s", error->message); + goto err_out; + } + + if (umount2 ("/sys", MNT_DETACH) < 0) { + _LOGE (NULL, "failed umount /sys: %s", error->message); + goto err_out; + } + + if (mount (NULL, "/sys", "sysfs", 0, NULL) < 0) { + _LOGE (NULL, "failed mount /sys: %s", error->message); + goto err_out; + } + + self = _netns_new (&error); + if (!self) { + _LOGE (NULL, "failed to create netns after unshare: %s", error->message); + g_clear_error (&error); + goto err_out; + } + + _stack_push (self); + + return self; +err_out: + info = _stack_peek (); + _netns_switch (info->netns, NULL); + return NULL; +} + +gboolean +nmp_netns_pop (NMPNetns *self) +{ + NetnsInfo *info; + + g_return_val_if_fail (NMP_IS_NETNS (self), FALSE); + + _stack_ensure_init (); + + info = _stack_peek (); + + g_return_val_if_fail (info, FALSE); + g_return_val_if_fail (info->netns == self, FALSE); + + if (info->count > 1) { + info->count--; + _LOGt (self, "pop (decrease count to %d)", 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 ()); + + _stack_pop (); + info = _stack_peek (); + + nm_assert (info); + + return _netns_switch (info->netns, NULL); +} + +NMPNetns * +nmp_netns_get_current (void) +{ + NetnsInfo *info; + + _stack_ensure_init (); + + info = _stack_peek (); + return info ? info->netns : NULL; +} + +NMPNetns * +nmp_netns_get_initial (void) +{ + NetnsInfo *info; + + _stack_ensure_init (); + + info = _stack_bottom (); + return info ? info->netns : NULL; +} + +/*********************************************************************************************/ + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMPNetns *self = NMP_NETNS (object); + + switch (prop_id) { + case PROP_FD_NET: + /* construct only */ + self->priv->fd_net = g_value_get_int (value); + g_return_if_fail (self->priv->fd_net > 0); + break; + case PROP_FD_MNT: + /* construct only */ + self->priv->fd_mnt = g_value_get_int (value); + g_return_if_fail (self->priv->fd_mnt > 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nmp_netns_init (NMPNetns *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NMP_TYPE_NETNS, NMPNetnsPrivate); +} + +static void +dispose (GObject *object) +{ + NMPNetns *self = NMP_NETNS (object); + + if (self->priv->fd_net > 0) { + close (self->priv->fd_net); + self->priv->fd_net = 0; + } + + if (self->priv->fd_mnt > 0) { + close (self->priv->fd_mnt); + self->priv->fd_mnt = 0; + } + + G_OBJECT_CLASS (nmp_netns_parent_class)->dispose (object); +} + +static void +nmp_netns_class_init (NMPNetnsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMPNetnsPrivate)); + + object_class->set_property = set_property; + object_class->dispose = dispose; + + obj_properties[PROP_FD_NET] + = g_param_spec_int (NMP_NETNS_FD_NET, "", "", + 0, G_MAXINT, 0, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_FD_MNT] + = g_param_spec_int (NMP_NETNS_FD_MNT, "", "", + 0, G_MAXINT, 0, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} |