summaryrefslogtreecommitdiff
path: root/src/platform/nmp-netns-utils.c
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2016-02-19 01:06:28 +0100
committerThomas Haller <thaller@redhat.com>2016-02-20 00:19:09 +0100
commit6635e54d613304c3b319d822d6ae1a93d016e968 (patch)
tree7d58a573cf408dff8a2a9a3c60ebc55fab940a4d /src/platform/nmp-netns-utils.c
parent94d6aaac70ecec4baca6a072d32825287f8ee109 (diff)
downloadNetworkManager-th/platform-netns.tar.gz
platform: add network namespace support to platformth/platform-netns
Platform not only uses the netlink socket, but also sysfs, udev, ethtool, mii. To properly support network namespaces, we must switch the namespace as necessary. In case of udev, it is only supported on the main namespace.
Diffstat (limited to 'src/platform/nmp-netns-utils.c')
-rw-r--r--src/platform/nmp-netns-utils.c484
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);
+}