platform: link management
authorPavel Šimerda <psimerda@redhat.com>
Wed, 27 Mar 2013 21:23:24 +0000 (22:23 +0100)
committerPavel Šimerda <psimerda@redhat.com>
Wed, 10 Apr 2013 14:40:58 +0000 (16:40 +0200)
Create the new nm-platform framework and implement link (or interface)
management. The nm-platform serves as the point of contact between
the rest of NetworkManager and the operating system.

There are two backends for nm-platform:

* NMFakePlatform: Fake kernel backend for testing purposes
* NMLinuxPlatform: Linux kernel backend for actual use

A comprehensive testsuite is included and will be extended with new
feature additions. To enable the Linux part of the testsuite, use
--enable-tests=root configure options and run 'make check' as root.
Use --enable-code-coverage for code coverage support.

  ./autogen.sh --enable-tests=root --enable-code-coverage
  make
  make -C src/platform check-code-coverage

Link features:

* Retrieve the list of links
* Translate between indexes and names
* Discover device type
* Add/remove dummy interfaces (for testing)

Thanks to Thomas Graf for helping with libnl3 synchronization issues.

18 files changed:
configure.ac
src/Makefile.am
src/main.c
src/platform/Makefile.am [new file with mode: 0644]
src/platform/nm-fake-platform.c [new file with mode: 0644]
src/platform/nm-fake-platform.h [new file with mode: 0644]
src/platform/nm-linux-platform.c [new file with mode: 0644]
src/platform/nm-linux-platform.h [new file with mode: 0644]
src/platform/nm-platform.c [new file with mode: 0644]
src/platform/nm-platform.h [new file with mode: 0644]
src/platform/tests/.gitignore [new file with mode: 0644]
src/platform/tests/Makefile.am [new file with mode: 0644]
src/platform/tests/dump.c [new file with mode: 0644]
src/platform/tests/monitor.c [new file with mode: 0644]
src/platform/tests/test-common.c [new file with mode: 0644]
src/platform/tests/test-common.h [new file with mode: 0644]
src/platform/tests/test-link.c [new file with mode: 0644]
valgrind.suppressions

index 034bd68..6fa6569 100644 (file)
@@ -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
index ec76d13..113b527 100644 (file)
@@ -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 \
index 16f1834..ed71bb6 100644 (file)
@@ -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 (file)
index 0000000..ef1b462
--- /dev/null
@@ -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 (file)
index 0000000..b591918
--- /dev/null
@@ -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 <errno.h>
+#include <unistd.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#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 (file)
index 0000000..c9fa42e
--- /dev/null
@@ -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 (file)
index 0000000..8df4735
--- /dev/null
@@ -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 <config.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <linux/if_arp.h>
+#include <netlink/netlink.h>
+#include <netlink/object.h>
+#include <netlink/cache.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/vlan.h>
+
+#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 (file)
index 0000000..53ea75e
--- /dev/null
@@ -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 (file)
index 0000000..b257f01
--- /dev/null
@@ -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 <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#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 (file)
index 0000000..aeb7ab6
--- /dev/null
@@ -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 <glib-object.h>
+#include <netinet/in.h>
+#include <linux/if.h>
+
+#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 (file)
index 0000000..e8c0a56
--- /dev/null
@@ -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 (file)
index 0000000..6e8bc11
--- /dev/null
@@ -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 (file)
index 0000000..347b5e6
--- /dev/null
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..b82ca09
--- /dev/null
@@ -0,0 +1,27 @@
+#include <stdlib.h>
+#include <syslog.h>
+
+#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 (file)
index 0000000..0f4492e
--- /dev/null
@@ -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 (file)
index 0000000..9c5f9e9
--- /dev/null
@@ -0,0 +1,28 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <string.h>
+
+#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 (file)
index 0000000..de42ecc
--- /dev/null
@@ -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;
+}
index 8402593..39ea939 100644 (file)
    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
+   ...
+}
+