Commit c7b38625 authored by Thomas Haller's avatar Thomas Haller

platform: add network namespace support to platform

Add a new NMPNetns class. This allows creation, deletion and
switching of network namespaces. The API only offers push/pop
operations to switch the namespace. This way the API enforces
the user to always restore the previous namespace.

A NMPlatform instance not only uses the netlink socket, but also
sysfs, udev, ethtool, mii. Still, a NMPlatform instance lives
entirely inside one namespace and is not spanning multiple namespaces.
To properly support network namespaces, the platform instance must
switch the namespace as necessary, transparent to the caller.
Udev is only supported in the main namespace.

For now, network namespaces are not actually used and are disabled
via the NM_PLATFORM_NETNS_SUPPORT argument.

https://bugzilla.gnome.org/show_bug.cgi?id=762408
parent 1a1c5fb7
......@@ -311,6 +311,8 @@ libNetworkManager_la_SOURCES = \
dnsmasq-manager/nm-dnsmasq-utils.c \
dnsmasq-manager/nm-dnsmasq-utils.h \
\
platform/nmp-netns.c \
platform/nmp-netns.h \
platform/nm-fake-platform.c \
platform/nm-fake-platform.h \
platform/nm-linux-platform.c \
......@@ -541,6 +543,8 @@ libnm_iface_helper_la_SOURCES = \
platform/nm-platform.h \
platform/nm-platform-utils.c \
platform/nm-platform-utils.h \
platform/nmp-netns.c \
platform/nmp-netns.h \
platform/nmp-object.c \
platform/nmp-object.h \
platform/wifi/wifi-utils-nl80211.c \
......
......@@ -79,7 +79,7 @@ typedef struct _NMPlatformIP4Route NMPlatformIP4Route;
typedef struct _NMPlatformIP6Address NMPlatformIP6Address;
typedef struct _NMPlatformIP6Route NMPlatformIP6Route;
typedef struct _NMPlatformLink NMPlatformLink;
typedef struct _NMPNetns NMPNetns;
typedef struct _NMPObject NMPObject;
typedef enum {
......
This diff is collapsed.
......@@ -41,6 +41,7 @@
#include "nm-enum-types.h"
#include "nm-platform-utils.h"
#include "nmp-object.h"
#include "nmp-netns.h"
/*****************************************************************************/
......@@ -89,6 +90,7 @@ static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
enum {
PROP_0,
PROP_NETNS_SUPPORT,
PROP_REGISTER_SINGLETON,
LAST_PROP,
};
......@@ -2128,6 +2130,10 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
/* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */
if (out_peer_ifindex) {
nm_auto_pop_netns NMPNetns *netns = NULL;
if (!nm_platform_netns_push (self, &netns))
return FALSE;
peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->name);
if (peer_ifindex <= 0)
return FALSE;
......@@ -2393,16 +2399,24 @@ _to_string_ifa_flags (guint32 ifa_flags, char *buf, gsize size)
gboolean
nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, const char *ifname, NMSettingWiredWakeOnLan wol, const char *wol_password)
{
nm_auto_pop_netns NMPNetns *netns = NULL;
_CHECK_SELF (self, klass, FALSE);
if (!nm_platform_netns_push (self, &netns))
return FALSE;
return nmp_utils_ethtool_set_wake_on_lan (ifname, wol, wol_password);
}
gboolean
nm_platform_ethtool_get_link_speed (NMPlatform *self, const char *ifname, guint32 *out_speed)
{
nm_auto_pop_netns NMPNetns *netns = NULL;
_CHECK_SELF (self, klass, FALSE);
if (!nm_platform_netns_push (self, &netns))
return FALSE;
return nmp_utils_ethtool_get_link_speed (ifname, out_speed);
}
......@@ -4018,6 +4032,31 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform
/******************************************************************/
NMPNetns *
nm_platform_netns_get (NMPlatform *self)
{
_CHECK_SELF (self, klass, NULL);
return self->_netns;
}
gboolean
nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns)
{
g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
if ( platform->_netns
&& !nmp_netns_push (platform->_netns)) {
NM_SET_OUT (netns, NULL);
return FALSE;
}
NM_SET_OUT (netns, platform->_netns);
return TRUE;
}
/******************************************************************/
static gboolean
_vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
{
......@@ -4117,9 +4156,20 @@ static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (object);
NMPlatform *self = NM_PLATFORM (object);
NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self);
switch (prop_id) {
case PROP_NETNS_SUPPORT:
/* construct-only */
if (g_value_get_boolean (value)) {
NMPNetns *netns;
netns = nmp_netns_get_current ();
if (netns)
self->_netns = g_object_ref (netns);
}
break;
case PROP_REGISTER_SINGLETON:
/* construct-only */
priv->register_singleton = g_value_get_boolean (value);
......@@ -4147,6 +4197,14 @@ nm_platform_init (NMPlatform *object)
{
}
static void
finalize (GObject *object)
{
NMPlatform *self = NM_PLATFORM (object);
g_clear_object (&self->_netns);
}
static void
nm_platform_class_init (NMPlatformClass *platform_class)
{
......@@ -4156,9 +4214,18 @@ nm_platform_class_init (NMPlatformClass *platform_class)
object_class->set_property = set_property;
object_class->constructed = constructed;
object_class->finalize = finalize;
platform_class->wifi_set_powersave = wifi_set_powersave;
g_object_class_install_property
(object_class, PROP_NETNS_SUPPORT,
g_param_spec_boolean (NM_PLATFORM_NETNS_SUPPORT, "", "",
FALSE,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property
(object_class, PROP_REGISTER_SINGLETON,
g_param_spec_boolean (NM_PLATFORM_REGISTER_SINGLETON, "", "",
......
......@@ -42,6 +42,7 @@
/******************************************************************/
#define NM_PLATFORM_NETNS_SUPPORT "netns-support"
#define NM_PLATFORM_REGISTER_SINGLETON "register-singleton"
/******************************************************************/
......@@ -462,6 +463,8 @@ typedef struct {
struct _NMPlatform {
GObject parent;
NMPNetns *_netns;
};
typedef struct {
......@@ -669,6 +672,9 @@ _nm_platform_uint8_inv (guint8 scope)
return (guint8) ~scope;
}
NMPNetns *nm_platform_netns_get (NMPlatform *self);
gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
const char *nm_link_type_to_string (NMLinkType link_type);
const char *_nm_platform_error_to_string (NMPlatformError error);
......
This diff is collapsed.
/* -*- 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.
*/
#ifndef __NMP_NETNS_UTILS_H__
#define __NMP_NETNS_UTILS_H__
/*****************************************************************************/
#define NMP_TYPE_NETNS (nmp_netns_get_type ())
#define NMP_NETNS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMP_TYPE_NETNS, NMPNetns))
#define NMP_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NMP_TYPE_NETNS, NMPNetnsClass))
#define NMP_IS_NETNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMP_TYPE_NETNS))
#define NMP_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMP_TYPE_NETNS))
#define NMP_NETNS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NMP_TYPE_NETNS, NMPNetnsClass))
#define NMP_NETNS_FD_NET "fd-net"
#define NMP_NETNS_FD_MNT "fd-mnt"
struct _NMPNetnsPrivate;
struct _NMPNetns {
GObject parent;
struct _NMPNetnsPrivate *priv;
};
typedef struct {
GObjectClass parent;
} NMPNetnsClass;
GType nmp_netns_get_type (void);
NMPNetns *nmp_netns_new (void);
gboolean nmp_netns_push (NMPNetns *self);
gboolean nmp_netns_pop (NMPNetns *self);
NMPNetns *nmp_netns_get_current (void);
NMPNetns *nmp_netns_get_initial (void);
gboolean nmp_netns_is_initial (void);
int nmp_netns_get_fd_net (NMPNetns *self);
int nmp_netns_get_fd_mnt (NMPNetns *self);
static inline void
_nm_auto_pop_netns (NMPNetns **p)
{
if (*p)
nmp_netns_pop (*p);
}
#define nm_auto_pop_netns __attribute__((cleanup(_nm_auto_pop_netns)))
#endif /* __NMP_NETNS_UTILS_H__ */
......@@ -23,6 +23,8 @@
#include <sched.h>
#include "nmp-object.h"
#include "nmp-netns.h"
#include "nm-platform-utils.h"
#include "test-common.h"
#include "nm-test-utils.h"
......@@ -1846,6 +1848,138 @@ again:
nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
}
/******************************************************************/
static void
test_netns_general_setup (gpointer fixture, gconstpointer test_data)
{
/* the singleton platform instance has netns support disabled.
* Destroy the instance before the test and re-create it afterwards. */
g_object_unref (nm_platform_get ());
}
static void
test_netns_general_teardown (gpointer fixture, gconstpointer test_data)
{
/* re-create platform instance */
SETUP ();
}
static void
test_netns_general (gpointer fixture, gconstpointer test_data)
{
gs_unref_object NMPlatform *platform_1 = NULL;
gs_unref_object NMPlatform *platform_2 = NULL;
gs_unref_object NMPNetns *netns_2 = NULL;
NMPNetns *netns_tmp;
char sbuf[100];
int i, j, k, errsv;
gboolean ethtool_support;
netns_tmp = nmp_netns_get_current ();
if (!netns_tmp) {
g_test_skip ("No netns support");
return;
}
g_assert (nmp_netns_get_fd_net (netns_tmp) > 0);
if (setns (nmp_netns_get_fd_net (netns_tmp), CLONE_NEWNET) != 0) {
errsv = errno;
_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (errsv));
g_test_skip ("No netns support (setns failed)");
return;
}
platform_1 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
netns_2 = nmp_netns_new ();
platform_2 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
nmp_netns_pop (netns_2);
/* add some dummy devices. The "other-*" devices are there to bump the ifindex */
for (k = 0; k < 2; k++) {
NMPlatform *p = (k == 0 ? platform_1 : platform_2);
const char *id = (k == 0 ? "a" : "b");
#define _ADD_DUMMY(platform, name) \
g_assert_cmpint (nm_platform_link_dummy_add ((platform), (name), NULL), ==, NM_PLATFORM_ERROR_SUCCESS)
for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-a-%s-%02d", id, i));
_ADD_DUMMY (p, "dummy1_");
for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-b-%s-%02d", id, i));
_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "dummy2%s", id));
for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i));
#undef _ADD_DUMMY
}
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/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/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));
for (i = 0; i < 10; i++) {
NMPlatform *pl;
const char *path;
j = nmtst_get_rand_int () % 2;
if (nmtst_get_rand_int () % 2) {
pl = platform_1;
if (nmtst_get_rand_int () % 2)
path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
else
path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6";
} else {
pl = platform_2;
if (nmtst_get_rand_int () % 2)
path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
else
path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6";
}
g_assert (nm_platform_sysctl_set (pl, path, nm_sprintf_buf (sbuf, "%d", j)));
g_assert_cmpstr (nm_platform_sysctl_get (pl, path), ==, nm_sprintf_buf (sbuf, "%d", j));
}
g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"), ==, NULL);
g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"), ==, NULL);
/* older kernels (Ubuntu 12.04) don't support ethtool -i for dummy devices. Work around that and
* skip asserts that are known to fail. */
ethtool_support = nmtstp_run_command ("ethtool -i dummy1_ > /dev/null") == 0;
if (ethtool_support) {
g_assert ( nmp_utils_ethtool_get_driver_info ("dummy1_", NULL, NULL, NULL));
g_assert ( nmp_utils_ethtool_get_driver_info ("dummy2a", NULL, NULL, NULL));
g_assert (!nmp_utils_ethtool_get_driver_info ("dummy2b", NULL, NULL, NULL));
g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy1_ > /dev/null"), ==, 0);
g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2a > /dev/null"), ==, 0);
g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2b 2> /dev/null"), !=, 0);
}
g_assert (nm_platform_netns_push (platform_2, &netns_tmp));
g_assert (netns_tmp == netns_2);
if (ethtool_support) {
g_assert ( nmp_utils_ethtool_get_driver_info ("dummy1_", NULL, NULL, NULL));
g_assert (!nmp_utils_ethtool_get_driver_info ("dummy2a", NULL, NULL, NULL));
g_assert ( nmp_utils_ethtool_get_driver_info ("dummy2b", NULL, NULL, NULL));
g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy1_ > /dev/null"), ==, 0);
g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2a 2> /dev/null"), !=, 0);
g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2b > /dev/null"), ==, 0);
}
nmp_netns_pop (netns_tmp);
}
/*****************************************************************************/
void
......@@ -1894,5 +2028,7 @@ setup_tests (void)
g_test_add_func ("/link/nl-bugs/veth", test_nl_bugs_veth);
g_test_add_func ("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink);
g_test_add_func ("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink);
g_test_add_vtable ("/general/netns/general", 0, NULL, test_netns_general_setup, test_netns_general, test_netns_general_teardown);
}
}
......@@ -139,6 +139,14 @@ if [ $RESULT -ne 0 -a $RESULT -ne 77 ]; then
exit $RESULT
fi
if [ $HAS_ERRORS -eq 0 ]; then
# valgrind doesn't support setns syscall and spams the logfile.
# hack around it...
if [ "$(basename "$TEST")" = 'test-link-linux' -a -z "$(sed -e '/^--[0-9]\+-- WARNING: unhandled .* syscall: /,/^--[0-9]\+-- it at http.*\.$/d' "$LOGFILE")" ]; then
HAS_ERRORS=1
fi
fi
if [ $HAS_ERRORS -eq 0 ]; then
# shouldn't actually happen...
echo "valgrind succeeded, but log is not empty: '`realpath "$LOGFILE"`'" >&2
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment