diff --git a/configure.ac b/configure.ac index 034bd688f7..6fa6569973 100644 --- a/configure.ac +++ b/configure.ac @@ -679,6 +679,8 @@ src/settings/plugins/keyfile/tests/Makefile src/settings/plugins/keyfile/tests/keyfiles/Makefile src/settings/plugins/example/Makefile src/settings/tests/Makefile +src/platform/Makefile +src/platform/tests/Makefile src/wimax/Makefile libnm-util/libnm-util.pc libnm-util/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index ec76d13a7b..113b527606 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS= \ logging \ config \ posix-signals \ + platform \ dns-manager \ vpn-manager \ dhcp-manager \ @@ -37,6 +38,7 @@ INCLUDES = -I${top_srcdir} \ -I${top_srcdir}/src/dhcp-manager \ -I${top_srcdir}/src/ip6-manager \ -I${top_srcdir}/src/supplicant-manager \ + -I${top_srcdir}/src/platform \ -I${top_srcdir}/src/dnsmasq-manager \ -I${top_srcdir}/src/modem-manager \ -I$(top_srcdir)/src/bluez-manager \ @@ -326,6 +328,7 @@ NetworkManager_LDADD = \ ./logging/libnm-logging.la \ ./config/libnm-config.la \ ./posix-signals/libnm-posix-signals.la \ + ./platform/libnm-platform.la \ ./dns-manager/libdns-manager.la \ ./vpn-manager/libvpn-manager.la \ ./dhcp-manager/libdhcp-manager.la \ diff --git a/src/main.c b/src/main.c index 16f1834225..ed71bb62e6 100644 --- a/src/main.c +++ b/src/main.c @@ -42,6 +42,7 @@ #include "NetworkManagerUtils.h" #include "nm-manager.h" #include "nm-policy.h" +#include "nm-linux-platform.h" #include "nm-dns-manager.h" #include "nm-dbus-manager.h" #include "nm-supplicant-manager.h" @@ -484,6 +485,9 @@ main (int argc, char *argv[]) main_loop = g_main_loop_new (NULL, FALSE); + /* Set up platform interaction layer */ + nm_linux_platform_setup (); + /* Initialize our DBus service & connection */ dbus_mgr = nm_dbus_manager_get (); g_assert (dbus_mgr != NULL); diff --git a/src/platform/Makefile.am b/src/platform/Makefile.am new file mode 100644 index 0000000000..ef1b4622d6 --- /dev/null +++ b/src/platform/Makefile.am @@ -0,0 +1,30 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = \ + -I${top_srcdir} \ + -I${top_srcdir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/libnm-util \ + -DKERNEL_HACKS=1 + +@GNOME_CODE_COVERAGE_RULES@ + +noinst_LTLIBRARIES = libnm-platform.la + +libnm_platform_la_SOURCES = \ + nm-platform.h \ + nm-platform.c \ + nm-fake-platform.h \ + nm-fake-platform.c \ + nm-linux-platform.h \ + nm-linux-platform.c + +libnm_platform_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(GLIB_CFLAGS) \ + $(LIBNL_CFLAGS) + +libnm_platform_la_LIBADD = \ + $(top_builddir)/src/logging/libnm-logging.la \ + $(GLIB_LIBS) \ + $(LIBNL_LIBS) diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c new file mode 100644 index 0000000000..b591918a9e --- /dev/null +++ b/src/platform/nm-fake-platform.c @@ -0,0 +1,216 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-platform-fake.c - Fake platform interaction code for testing NetworkManager + * + * 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) 2012–2013 Red Hat, Inc. + */ + +#include +#include +#include +#include + +#include "nm-fake-platform.h" +#include "nm-logging.h" + +#define debug(format, ...) nm_log_dbg (LOGD_PLATFORM, format, __VA_ARGS__) + +typedef struct { + GArray *links; +} NMFakePlatformPrivate; + +#define NM_FAKE_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_FAKE_PLATFORM, NMFakePlatformPrivate)) + +G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM) + +/******************************************************************/ + +void +nm_fake_platform_setup (void) +{ + nm_platform_setup (NM_TYPE_FAKE_PLATFORM); +} + +/******************************************************************/ + +static void +link_init (NMPlatformLink *device, int ifindex, int type, const char *name) +{ + g_assert (!name || strlen (name) < sizeof(device->name)); + + memset (device, 0, sizeof (*device)); + + device->ifindex = name ? ifindex : 0; + device->type = type; + if (name) + strcpy (device->name, name); +} + +static NMPlatformLink * +link_get (NMPlatform *platform, int ifindex) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform); + NMPlatformLink *device; + + if (ifindex >= priv->links->len) + goto not_found; + device = &g_array_index (priv->links, NMPlatformLink, ifindex); + if (!device->ifindex) + goto not_found; + + return device; +not_found: + debug ("link not found: %d", ifindex); + platform->error = NM_PLATFORM_ERROR_NOT_FOUND; + return NULL; +} + +static GArray * +link_get_all (NMPlatform *platform) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform); + GArray *links = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), priv->links->len); + int i; + + for (i = 0; i < priv->links->len; i++) + if (g_array_index (priv->links, NMPlatformLink, i).ifindex) + g_array_append_val (links, g_array_index (priv->links, NMPlatformLink, i)); + + return links; +} + +static gboolean +link_add (NMPlatform *platform, const char *name, NMLinkType type) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform); + NMPlatformLink device; + + link_init (&device, priv->links->len, type, name); + + g_array_append_val (priv->links, device); + + if (device.ifindex) + g_signal_emit_by_name (platform, NM_PLATFORM_LINK_ADDED, &device); + + return TRUE; +} + +static gboolean +link_delete (NMPlatform *platform, int ifindex) +{ + NMPlatformLink *device = link_get (platform, ifindex); + NMPlatformLink deleted_device; + + if (!device) + return FALSE; + + memcpy (&deleted_device, device, sizeof (deleted_device)); + memset (device, 0, sizeof (*device)); + + g_signal_emit_by_name (platform, NM_PLATFORM_LINK_REMOVED, &deleted_device); + + return TRUE; +} + +static int +link_get_ifindex (NMPlatform *platform, const char *name) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform); + int i; + + for (i = 0; i < priv->links->len; i++) { + NMPlatformLink *device = &g_array_index (priv->links, NMPlatformLink, i); + + if (device && !g_strcmp0 (device->name, name)) + return device->ifindex; + } + + return 0; +} + +static const char * +link_get_name (NMPlatform *platform, int ifindex) +{ + NMPlatformLink *device = link_get (platform, ifindex); + + return device ? device->name : NULL; +} + +static NMLinkType +link_get_type (NMPlatform *platform, int ifindex) +{ + NMPlatformLink *device = link_get (platform, ifindex); + + return device ? device->type : NM_LINK_TYPE_NONE; +} + +/******************************************************************/ + +static void +nm_fake_platform_init (NMFakePlatform *fake_platform) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (fake_platform); + + priv->links = g_array_new (TRUE, TRUE, sizeof (NMPlatformLink)); +} + +static gboolean +setup (NMPlatform *platform) +{ + /* skip zero element */ + link_add (platform, NULL, NM_LINK_TYPE_NONE); + + /* add loopback interface */ + link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK); + + /* add some ethernets */ + link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET); + link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET); + link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET); + + return TRUE; +} + +static void +nm_fake_platform_finalize (GObject *object) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (object); + + g_array_unref (priv->links); + + G_OBJECT_CLASS (nm_fake_platform_parent_class)->finalize (object); +} + +static void +nm_fake_platform_class_init (NMFakePlatformClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMPlatformClass *platform_class = NM_PLATFORM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMFakePlatformPrivate)); + + /* virtual methods */ + object_class->finalize = nm_fake_platform_finalize; + + platform_class->setup = setup; + + platform_class->link_get_all = link_get_all; + platform_class->link_add = link_add; + platform_class->link_delete = link_delete; + platform_class->link_get_ifindex = link_get_ifindex; + platform_class->link_get_name = link_get_name; + platform_class->link_get_type = link_get_type; +} diff --git a/src/platform/nm-fake-platform.h b/src/platform/nm-fake-platform.h new file mode 100644 index 0000000000..c9fa42eb6e --- /dev/null +++ b/src/platform/nm-fake-platform.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-fake-platform.h - Fake platform interaction code for testing NetworkManager + * + * 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) 2012 Red Hat, Inc. + */ + +#ifndef NM_FAKE_PLATFORM_H +#define NM_FAKE_PLATFORM_H + +#include "nm-platform.h" + +#define NM_TYPE_FAKE_PLATFORM (nm_fake_platform_get_type ()) +#define NM_FAKE_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_FAKE_PLATFORM, NMFakePlatform)) +#define NM_FAKE_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_FAKE_PLATFORM, NMFakePlatformClass)) +#define NM_IS_FAKE_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_FAKE_PLATFORM)) +#define NM_IS_FAKE_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_FAKE_PLATFORM)) +#define NM_FAKE_PLATFORM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_FAKE_PLATFORM, NMFakePlatformClass)) + +/******************************************************************/ + +typedef struct { + NMPlatform parent; +} NMFakePlatform; + +typedef struct { + NMPlatformClass parent; +} NMFakePlatformClass; + +/******************************************************************/ + +GType nm_fake_platform_get_type (void); + +void nm_fake_platform_setup (void); + +#endif /* NM_FAKE_PLATFORM_H */ diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c new file mode 100644 index 0000000000..8df473578c --- /dev/null +++ b/src/platform/nm-linux-platform.c @@ -0,0 +1,701 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-linux-platform.c - Linux kernel & udev network configuration layer + * + * 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) 2012-2013 Red Hat, Inc. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-linux-platform.h" +#include "nm-logging.h" + +/* This is only included for the translation of VLAN flags */ +#include "nm-setting-vlan.h" + +#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__) +#define warning(...) nm_log_warn (LOGD_PLATFORM, __VA_ARGS__) +#define error(...) nm_log_err (LOGD_PLATFORM, __VA_ARGS__) + +typedef struct { + struct nl_sock *nlh; + struct nl_sock *nlh_event; + struct nl_cache *link_cache; + GIOChannel *event_channel; + guint event_id; +} NMLinuxPlatformPrivate; + +#define NM_LINUX_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate)) + +G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM) + +void +nm_linux_platform_setup (void) +{ + nm_platform_setup (NM_TYPE_LINUX_PLATFORM); +} + +/******************************************************************/ + +/* libnl library workarounds and additions */ + +/* Automatic deallocation of local variables */ +#define auto_nl_object __attribute__((cleanup(put_nl_object))) +static void +put_nl_object (void *ptr) +{ + struct nl_object **object = ptr; + + if (object && *object) { + nl_object_put (*object); + *object = NULL; + } +} + +/* libnl doesn't use const where due */ +#define nl_addr_build(family, addr, addrlen) nl_addr_build (family, (gpointer) addr, addrlen) + +typedef enum { + LINK, + N_TYPES +} ObjectType; + +typedef enum { + ADDED, + CHANGED, + REMOVED, + N_STATUSES +} ObjectStatus; + +static ObjectType +object_type_from_nl_object (const struct nl_object *object) +{ + g_assert (object); + + if (!strcmp (nl_object_get_type (object), "route/link")) + return LINK; + else + g_assert_not_reached (); +} + +/* libnl inclues LINK_ATTR_FAMILY in oo_id_attrs of link_obj_ops and thus + * refuses to search for items that lack this attribute. I believe this is a + * bug or a bad design at the least. Address family is not an identifying + * attribute of a network interface and IMO is not an attribute of a network + * interface at all. + */ +static struct nl_object * +nm_nl_cache_search (struct nl_cache *cache, struct nl_object *needle) +{ + if (object_type_from_nl_object (needle) == LINK) + rtnl_link_set_family ((struct rtnl_link *) needle, AF_UNSPEC); + + return nl_cache_search (cache, needle); +} +#define nl_cache_search nm_nl_cache_search + +/* Ask the kernel for an object identical (as in nl_cache_identical) to the + * needle argument. This is a kernel counterpart for nl_cache_search. + * + * libnl 3.2 doesn't seem to provide such functionality. + */ +static struct nl_object * +get_kernel_object (struct nl_sock *sock, struct nl_object *needle) +{ + + switch (object_type_from_nl_object (needle)) { + case LINK: + { + struct nl_object *kernel_object; + int ifindex = rtnl_link_get_ifindex ((struct rtnl_link *) needle); + const char *name = rtnl_link_get_name ((struct rtnl_link *) needle); + int nle; + + nle = rtnl_link_get_kernel (sock, ifindex, name, (struct rtnl_link **) &kernel_object); + switch (nle) { + case -NLE_SUCCESS: + return kernel_object; + case -NLE_NODEV: + return NULL; + default: + error ("Netlink error: %s", nl_geterror (nle)); + return NULL; + } + } + default: + /* Fallback to a one-time cache allocation. */ + { + struct nl_cache *cache; + struct nl_object *object; + int nle; + + nle = nl_cache_alloc_and_fill ( + nl_cache_ops_lookup (nl_object_get_type (needle)), + sock, &cache); + g_return_val_if_fail (!nle, NULL); + object = nl_cache_search (cache, needle); + + nl_cache_put (cache); + return object; + } + } +} + +/* libnl 3.2 doesn't seem to provide such a generic way to add libnl-route objects. */ +static gboolean +add_kernel_object (struct nl_sock *sock, struct nl_object *object) +{ + switch (object_type_from_nl_object (object)) { + case LINK: + return rtnl_link_add (sock, (struct rtnl_link *) object, NLM_F_CREATE); + default: + g_assert_not_reached (); + } +} + +/* libnl 3.2 doesn't seem to provide such a generic way to delete libnl-route objects. */ +static int +delete_kernel_object (struct nl_sock *sock, struct nl_object *object) +{ + switch (object_type_from_nl_object (object)) { + case LINK: + return rtnl_link_delete (sock, (struct rtnl_link *) object); + default: + g_assert_not_reached (); + } +} + +/******************************************************************/ + +/* Object type specific utilities */ + +static const char * +type_to_string (NMLinkType type) +{ + switch (type) { + case NM_LINK_TYPE_DUMMY: + return "dummy"; + default: + g_warning ("Wrong type: %d", type); + return NULL; + } +} + +static NMLinkType +link_extract_type (struct rtnl_link *rtnllink) +{ + const char *type; + + if (!rtnllink) + return NM_LINK_TYPE_NONE; + + type = rtnl_link_get_type (rtnllink); + + if (!type) + switch (rtnl_link_get_arptype (rtnllink)) { + case ARPHRD_LOOPBACK: + return NM_LINK_TYPE_LOOPBACK; + case ARPHRD_ETHER: + return NM_LINK_TYPE_ETHERNET; + default: + return NM_LINK_TYPE_GENERIC; + } + else if (!g_strcmp0 (type, "dummy")) + return NM_LINK_TYPE_DUMMY; + else + return NM_LINK_TYPE_UNKNOWN; +} + +static void +link_init (NMPlatformLink *info, struct rtnl_link *rtnllink) +{ + memset (info, 0, sizeof (*info)); + + g_assert (rtnllink); + + info->ifindex = rtnl_link_get_ifindex (rtnllink); + strcpy (info->name, rtnl_link_get_name (rtnllink)); + info->type = link_extract_type (rtnllink); +} + +/******************************************************************/ + +/* Object and cache manipulation */ + +static const char *signal_by_type_and_status[N_TYPES][N_STATUSES] = { + { NM_PLATFORM_LINK_ADDED, NM_PLATFORM_LINK_CHANGED, NM_PLATFORM_LINK_REMOVED }, +}; + +static struct nl_cache * +choose_cache (NMPlatform *platform, struct nl_object *object) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + switch (object_type_from_nl_object (object)) { + case LINK: + return priv->link_cache; + default: + g_assert_not_reached (); + } +} + +static void +announce_object (NMPlatform *platform, const struct nl_object *object, ObjectStatus status) +{ + ObjectType object_type = object_type_from_nl_object (object); + const char *sig = signal_by_type_and_status[object_type][status]; + + switch (object_type) { + case LINK: + { + NMPlatformLink device; + + link_init (&device, (struct rtnl_link *) object); + g_signal_emit_by_name (platform, sig, &device); + } + return; + default: + error ("Announcing object: object type unknown: %d", object_type); + } +} + +static gboolean +process_nl_error (NMPlatform *platform, int nle) +{ + /* NLE_EXIST is considered equivalent to success to avoid race conditions. You + * never know when something sends an identical object just before + * NetworkManager, e.g. from a dispatcher script. + */ + switch (nle) { + case -NLE_SUCCESS: + case -NLE_EXIST: + return FALSE; + default: + error ("Netlink error: %s", nl_geterror (nle)); + return TRUE; + } +} + +static gboolean +refresh_object (NMPlatform *platform, struct nl_object *object, int nle) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + auto_nl_object struct nl_object *cached_object = NULL; + auto_nl_object struct nl_object *kernel_object = NULL; + struct nl_cache *cache; + + if (process_nl_error (platform, nle)) + return FALSE; + + cache = choose_cache (platform, object); + cached_object = nl_cache_search (choose_cache (platform, object), object); + kernel_object = get_kernel_object (priv->nlh, object); + + g_return_val_if_fail (kernel_object, FALSE); + + if (cached_object) { + nl_cache_remove (cached_object); + nle = nl_cache_add (cache, kernel_object); + g_return_val_if_fail (!nle, 0); + } else { + nle = nl_cache_add (cache, kernel_object); + g_return_val_if_fail (!nle, FALSE); + } + + announce_object (platform, kernel_object, cached_object ? CHANGED : ADDED); + + return TRUE; +} + +/* Decreases the reference count if @obj for convenience */ +static gboolean +add_object (NMPlatform *platform, struct nl_object *obj) +{ + auto_nl_object struct nl_object *object = obj; + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + return refresh_object (platform, object, add_kernel_object (priv->nlh, object)); +} + +/* Decreases the reference count if @obj for convenience */ +static gboolean +delete_object (NMPlatform *platform, struct nl_object *obj) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + auto_nl_object struct nl_object *object = obj; + auto_nl_object struct nl_object *cached_object; + + cached_object = nl_cache_search (choose_cache (platform, object), object); + g_assert (cached_object); + + if (process_nl_error (platform, delete_kernel_object (priv->nlh, cached_object))) + return FALSE; + + nl_cache_remove (cached_object); + announce_object (platform, cached_object, REMOVED); + + return TRUE; +} + +static void +ref_object (struct nl_object *obj, void *data) +{ + struct nl_object **out = data; + + nl_object_get (obj); + *out = obj; +} + +/* This function does all the magic to avoid race conditions caused + * by concurrent usage of synchronous commands and an asynchronous cache. This + * might be a nice future addition to libnl but it requires to do all operations + * through the cache manager. In this case, nm-linux-platform serves as the + * cache manager instead of the one provided by libnl. + */ +static int +event_notification (struct nl_msg *msg, gpointer user_data) +{ + NMPlatform *platform = NM_PLATFORM (user_data); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + struct nl_cache *cache; + auto_nl_object struct nl_object *object = NULL; + auto_nl_object struct nl_object *cached_object = NULL; + auto_nl_object struct nl_object *kernel_object = NULL; + int event; + int nle; + + event = nlmsg_hdr (msg)->nlmsg_type; + nl_msg_parse (msg, ref_object, &object); + g_return_val_if_fail (object, NL_OK); + + cache = choose_cache (platform, object); + cached_object = nl_cache_search (cache, object); + kernel_object = get_kernel_object (priv->nlh, object); + + debug ("netlink event (type %d)", event); + + /* Removed object */ + switch (event) { + case RTM_DELLINK: + /* Ignore inconsistent deletion + * + * Quick external deletion and addition can be occasionally + * seen as just a change. + */ + if (kernel_object) + return NL_OK; + /* Ignore internal deletion */ + if (!cached_object) + return NL_OK; + + nl_cache_remove (cached_object); + announce_object (platform, cached_object, REMOVED); + + return NL_OK; + case RTM_NEWLINK: + /* Ignore inconsistent addition or change (kernel will send a good one) + * + * Quick sequence of RTM_NEWLINK notifications can be occasionally + * collapsed to just one addition or deletion, depending of whether we + * already have the object in cache. + */ + if (!kernel_object) + return NL_OK; + /* Handle external addition */ + if (!cached_object) { + nle = nl_cache_add (cache, kernel_object); + if (nle) { + error ("netlink cache error: %s", nl_geterror (nle)); + return NL_OK; + } + announce_object (platform, kernel_object, ADDED); + return NL_OK; + } + /* Ignore non-change + * + * This also catches notifications for internal addition or change, unless + * another action occured very soon after it. + */ + if (!nl_object_diff (kernel_object, cached_object)) + return NL_OK; + /* Handle external change */ + nl_cache_remove (cached_object); + nle = nl_cache_add (cache, kernel_object); + if (nle) { + error ("netlink cache error: %s", nl_geterror (nle)); + return NL_OK; + } + announce_object (platform, kernel_object, CHANGED); + + return NL_OK; + default: + error ("Unknown netlink event: %d", event); + return NL_OK; + } +} + +/******************************************************************/ + +static GArray * +link_get_all (NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + GArray *links = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), nl_cache_nitems (priv->link_cache)); + NMPlatformLink device; + struct nl_object *object; + + for (object = nl_cache_get_first (priv->link_cache); object; object = nl_cache_get_next (object)) { + link_init (&device, (struct rtnl_link *) object); + g_array_append_val (links, device); + } + + return links; +} + +static struct nl_object * +build_rtnl_link (int ifindex, const char *name, NMLinkType type) +{ + struct rtnl_link *rtnllink; + int nle; + + rtnllink = rtnl_link_alloc (); + g_assert (rtnllink); + if (ifindex) + rtnl_link_set_ifindex (rtnllink, ifindex); + if (name) + rtnl_link_set_name (rtnllink, name); + if (type) { + nle = rtnl_link_set_type (rtnllink, type_to_string (type)); + g_assert (!nle); + } + + return (struct nl_object *) rtnllink; +} + +static gboolean +link_add (NMPlatform *platform, const char *name, NMLinkType type) +{ + return add_object (platform, build_rtnl_link (0, name, type)); +} + +static gboolean +link_delete (NMPlatform *platform, int ifindex) +{ + return delete_object (platform, build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_NONE)); +} + +static int +link_get_ifindex (NMPlatform *platform, const char *ifname) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + return rtnl_link_name2i (priv->link_cache, ifname); +} + +static struct rtnl_link * +link_get (NMPlatform *platform, int ifindex) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + struct rtnl_link *rtnllink = rtnl_link_get (priv->link_cache, ifindex); + + if (!rtnllink) + platform->error = NM_PLATFORM_ERROR_NOT_FOUND; + + return rtnllink; +} + +static const char * +link_get_name (NMPlatform *platform, int ifindex) +{ + auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex); + + return rtnllink ? rtnl_link_get_name (rtnllink) : NULL; +} + +static NMLinkType +link_get_type (NMPlatform *platform, int ifindex) +{ + auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex); + + return link_extract_type (rtnllink); +} + +/******************************************************************/ + +#define EVENT_CONDITIONS ((GIOCondition) (G_IO_IN | G_IO_PRI)) +#define ERROR_CONDITIONS ((GIOCondition) (G_IO_ERR | G_IO_NVAL)) +#define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP)) + +static int +verify_source (struct nl_msg *msg, gpointer user_data) +{ + struct ucred *creds = nlmsg_get_creds (msg); + + if (!creds || creds->pid || creds->uid || creds->gid) { + if (creds) + warning ("netlink: received non-kernel message (pid %d uid %d gid %d)", + creds->pid, creds->uid, creds->gid); + else + warning ("netlink: received message without credentials"); + return NL_STOP; + } + + return NL_OK; +} + +static gboolean +event_handler (GIOChannel *channel, + GIOCondition io_condition, + gpointer user_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (user_data); + int nle; + + nle = nl_recvmsgs_default (priv->nlh_event); + if (nle) + error ("Failed to retrieve incoming events: %s", nl_geterror (nle)); + return TRUE; +} + +static struct nl_sock * +setup_socket (gboolean event, gpointer user_data) +{ + struct nl_sock *sock; + int nle; + + sock = nl_socket_alloc (); + g_return_val_if_fail (sock, NULL); + + /* Only ever accept messages from kernel */ + nle = nl_socket_modify_cb (sock, NL_CB_MSG_IN, NL_CB_CUSTOM, verify_source, user_data); + g_assert (!nle); + + /* Dispatch event messages (event socket only) */ + if (event) { + nl_socket_modify_cb (sock, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data); + nl_socket_disable_seq_check (sock); + } + + nle = nl_connect (sock, NETLINK_ROUTE); + g_assert (!nle); + nle = nl_socket_set_passcred (sock, 1); + g_assert (!nle); + + return sock; +} + +/******************************************************************/ + +static void +nm_linux_platform_init (NMLinuxPlatform *platform) +{ +} + +static gboolean +setup (NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + int channel_flags; + gboolean status; + int nle; + + /* Initialize netlink socket for requests */ + priv->nlh = setup_socket (FALSE, platform); + g_assert (priv->nlh); + debug ("Netlink socket for requests established: %d", nl_socket_get_local_port (priv->nlh)); + + /* Initialize netlink socket for events */ + priv->nlh_event = setup_socket (TRUE, platform); + g_assert (priv->nlh_event); + /* The default buffer size wasn't enough for the testsuites. It might just + * as well happen with NetworkManager itself. For now let's hope 128KB is + * good enough. + */ + nle = nl_socket_set_buffer_size (priv->nlh_event, 131072, 0); + g_assert (!nle); + nle = nl_socket_add_memberships (priv->nlh_event, + RTNLGRP_LINK, + NULL); + g_assert (!nle); + debug ("Netlink socket for events established: %d", nl_socket_get_local_port (priv->nlh_event)); + + priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh_event)); + g_io_channel_set_encoding (priv->event_channel, NULL, NULL); + g_io_channel_set_close_on_unref (priv->event_channel, TRUE); + + channel_flags = g_io_channel_get_flags (priv->event_channel); + status = g_io_channel_set_flags (priv->event_channel, + channel_flags | G_IO_FLAG_NONBLOCK, NULL); + g_assert (status); + priv->event_id = g_io_add_watch (priv->event_channel, + (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS), + event_handler, platform); + + /* Allocate netlink caches */ + rtnl_link_alloc_cache (priv->nlh, AF_UNSPEC, &priv->link_cache); + g_assert (priv->link_cache); + + return TRUE; +} + +static void +nm_linux_platform_finalize (GObject *object) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (object); + + /* Free netlink resources */ + g_source_remove (priv->event_id); + g_io_channel_unref (priv->event_channel); + nl_socket_free (priv->nlh); + nl_socket_free (priv->nlh_event); + nl_cache_free (priv->link_cache); + + G_OBJECT_CLASS (nm_linux_platform_parent_class)->finalize (object); +} + +#define OVERRIDE(function) platform_class->function = function + +static void +nm_linux_platform_class_init (NMLinuxPlatformClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMPlatformClass *platform_class = NM_PLATFORM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMLinuxPlatformPrivate)); + + /* virtual methods */ + object_class->finalize = nm_linux_platform_finalize; + + platform_class->setup = setup; + + platform_class->link_get_all = link_get_all; + platform_class->link_add = link_add; + platform_class->link_delete = link_delete; + platform_class->link_get_ifindex = link_get_ifindex; + platform_class->link_get_name = link_get_name; + platform_class->link_get_type = link_get_type; +} diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h new file mode 100644 index 0000000000..53ea75e692 --- /dev/null +++ b/src/platform/nm-linux-platform.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-linux-platform.h - Linux kernel & udev network configuration layer + * + * 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) 2012 Red Hat, Inc. + */ + +#ifndef NM_LINUX_PLATFORM_H +#define NM_LINUX_PLATFORM_H + +#include "nm-platform.h" + +#define NM_TYPE_LINUX_PLATFORM (nm_linux_platform_get_type ()) +#define NM_LINUX_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatform)) +#define NM_LINUX_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass)) +#define NM_IS_LINUX_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_LINUX_PLATFORM)) +#define NM_IS_LINUX_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_LINUX_PLATFORM)) +#define NM_LINUX_PLATFORM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass)) + +/******************************************************************/ + +typedef struct { + NMPlatform parent; +} NMLinuxPlatform; + +typedef struct { + NMPlatformClass parent; +} NMLinuxPlatformClass; + +/******************************************************************/ + +GType nm_linux_platform_get_type (void); + +void nm_linux_platform_setup (void); + +#endif /* NM_LINUX_PLATFORM_H */ diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c new file mode 100644 index 0000000000..b257f010f5 --- /dev/null +++ b/src/platform/nm-platform.c @@ -0,0 +1,396 @@ +/* -*- 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) 2012 Red Hat, Inc. + */ + +#include +#include +#include +#include + +#include "nm-platform.h" +#include "nm-logging.h" + +#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__) + +#define NM_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_PLATFORM, NMPlatformPrivate)) + +G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT) + +/* NMPlatform signals */ +enum { + LINK_ADDED, + LINK_CHANGED, + LINK_REMOVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/******************************************************************/ + +/* Singleton NMPlatform subclass instance and cached class object */ +static NMPlatform *platform = NULL; +static NMPlatformClass *klass = NULL; + +/** + * nm_platform_setup: + * @type: The #GType for a subclass of #NMPlatform + * + * Do not use this function directly, it is intended to be called by + * NMPlatform subclasses. For the linux platform initialization use + * nm_linux_platform_setup() instead. + * + * Failing to set up #NMPlatform singleton results in a fatal error, + * as well as trying to initialize it multiple times without freeing + * it. + * + * NetworkManager will typically use only one platform object during + * its run. Test programs might want to switch platform implementations, + * though. This is done with a combination of nm_platform_free() and + * nm_*_platform_setup(). + */ +void +nm_platform_setup (GType type) +{ + gboolean status; + + g_assert (platform == NULL); + + platform = g_object_new (type, NULL); + g_assert (NM_IS_PLATFORM (platform)); + + klass = NM_PLATFORM_GET_CLASS (platform); + g_assert (klass->setup); + + status = klass->setup (platform); + g_assert (status); +} + +/** + * nm_platform_free: + * + * Free #NMPlatform singleton created by nm_*_platform_setup(). + */ +void +nm_platform_free (void) +{ + g_assert (platform); + + g_object_unref (platform); + platform = NULL; +} + +/** + * nm_platform_get: + * + * Retrieve #NMPlatform singleton. Use this whenever you want to connect to + * #NMPlatform signals. It is an error to call it before nm_*_platform_setup() + * or after nm_platform_free(). + * + * Returns: (transfer none): The #NMPlatform singleton reference. + */ +NMPlatform * +nm_platform_get (void) +{ + g_assert (platform); + + return platform; +} + +/******************************************************************/ + +/** + * nm_platform_get_error: + * + * Convenience function to quickly retrieve the error code of the last + * operation. + * + * Returns: Integer error code. + */ +int +nm_platform_get_error (void) +{ + g_assert (platform); + + return platform->error; +} + +/** + * nm_platform_get_error_message: + * + * Returns: Static human-readable string for the error. Don't free. + */ +const char * +nm_platform_get_error_msg (void) +{ + g_assert (platform); + + switch (platform->error) { + case NM_PLATFORM_ERROR_NONE: + return "unknown error"; + case NM_PLATFORM_ERROR_NOT_FOUND: + return "object not found"; + case NM_PLATFORM_ERROR_EXISTS: + return "object already exists"; + default: + return "invalid error number"; + } +} + +static void +reset_error (void) +{ + g_assert (platform); + platform->error = NM_PLATFORM_ERROR_NONE; +} + +/******************************************************************/ + +/** + * nm_platform_link_get_all: + * + * Retrieve a snapshot of configuration for all links at once. The result is + * owned by the caller and should be freed with g_array_unref(). + */ +GArray * +nm_platform_link_get_all () +{ + reset_error (); + + g_return_val_if_fail (klass->link_get_all, NULL); + + return klass->link_get_all (platform); +} + +/** + * nm_platform_link_add: + * @name: Interface name + * @type: Interface type + * + * Add a software interface. Sets platform->error to NM_PLATFORM_ERROR_EXISTS + * if interface is already already exists. + */ +static gboolean +nm_platform_link_add (const char *name, NMLinkType type) +{ + reset_error (); + + g_return_val_if_fail (name, FALSE); + g_return_val_if_fail (klass->link_add, FALSE); + + if (nm_platform_link_exists (name)) { + debug ("link: already exists"); + platform->error = NM_PLATFORM_ERROR_EXISTS; + return FALSE; + } + + return klass->link_add (platform, name, type); +} + +/** + * nm_platform_dummy_add: + * @name: New interface name + * + * Create a software ethernet-like interface + */ +gboolean +nm_platform_dummy_add (const char *name) +{ + g_return_val_if_fail (name, FALSE); + + debug ("link: adding dummy '%s'", name); + return nm_platform_link_add (name, NM_LINK_TYPE_DUMMY); +} + +/** + * nm_platform_link_exists: + * @name: Interface name + * + * Returns: %TRUE if an interface of this name exists, %FALSE otherwise. + */ +gboolean +nm_platform_link_exists (const char *name) +{ + int ifindex = nm_platform_link_get_ifindex (name); + + reset_error(); + return ifindex > 0; +} + +/** + * nm_platform_link_delete: + * @ifindex: Interface index + * + * Delete a software interface. Sets platform->error to + * NM_PLATFORM_ERROR_NOT_FOUND if ifindex not available. + */ +gboolean +nm_platform_link_delete (int ifindex) +{ + const char *name; + + reset_error (); + + g_return_val_if_fail (ifindex > 0, FALSE); + g_return_val_if_fail (klass->link_delete, FALSE); + + name = nm_platform_link_get_name (ifindex); + + if (!name) + return FALSE; + + debug ("link: deleting '%s' (%d)", name, ifindex); + return klass->link_delete (platform, ifindex); +} + +/** + * nm_platform_link_delete_by_name: + * @name: Interface name + * + * Delete a software interface. + */ +gboolean +nm_platform_link_delete_by_name (const char *name) +{ + int ifindex = nm_platform_link_get_ifindex (name); + + if (!ifindex) + return FALSE; + + return nm_platform_link_delete (ifindex); +} + +/** + * nm_platform_link_get_index: + * @name: Interface name + * + * Returns: The interface index corresponding to the given interface name + * or 0. Inteface name is owned by #NMPlatform, don't free it. + */ +int +nm_platform_link_get_ifindex (const char *name) +{ + int ifindex; + + reset_error (); + + g_return_val_if_fail (name, 0); + g_return_val_if_fail (klass->link_get_ifindex, 0); + + ifindex = klass->link_get_ifindex (platform, name); + + if (!ifindex) { + debug ("link not found: %s", name); + platform->error = NM_PLATFORM_ERROR_NOT_FOUND; + } + + return ifindex; +} + +/** + * nm_platform_link_get_name: + * @name: Interface name + * + * Returns: The interface name corresponding to the given interface index + * or NULL. + */ +const char * +nm_platform_link_get_name (int ifindex) +{ + const char *name; + + reset_error (); + + g_return_val_if_fail (ifindex > 0, NULL); + g_return_val_if_fail (klass->link_get_name, NULL); + + name = klass->link_get_name (platform, ifindex); + + if (!name) { + debug ("link not found: %d", ifindex); + platform->error = NM_PLATFORM_ERROR_NOT_FOUND; + return FALSE; + } + + return name; +} + +/** + * nm_platform_link_get_type: + * @ifindex: Interface index. + * + * Returns: Link type constant as defined in nm-platform.h. On error, + * NM_LINK_TYPE_NONE is returned. + */ +NMLinkType +nm_platform_link_get_type (int ifindex) +{ + reset_error (); + + g_return_val_if_fail (klass->link_get_type, NM_LINK_TYPE_NONE); + + return klass->link_get_type (platform, ifindex); +} + +/******************************************************************/ + +static void +log_link_added (NMPlatform *p, NMPlatformLink *info, gpointer user_data) +{ + debug ("signal: link address: '%s' (%d)", info->name, info->ifindex); +} + +static void +log_link_changed (NMPlatform *p, NMPlatformLink *info, gpointer user_data) +{ + debug ("signal: link changed: '%s' (%d)", info->name, info->ifindex); +} + +static void +log_link_removed (NMPlatform *p, NMPlatformLink *info, gpointer user_data) +{ + debug ("signal: link removed: '%s' (%d)", info->name, info->ifindex); +} + +/******************************************************************/ + +static void +nm_platform_init (NMPlatform *object) +{ +} + +#define SIGNAL(signal_id, method) signals[signal_id] = \ + g_signal_new_class_handler (NM_PLATFORM_ ## signal_id, \ + G_OBJECT_CLASS_TYPE (object_class), \ + G_SIGNAL_RUN_FIRST, \ + G_CALLBACK (method), \ + NULL, NULL, \ + g_cclosure_marshal_VOID__POINTER, \ + G_TYPE_NONE, 1, G_TYPE_POINTER); \ + +static void +nm_platform_class_init (NMPlatformClass *platform_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (platform_class); + + /* Signals */ + SIGNAL (LINK_ADDED, log_link_added) + SIGNAL (LINK_CHANGED, log_link_changed) + SIGNAL (LINK_REMOVED, log_link_removed) +} diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h new file mode 100644 index 0000000000..aeb7ab611b --- /dev/null +++ b/src/platform/nm-platform.h @@ -0,0 +1,148 @@ +/* -*- 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) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef NM_PLATFORM_H +#define NM_PLATFORM_H + +#include +#include +#include + +#define NM_TYPE_PLATFORM (nm_platform_get_type ()) +#define NM_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform)) +#define NM_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_PLATFORM, NMPlatformClass)) +#define NM_IS_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_PLATFORM)) +#define NM_IS_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_PLATFORM)) +#define NM_PLATFORM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_PLATFORM, NMPlatformClass)) + +/******************************************************************/ + +typedef enum { + NM_LINK_TYPE_NONE, + NM_LINK_TYPE_UNKNOWN, + NM_LINK_TYPE_GENERIC, + NM_LINK_TYPE_LOOPBACK, + NM_LINK_TYPE_ETHERNET, + NM_LINK_TYPE_DUMMY, +} NMLinkType; + +typedef struct { + int ifindex; + char name[IFNAMSIZ]; + NMLinkType type; +} NMPlatformLink; + +/******************************************************************/ + +/* NMPlatform abstract class and its implementations provide a layer between + * networkmanager's device management classes and the operating system kernel. + * + * How it works, is best seen in tests/nm-platform-test.c source file. + * + * NMPlatform provides interface to configure kernel interfaces and receive + * notifications about both internal and external configuration changes. It + * respects the following rules: + * + * 1) Every change made through NMPlatform is readily available and the respective + * signals are called synchronously. + * + * 2) State of an object retrieved from NMPlatform (through functions or events) + * is at least as recent than the state retrieved before. + * + * Any failure of the above rules should be fixed in NMPlatform implementations + * and tested in nm-platform-test. Synchronization hacks should never be put + * to any other code. That's why NMPlatform was created and that's why the + * testing code was written for it. + * + * In future, parts of linux platform implementation may be moved to the libnl + * library. + * + * If you have any problems related to NMPlatform on your system, you should + * always first run tests/nm-linux-platform-test as root and with all + * network configuration daemons stopped. Look at the code first. + */ + +typedef struct { + GObject parent; + + int error; +} NMPlatform; + +typedef struct { + GObjectClass parent; + + gboolean (*setup) (NMPlatform *); + + GArray *(*link_get_all) (NMPlatform *); + gboolean (*link_add) (NMPlatform *, const char *name, NMLinkType type); + gboolean (*link_delete) (NMPlatform *, int ifindex); + int (*link_get_ifindex) (NMPlatform *, const char *name); + const char *(*link_get_name) (NMPlatform *, int ifindex); + NMLinkType (*link_get_type) (NMPlatform *, int ifindex); +} NMPlatformClass; + +/* NMPlatform signals + * + * Each signal handler is called with a type-specific object that provides + * key attributes that constitute identity of the object. They may also + * provide additional attributes for convenience. + * + * The object only intended to be used by the signal handler to determine + * the current values. It is no longer valid after the signal handler exits + * but you are free to copy the provided information and use it for later + * reference. + */ +#define NM_PLATFORM_LINK_ADDED "link-added" +#define NM_PLATFORM_LINK_CHANGED "link-changed" +#define NM_PLATFORM_LINK_REMOVED "link-removed" + +/* NMPlatform error codes */ +enum { + /* no error specified, sometimes this means the arguments were wrong */ + NM_PLATFORM_ERROR_NONE, + /* object was not found */ + NM_PLATFORM_ERROR_NOT_FOUND, + /* object already exists */ + NM_PLATFORM_ERROR_EXISTS, +}; + +/******************************************************************/ + +GType nm_platform_get_type (void); + +void nm_platform_setup (GType type); +NMPlatform *nm_platform_get (void); +void nm_platform_free (void); + +/******************************************************************/ + +int nm_platform_get_error (void); +const char *nm_platform_get_error_msg (void); + +GArray *nm_platform_link_get_all (void); +gboolean nm_platform_dummy_add (const char *name); +gboolean nm_platform_link_exists (const char *name); +gboolean nm_platform_link_delete (int ifindex); +gboolean nm_platform_link_delete_by_name (const char *ifindex); +int nm_platform_link_get_ifindex (const char *name); +const char *nm_platform_link_get_name (int ifindex); +NMLinkType nm_platform_link_get_type (int ifindex); + +#endif /* NM_PLATFORM_H */ diff --git a/src/platform/tests/.gitignore b/src/platform/tests/.gitignore new file mode 100644 index 0000000000..e8c0a56b97 --- /dev/null +++ b/src/platform/tests/.gitignore @@ -0,0 +1,4 @@ +/dump +/monitor +/test-link-fake +/test-link-linux diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am new file mode 100644 index 0000000000..6e8bc117d5 --- /dev/null +++ b/src/platform/tests/Makefile.am @@ -0,0 +1,72 @@ +if ENABLE_TESTS + +AM_CPPFLAGS = \ + -I${top_srcdir} \ + -I${top_srcdir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/libnm-util \ + -I${srcdir}/.. \ + $(GLIB_CFLAGS) \ + $(LIBNL_CFLAGS) + +AM_CFLAGS = $(CODE_COVERAGE_CFLAGS) +AM_LDFLAGS = $(GLIB_LIBS) $(LIBNL_LIBS) $(CODE_COVERAGE_LDFLAGS) +COMMON_LDADD = $(top_builddir)/src/logging/libnm-logging.la +PLATFORM_LDADD = $(COMMON_LDADD) $(top_builddir)/src/platform/libnm-platform.la + +@GNOME_CODE_COVERAGE_RULES@ + +noinst_PROGRAMS = \ + dump \ + monitor \ + test-link-fake \ + test-link-linux + +EXTRA_DIST = test-common.h + +monitor_SOURCES = monitor.c +monitor_CPPFLAGS = $(AM_CPPFLAGS) +monitor_LDADD = $(PLATFORM_LDADD) + +dump_SOURCES = dump.c +dump_CPPFLAGS = $(AM_CPPFLAGS) +dump_LDADD = $(PLATFORM_LDADD) + +test_link_fake_SOURCES = \ + test-link.c \ + test-common.c \ + ${srcdir}/../nm-platform.c \ + ${srcdir}/../nm-fake-platform.c +test_link_fake_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DSETUP=nm_fake_platform_setup \ + -DKERNEL_HACKS=0 +test_link_fake_LDADD = $(COMMON_LDADD) + +test_link_linux_SOURCES = \ + test-link.c \ + test-common.c \ + ${srcdir}/../nm-platform.c \ + ${srcdir}/../nm-linux-platform.c +test_link_linux_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DSETUP=nm_linux_platform_setup \ + -DKERNEL_HACKS=1 +test_link_linux_LDADD = $(COMMON_LDADD) + +# Unfortunately, we cannot run nm-linux-platform-test as an automatic test +# program by default, as it requires root access and modifies kernel +# configuration. +# +# However, we can check whether the fake platform fakes platform behavior +# correctly. +@VALGRIND_RULES@ +TESTS = ./test-link-fake +ROOTTESTS = ./test-link-linux + +# If explicitly enabled, we can run the root tests +if RUN_ROOT_TESTS +TESTS += $(ROOTTESTS) +endif + +endif diff --git a/src/platform/tests/dump.c b/src/platform/tests/dump.c new file mode 100644 index 0000000000..347b5e6a6a --- /dev/null +++ b/src/platform/tests/dump.c @@ -0,0 +1,54 @@ +#include +#include + +#include "nm-platform.h" +#include "nm-linux-platform.h" +#include "nm-fake-platform.h" + +static const char * +type_to_string (NMLinkType type) +{ + switch (type) { + case NM_LINK_TYPE_LOOPBACK: + return "loopback"; + case NM_LINK_TYPE_ETHERNET: + return "ethernet"; + case NM_LINK_TYPE_DUMMY: + return "dummy"; + default: + return "unknown-type"; + } +} + +static void +dump_interface (NMPlatformLink *link) +{ + printf ("%d: %s: %s", link->ifindex, link->name, type_to_string (link->type)); + printf ("\n"); +} + +static void +dump_all (void) +{ + GArray *links = nm_platform_link_get_all (); + int i; + + for (i = 0; i < links->len; i++) + dump_interface (&g_array_index (links, NMPlatformLink, i)); +} + +int +main (int argc, char **argv) +{ + g_type_init (); + + g_assert (argc <= 2); + if (argc > 1 && !g_strcmp0 (argv[1], "--fake")) + nm_fake_platform_setup (); + else + nm_linux_platform_setup (); + + dump_all (); + + return EXIT_SUCCESS; +} diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c new file mode 100644 index 0000000000..b82ca09764 --- /dev/null +++ b/src/platform/tests/monitor.c @@ -0,0 +1,27 @@ +#include +#include + +#include "nm-fake-platform.h" +#include "nm-linux-platform.h" +#include "nm-logging.h" + +int +main (int argc, char **argv) +{ + GMainLoop *loop; + + g_type_init (); + loop = g_main_loop_new (NULL, FALSE); + nm_logging_setup ("debug", NULL, NULL); + openlog (G_LOG_DOMAIN, LOG_CONS | LOG_PERROR, LOG_DAEMON); + + g_assert (argc <= 2); + if (argc > 1 && !g_strcmp0 (argv[1], "--fake")) + nm_fake_platform_setup (); + else + nm_linux_platform_setup (); + + g_main_loop_run (loop); + + return EXIT_SUCCESS; +} diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c new file mode 100644 index 0000000000..0f4492e134 --- /dev/null +++ b/src/platform/tests/test-common.c @@ -0,0 +1,60 @@ +#include "test-common.h" + +SignalData * +add_signal_full (const char *name, GCallback callback, int ifindex) +{ + SignalData *data = g_new0 (SignalData, 1); + + data->name = name; + data->received = FALSE; + data->handler_id = g_signal_connect (nm_platform_get (), name, callback, data); + data->ifindex = ifindex; + + g_assert (data->handler_id >= 0); + + return data; +} + +void +accept_signal (SignalData *data) +{ + if (!data->received) + g_error ("Attemted to accept a non-received signal '%s'.", data->name); + + data->received = FALSE; +} + +void +wait_signal (SignalData *data) +{ + data->loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (data->loop); + g_main_loop_unref (data->loop); + data->loop = NULL; + + accept_signal (data); +} + +void +free_signal (SignalData *data) +{ + if (data->received) + g_error ("Attempted to free received but not accepted signal '%s'.", data->name); + + g_signal_handler_disconnect (nm_platform_get (), data->handler_id); + g_free (data); +} + +void +run_command (const char *format, ...) +{ + char *command; + va_list ap; + + va_start (ap, format); + + command = g_strdup_vprintf (format, ap); + g_assert (!system (command)); + g_free (command); +} + diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h new file mode 100644 index 0000000000..9c5f9e9161 --- /dev/null +++ b/src/platform/tests/test-common.h @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +#include "nm-logging.h" +#include "nm-platform.h" +#include "nm-fake-platform.h" +#include "nm-linux-platform.h" + +#define error(err) g_assert (nm_platform_get_error () == err) +#define no_error() error (NM_PLATFORM_ERROR_NONE) + +typedef struct { + int handler_id; + const char *name; + gboolean received; + GMainLoop *loop; + int ifindex; +} SignalData; + +SignalData *add_signal_full (const char *name, GCallback callback, int ifindex); +#define add_signal(name, callback) add_signal_full (name, (GCallback) callback, 0) +void accept_signal (SignalData *data); +void wait_signal (SignalData *data); +void free_signal (SignalData *data); + +void run_command (const char *format, ...); diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c new file mode 100644 index 0000000000..de42ecc7e6 --- /dev/null +++ b/src/platform/tests/test-link.c @@ -0,0 +1,190 @@ +#include "test-common.h" + +#define LO_INDEX 1 +#define LO_NAME "lo" + +#define DEVICE_NAME "nm-test-device" +#define BOGUS_NAME "nm-bogus-device" +#define BOGUS_IFINDEX INT_MAX + +static void +link_callback (NMPlatform *platform, NMPlatformLink *received, SignalData *data) +{ + + GArray *links; + NMPlatformLink *cached; + int i; + + g_assert (received); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + + if (data->loop) + g_main_loop_quit (data->loop); + + if (data->received) + g_error ("Received signal '%s' a second time.", data->name); + + data->received = TRUE; + + /* Check the data */ + g_assert (received->ifindex > 0); + links = nm_platform_link_get_all (); + for (i = 0; i < links->len; i++) { + cached = &g_array_index (links, NMPlatformLink, i); + if (cached->ifindex == received->ifindex) { + g_assert (!memcmp (cached, received, sizeof (*cached))); + if (!g_strcmp0 (data->name, NM_PLATFORM_LINK_REMOVED)) { + g_error ("Deleted link still found in the local cache."); + } + g_array_unref (links); + return; + } + } + g_array_unref (links); + + if (g_strcmp0 (data->name, NM_PLATFORM_LINK_REMOVED)) + g_error ("Added/changed link not found in the local cache."); +} + +static void +test_bogus(void) +{ + g_assert (!nm_platform_link_exists (BOGUS_NAME)); + no_error (); + g_assert (!nm_platform_link_delete (BOGUS_IFINDEX)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + g_assert (!nm_platform_link_delete_by_name (BOGUS_NAME)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + g_assert (!nm_platform_link_get_ifindex (BOGUS_NAME)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + g_assert (!nm_platform_link_get_name (BOGUS_IFINDEX)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + g_assert (!nm_platform_link_get_type (BOGUS_IFINDEX)); + error (NM_PLATFORM_ERROR_NOT_FOUND); +} + +static void +test_loopback (void) +{ + g_assert (nm_platform_link_exists (LO_NAME)); + g_assert (nm_platform_link_get_type (LO_INDEX) == NM_LINK_TYPE_LOOPBACK); + g_assert (nm_platform_link_get_ifindex (LO_NAME) == LO_INDEX); + g_assert (!g_strcmp0 (nm_platform_link_get_name (LO_INDEX), LO_NAME)); +} + +static void +test_internal (void) +{ + SignalData *link_added = add_signal (NM_PLATFORM_LINK_ADDED, link_callback); + SignalData *link_changed = add_signal (NM_PLATFORM_LINK_CHANGED, link_callback); + SignalData *link_removed = add_signal (NM_PLATFORM_LINK_REMOVED, link_callback); + int ifindex; + + /* Check the functions for non-existent devices */ + g_assert (!nm_platform_link_exists (DEVICE_NAME)); no_error (); + g_assert (!nm_platform_link_get_ifindex (DEVICE_NAME)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + + /* Add device */ + g_assert (nm_platform_dummy_add (DEVICE_NAME)); + no_error (); + accept_signal (link_added); + + /* Try to add again */ + g_assert (!nm_platform_dummy_add (DEVICE_NAME)); + error (NM_PLATFORM_ERROR_EXISTS); + + /* Check device index, name and type */ + ifindex = nm_platform_link_get_ifindex (DEVICE_NAME); + g_assert (ifindex > 0); + g_assert (!g_strcmp0 (nm_platform_link_get_name (ifindex), DEVICE_NAME)); + g_assert (nm_platform_link_get_type (ifindex) == NM_LINK_TYPE_DUMMY); + + /* Delete device */ + g_assert (nm_platform_link_delete (ifindex)); + no_error (); + accept_signal (link_removed); + + /* Try to delete again */ + g_assert (!nm_platform_link_delete (ifindex)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + + /* Add back */ + g_assert (nm_platform_dummy_add (DEVICE_NAME)); + no_error (); + accept_signal (link_added); + + /* Delete device by name */ + g_assert (nm_platform_link_delete_by_name (DEVICE_NAME)); + no_error (); + accept_signal (link_removed); + + /* Try to delete again */ + g_assert (!nm_platform_link_delete_by_name (DEVICE_NAME)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + + free_signal (link_added); + free_signal (link_changed); + free_signal (link_removed); +} + +static void +test_external (void) +{ + SignalData *link_added = add_signal (NM_PLATFORM_LINK_ADDED, link_callback); + SignalData *link_changed = add_signal (NM_PLATFORM_LINK_CHANGED, link_callback); + SignalData *link_removed = add_signal (NM_PLATFORM_LINK_REMOVED, link_callback); + int ifindex; + + run_command ("ip link add %s type %s", DEVICE_NAME, "dummy"); + wait_signal (link_added); + g_assert (nm_platform_link_exists (DEVICE_NAME)); + ifindex = nm_platform_link_get_ifindex (DEVICE_NAME); + g_assert (ifindex > 0); + g_assert (!g_strcmp0 (nm_platform_link_get_name (ifindex), DEVICE_NAME)); + g_assert (nm_platform_link_get_type (ifindex) == NM_LINK_TYPE_DUMMY); + + run_command ("ip link del %s", DEVICE_NAME); + wait_signal (link_removed); + g_assert (!nm_platform_link_exists (DEVICE_NAME)); + + free_signal (link_added); + free_signal (link_changed); + free_signal (link_removed); +} + +int +main (int argc, char **argv) +{ + int result; + + openlog (G_LOG_DOMAIN, LOG_CONS | LOG_PERROR, LOG_DAEMON); + g_type_init (); + g_test_init (&argc, &argv, NULL); + /* Enable debug messages if called with --debug */ + for (; *argv; argv++) { + if (!g_strcmp0 (*argv, "--debug")) { + nm_logging_setup ("debug", NULL, NULL); + } + } + + SETUP (); + + /* Clean up */ + nm_platform_link_delete_by_name (DEVICE_NAME); + g_assert (!nm_platform_link_exists (DEVICE_NAME)); + + g_test_add_func ("/link/bogus", test_bogus); + g_test_add_func ("/link/loopback", test_loopback); + g_test_add_func ("/link/internal", test_internal); + + if (strcmp (g_type_name (G_TYPE_FROM_INSTANCE (nm_platform_get ())), "NMFakePlatform")) + g_test_add_func ("/link/external", test_external); + + result = g_test_run (); + + nm_platform_free (); + return result; +} diff --git a/valgrind.suppressions b/valgrind.suppressions index 8402593b34..39ea9395bd 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -74,3 +74,13 @@ fun:g_object_constructor ... } +{ + g_signal_new_class_handler + Memcheck:Leak + ... + fun:g_closure_new_simple + fun:g_cclosure_new + fun:g_signal_new_class_handler + ... +} +