summary refs log tree commit diff
path: root/src/platform/nm-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
committerMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
commit33491bc4279481db8ae47213e34a6d695a0e8830 (patch)
tree097d2b0fdff3fae6885381ae5e57a182cd8cbbba /src/platform/nm-platform.c
parent59c3714a494c3b3765657c0551ad82842d98a7d2 (diff)
Imported Upstream version 0.9.10.0 upstream/0.9.10.0
Diffstat (limited to 'src/platform/nm-platform.c')
-rw-r--r--src/platform/nm-platform.c2618
1 files changed, 2618 insertions, 0 deletions
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
new file mode 100644
index 00000000..542b0faa
--- /dev/null
+++ b/src/platform/nm-platform.c
@@ -0,0 +1,2618 @@
+/* -*- 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 <arpa/inet.h>
+#include <string.h>
+#include <netlink/route/addr.h>
+
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-platform.h"
+#include "NetworkManagerUtils.h"
+#include "nm-logging.h"
+#include "nm-enum-types.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 {
+	SIGNAL_LINK_CHANGED,
+	SIGNAL_IP4_ADDRESS_CHANGED,
+	SIGNAL_IP6_ADDRESS_CHANGED,
+	SIGNAL_IP4_ROUTE_CHANGED,
+	SIGNAL_IP6_ROUTE_CHANGED,
+	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_set_error:
+ * @error: The error code
+ *
+ * Convenience function to falsify platform->error. It can be used for example
+ * by functions that want to save the error, execute some operations and
+ * restore it.
+ */
+void nm_platform_set_error (NMPlatformError error)
+{
+	platform->error = error;
+}
+
+/**
+ * nm_platform_get_error:
+ *
+ * Convenience function to quickly retrieve the error code of the last
+ * operation.
+ *
+ * Returns: Integer error code.
+ */
+NMPlatformError
+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";
+	case NM_PLATFORM_ERROR_WRONG_TYPE:
+		return "object is wrong type";
+	case NM_PLATFORM_ERROR_NOT_SLAVE:
+		return "link not a slave";
+	case NM_PLATFORM_ERROR_NO_FIRMWARE:
+		return "firmware not found";
+	default:
+		return "invalid error number";
+	}
+}
+
+static void
+reset_error (void)
+{
+	g_assert (platform);
+	platform->error = NM_PLATFORM_ERROR_NONE;
+}
+
+#define IFA_F_MANAGETEMPADDR_STR "mngtmpaddr"
+#define IFA_F_NOPREFIXROUTE_STR "noprefixroute"
+gboolean
+nm_platform_check_support_libnl_extended_ifa_flags ()
+{
+	static int supported = -1;
+
+	/* support for extended ifa-flags was added together
+	 * with the IFA_F_MANAGETEMPADDR flag. So, check if libnl
+	 * is able to parse this flag. */
+	if (supported == -1)
+		supported = rtnl_addr_str2flags (IFA_F_MANAGETEMPADDR_STR) == IFA_F_MANAGETEMPADDR;
+
+	return supported;
+}
+
+gboolean
+nm_platform_check_support_kernel_extended_ifa_flags ()
+{
+	g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
+
+	if (!klass->check_support_kernel_extended_ifa_flags)
+		return FALSE;
+
+	return klass->check_support_kernel_extended_ifa_flags (platform);
+}
+
+/******************************************************************/
+
+/**
+ * nm_platform_sysctl_set:
+ * @path: Absolute option path
+ * @value: Value to write
+ *
+ * This function is intended to be used for writing values to sysctl-style
+ * virtual runtime configuration files. This includes not only /proc/sys
+ * but also for example /sys/class.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nm_platform_sysctl_set (const char *path, const char *value)
+{
+	reset_error ();
+
+	g_return_val_if_fail (path, FALSE);
+	g_return_val_if_fail (value, FALSE);
+	g_return_val_if_fail (klass->sysctl_set, FALSE);
+
+	return klass->sysctl_set (platform, path, value);
+}
+
+/**
+ * nm_platform_sysctl_get:
+ * @path: Absolute path to sysctl
+ *
+ * Returns: (transfer full): Contents of the virtual sysctl file.
+ */
+char *
+nm_platform_sysctl_get (const char *path)
+{
+	reset_error ();
+
+	g_return_val_if_fail (path, NULL);
+	g_return_val_if_fail (klass->sysctl_get, NULL);
+
+	return klass->sysctl_get (platform, path);
+}
+
+/**
+ * nm_platform_sysctl_get_int32:
+ * @path: Absolute path to sysctl
+ * @fallback: default value, if the content of path could not be read
+ * as decimal integer.
+ *
+ * Returns: contents of the sysctl file parsed as s32 integer, or
+ * @fallback on error. On error, %errno will be set to a non-zero
+ * value, on success %errno will be set to zero.
+ */
+gint32
+nm_platform_sysctl_get_int32 (const char *path, gint32 fallback)
+{
+	return nm_platform_sysctl_get_int_checked (path, 10, G_MININT32, G_MAXINT32, fallback);
+}
+
+/**
+ * nm_platform_sysctl_get_int_checked:
+ * @path: Absolute path to sysctl
+ * @base: base of numeric conversion
+ * @min: minimal value that is still valid
+ * @max: maximal value that is still valid
+ * @fallback: default value, if the content of path could not be read
+ * as valid integer.
+ *
+ * Returns: contents of the sysctl file parsed as s64 integer, or
+ * @fallback on error. On error, %errno will be set to a non-zero
+ * value. On success, %errno will be set to zero. The returned value
+ * will always be in the range between @min and @max
+ * (inclusive) or @fallback.
+ */
+gint64
+nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
+{
+	char *value = NULL;
+	gint32 ret;
+
+	g_return_val_if_fail (path, fallback);
+
+	if (path)
+		value = nm_platform_sysctl_get (path);
+
+	if (!value) {
+		errno = EINVAL;
+		return fallback;
+	}
+
+	ret = nm_utils_ascii_str_to_int64 (value, base, min, max, fallback);
+	g_free (value);
+	return ret;
+}
+
+/******************************************************************/
+
+/**
+ * nm_platform_query_devices:
+ *
+ * Emit #NMPlatform:link-changed ADDED signals for all currently-known links.
+ * Should only be called at startup.
+ */
+void
+nm_platform_query_devices (void)
+{
+	GArray *links_array;
+	NMPlatformLink *links;
+	int i;
+
+	links_array = nm_platform_link_get_all ();
+	links = (NMPlatformLink *) links_array->data;
+	for (i = 0; i < links_array->len; i++) {
+		g_signal_emit (platform, signals[SIGNAL_LINK_CHANGED], 0,
+		               links[i].ifindex, &links[i], NM_PLATFORM_SIGNAL_ADDED,
+		               NM_PLATFORM_REASON_INTERNAL);
+	}
+	g_array_unref (links_array);
+}
+
+/**
+ * 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 (void)
+{
+	GArray *links, *result;
+	guint i, j, nresult;
+	GHashTable *unseen;
+	NMPlatformLink *item;
+
+	reset_error ();
+
+	g_return_val_if_fail (klass->link_get_all, NULL);
+
+	links = klass->link_get_all (platform);
+
+	if (!links || links->len == 0)
+		return links;
+
+	unseen = g_hash_table_new (g_direct_hash, g_direct_equal);
+	for (i = 0; i < links->len; i++) {
+		item = &g_array_index (links, NMPlatformLink, i);
+
+		if (item->ifindex <= 0 || g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex))) {
+			g_warn_if_reached ();
+			item->ifindex = 0;
+			continue;
+		}
+
+		g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL);
+	}
+
+#ifndef G_DISABLE_ASSERT
+	/* Ensure that link_get_all returns a consistent and valid result. */
+	for (i = 0; i < links->len; i++) {
+		item = &g_array_index (links, NMPlatformLink, i);
+
+		if (!item->ifindex)
+			continue;
+		if (item->master != 0) {
+			g_warn_if_fail (item->master > 0);
+			g_warn_if_fail (item->master != item->ifindex);
+			g_warn_if_fail (g_hash_table_contains (unseen, GINT_TO_POINTER (item->master)));
+		}
+		if (item->parent != 0) {
+			g_warn_if_fail (item->parent > 0);
+			g_warn_if_fail (item->parent != item->ifindex);
+			g_warn_if_fail (g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)));
+		}
+	}
+#endif
+
+	/* Re-order the links list such that children/slaves come after all ancestors */
+	nresult = g_hash_table_size (unseen);
+	result = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), nresult);
+	g_array_set_size (result, nresult);
+
+	j = 0;
+	do {
+		gboolean found_something = FALSE;
+		guint first_idx = G_MAXUINT;
+
+		for (i = 0; i < links->len; i++) {
+			item = &g_array_index (links, NMPlatformLink, i);
+
+			if (!item->ifindex)
+				continue;
+
+			if (first_idx == G_MAXUINT)
+				first_idx = i;
+
+			g_assert (g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));
+
+			if (item->master > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->master)))
+				continue;
+			if (item->parent > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)))
+				continue;
+
+			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
+			g_array_index (result, NMPlatformLink, j++) = *item;
+			item->ifindex = 0;
+			found_something = TRUE;
+		}
+
+		if (!found_something) {
+			/* there is a circle, pop the first (remaining) element from the list */
+			g_warn_if_reached ();
+			item = &g_array_index (links, NMPlatformLink, first_idx);
+
+			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
+			g_array_index (result, NMPlatformLink, j++) = *item;
+			item->ifindex = 0;
+		}
+	} while (j < nresult);
+
+	g_hash_table_destroy (unseen);
+	g_array_free (links, TRUE);
+
+	return result;
+}
+
+/**
+ * nm_platform_link_get:
+ * @ifindex: ifindex of the link
+ * @link: (out): output NMPlatformLink structure.
+ *
+ * If a link with given @ifindex exists, fill the given NMPlatformLink
+ * structure.
+ *
+ * Returns: %TRUE, if such a link exists, %FALSE otherwise.
+ * If the link does not exist, the content of @link is undefined.
+ **/
+gboolean
+nm_platform_link_get (int ifindex, NMPlatformLink *link)
+{
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (link, FALSE);
+
+	g_return_val_if_fail (klass->link_get, FALSE);
+	return !!klass->link_get (platform, ifindex, link);
+}
+
+/**
+ * nm_platform_link_add:
+ * @name: Interface name
+ * @type: Interface type
+ * @address: (allow-none): set the mac address of the link
+ * @address_len: the length of the @address
+ *
+ * Add a software interface. Sets platform->error to NM_PLATFORM_ERROR_EXISTS
+ * if interface is already already exists.  Any link-changed ADDED signal will be
+ * emitted directly, before this function finishes.
+ */
+static gboolean
+nm_platform_link_add (const char *name, NMLinkType type, const void *address, size_t address_len)
+{
+	reset_error ();
+
+	g_return_val_if_fail (name, FALSE);
+	g_return_val_if_fail (klass->link_add, FALSE);
+	g_return_val_if_fail ( (address != NULL) ^ (address_len == 0) , 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, address, address_len);
+}
+
+/**
+ * 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, NULL, 0);
+}
+
+/**
+ * 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 (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_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 (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);
+}
+
+/**
+ * nm_platform_link_get_type_name:
+ * @ifindex: Interface index.
+ *
+ * Returns: A string describing the type of link. In some cases this
+ * may be more specific than nm_platform_link_get_type(), but in
+ * other cases it may not. On error, %NULL is returned.
+ */
+const char *
+nm_platform_link_get_type_name (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (klass->link_get_type_name, NULL);
+
+	return klass->link_get_type_name (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_is_software:
+ * @ifindex: Interface index.
+ *
+ * Returns: %TRUE if ifindex belongs to a software interface, not backed by
+ * a physical device.
+ */
+gboolean
+nm_platform_link_is_software (int ifindex)
+{
+	return (nm_platform_link_get_type (ifindex) & 0x10000);
+}
+
+/**
+ * nm_platform_link_supports_slaves:
+ * @ifindex: Interface index.
+ *
+ * Returns: %TRUE if ifindex belongs to an interface capable of enslaving
+ * other interfaces.
+ */
+gboolean
+nm_platform_link_supports_slaves (int ifindex)
+{
+	return (nm_platform_link_get_type (ifindex) & 0x20000);
+}
+
+/**
+ * nm_platform_link_refresh:
+ * @ifindex: Interface index
+ *
+ * Reload the cache for ifindex synchronously.
+ */
+gboolean
+nm_platform_link_refresh (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	if (klass->link_refresh)
+		return klass->link_refresh (platform, ifindex);
+
+	return TRUE;
+}
+
+/**
+ * nm_platform_link_is_up:
+ * @ifindex: Interface index
+ *
+ * Check if the interface is up.
+ */
+gboolean
+nm_platform_link_is_up (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_is_up, FALSE);
+
+	return klass->link_is_up (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_is_connected:
+ * @ifindex: Interface index
+ *
+ * Check if the interface is connected.
+ */
+gboolean
+nm_platform_link_is_connected (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_is_connected, FALSE);
+
+	return klass->link_is_connected (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_uses_arp:
+ * @ifindex: Interface index
+ *
+ * Check if the interface is configured to use ARP.
+ */
+gboolean
+nm_platform_link_uses_arp (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_uses_arp, FALSE);
+
+	return klass->link_uses_arp (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_set_address:
+ * @ifindex: Interface index
+ * @address: The new MAC address
+ *
+ * Set interface MAC address.
+ */
+gboolean
+nm_platform_link_set_address (int ifindex, gconstpointer address, size_t length)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (address, FALSE);
+	g_return_val_if_fail (length > 0, FALSE);
+	g_return_val_if_fail (klass->link_set_address, FALSE);
+
+	debug ("link: setting '%s' (%d) hardware address", nm_platform_link_get_name (ifindex), ifindex);
+	return klass->link_set_address (platform, ifindex, address, length);
+}
+
+/**
+ * nm_platform_link_get_address:
+ * @ifindex: Interface index
+ * @length: Pointer to a variable to store address length
+ *
+ * Saves interface hardware address to @address.
+ */
+gconstpointer
+nm_platform_link_get_address (int ifindex, size_t *length)
+{
+	reset_error ();
+
+	if (length)
+		*length = 0;
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (klass->link_get_address, NULL);
+
+	return klass->link_get_address (platform, ifindex, length);
+}
+
+gboolean
+nm_platform_link_supports_carrier_detect (int ifindex)
+{
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_supports_carrier_detect, FALSE);
+
+	return klass->link_supports_carrier_detect (platform, ifindex);
+}
+
+gboolean
+nm_platform_link_supports_vlans (int ifindex)
+{
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_supports_vlans, FALSE);
+
+	return klass->link_supports_vlans (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_set_up:
+ * @ifindex: Interface index
+ *
+ * Bring the interface up.
+ */
+gboolean
+nm_platform_link_set_up (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (klass->link_set_up, FALSE);
+
+	debug ("link: setting up '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
+	return klass->link_set_up (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_set_down:
+ * @ifindex: Interface index
+ *
+ * Take the interface down.
+ */
+gboolean
+nm_platform_link_set_down (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (klass->link_set_down, FALSE);
+
+	debug ("link: setting down '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
+	return klass->link_set_down (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_set_arp:
+ * @ifindex: Interface index
+ *
+ * Enable ARP on the interface.
+ */
+gboolean
+nm_platform_link_set_arp (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_set_arp, FALSE);
+
+	debug ("link: setting arp '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
+	return klass->link_set_arp (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_set_noarp:
+ * @ifindex: Interface index
+ *
+ * Disable ARP on the interface.
+ */
+gboolean
+nm_platform_link_set_noarp (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_set_noarp, FALSE);
+
+	debug ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (ifindex), ifindex);
+	return klass->link_set_noarp (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_set_mtu:
+ * @ifindex: Interface index
+ * @mtu: The new MTU value
+ *
+ * Set interface MTU.
+ */
+gboolean
+nm_platform_link_set_mtu (int ifindex, guint32 mtu)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (mtu > 0, FALSE);
+	g_return_val_if_fail (klass->link_set_mtu, FALSE);
+
+	debug ("link: setting '%s' (%d) mtu %d", nm_platform_link_get_name (ifindex), ifindex, mtu);
+	return klass->link_set_mtu (platform, ifindex, mtu);
+}
+
+/**
+ * nm_platform_link_get_mtu:
+ * @ifindex: Interface index
+ *
+ * Returns: MTU value for the interface or 0 on error.
+ */
+guint32
+nm_platform_link_get_mtu (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, 0);
+	g_return_val_if_fail (klass->link_get_mtu, 0);
+
+	return klass->link_get_mtu (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_get_mtu:
+ * @ifindex: Interface index
+ *
+ * Returns: physical port ID for the interface, or %NULL on error
+ * or if the interface has no physical port ID.
+ */
+char *
+nm_platform_link_get_physical_port_id (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, NULL);
+	g_return_val_if_fail (klass->link_get_physical_port_id, NULL);
+
+	return klass->link_get_physical_port_id (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_get_wake_onlan:
+ * @ifindex: Interface index
+ *
+ * Returns: the "Wake-on-LAN" status for @ifindex.
+ */
+gboolean
+nm_platform_link_get_wake_on_lan (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (klass->link_get_wake_on_lan, FALSE);
+
+	return klass->link_get_wake_on_lan (platform, ifindex);
+}
+
+/**
+ * nm_platform_link_enslave:
+ * @master: Interface index of the master
+ * @slave: Interface index of the slave
+ *
+ * Enslave @slave to @master.
+ */
+gboolean
+nm_platform_link_enslave (int master, int slave)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (master > 0, FALSE);
+	g_return_val_if_fail (slave> 0, FALSE);
+	g_return_val_if_fail (klass->link_enslave, FALSE);
+
+	debug ("link: enslaving '%s' (%d) to master '%s' (%d)",
+		nm_platform_link_get_name (slave), slave,
+		nm_platform_link_get_name (master), master);
+	return klass->link_enslave (platform, master, slave);
+}
+
+/**
+ * nm_platform_link_release:
+ * @master: Interface index of the master
+ * @slave: Interface index of the slave
+ *
+ * Release @slave from @master.
+ */
+gboolean
+nm_platform_link_release (int master, int slave)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (master > 0, FALSE);
+	g_return_val_if_fail (slave > 0, FALSE);
+	g_return_val_if_fail (klass->link_release, FALSE);
+
+	if (nm_platform_link_get_master (slave) != master) {
+		platform->error = NM_PLATFORM_ERROR_NOT_SLAVE;
+		return FALSE;
+	}
+
+	debug ("link: releasing '%s' (%d) from master '%s' (%d)",
+		nm_platform_link_get_name (slave), slave,
+		nm_platform_link_get_name (master), master);
+	return klass->link_release (platform, master, slave);
+}
+
+/**
+ * nm_platform_link_get_master:
+ * @slave: Interface index of the slave.
+ *
+ * Returns: Interfase index of the slave's master.
+ */
+int
+nm_platform_link_get_master (int slave)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (slave >= 0, FALSE);
+	g_return_val_if_fail (klass->link_get_master, FALSE);
+
+	if (!nm_platform_link_get_name (slave)) {
+		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
+		return 0;
+	}
+	return klass->link_get_master (platform, slave);
+}
+
+/**
+ * nm_platform_bridge_add:
+ * @name: New interface name
+ * @address: (allow-none): set the mac address of the new bridge
+ * @address_len: the length of the @address
+ *
+ * Create a software bridge.
+ */
+gboolean
+nm_platform_bridge_add (const char *name, const void *address, size_t address_len)
+{
+	debug ("link: adding bridge '%s'", name);
+	return nm_platform_link_add (name, NM_LINK_TYPE_BRIDGE, address, address_len);
+}
+
+/**
+ * nm_platform_bond_add:
+ * @name: New interface name
+ *
+ * Create a software bonding device.
+ */
+gboolean
+nm_platform_bond_add (const char *name)
+{
+	debug ("link: adding bond '%s'", name);
+	return nm_platform_link_add (name, NM_LINK_TYPE_BOND, NULL, 0);
+}
+
+/**
+ * nm_platform_team_add:
+ * @name: New interface name
+ *
+ * Create a software teaming device.
+ */
+gboolean
+nm_platform_team_add (const char *name)
+{
+	debug ("link: adding team '%s'", name);
+	return nm_platform_link_add (name, NM_LINK_TYPE_TEAM, NULL, 0);
+}
+
+/**
+ * nm_platform_vlan_add:
+ * @name: New interface name
+ * @vlanid: VLAN identifier
+ * @vlanflags: VLAN flags from libnm-util
+ *
+ * Create a software VLAN device.
+ */
+gboolean
+nm_platform_vlan_add (const char *name, int parent, int vlanid, guint32 vlanflags)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (parent >= 0, FALSE);
+	g_return_val_if_fail (vlanid >= 0, FALSE);
+	g_return_val_if_fail (name, FALSE);
+	g_return_val_if_fail (klass->vlan_add, FALSE);
+
+	if (nm_platform_link_exists (name)) {
+		debug ("link already exists: %s", name);
+		platform->error = NM_PLATFORM_ERROR_EXISTS;
+		return FALSE;
+	}
+
+	debug ("link: adding vlan '%s' parent %d vlanid %d vlanflags %x",
+		name, parent, vlanid, vlanflags);
+	return klass->vlan_add (platform, name, parent, vlanid, vlanflags);
+}
+
+gboolean
+nm_platform_master_set_option (int ifindex, const char *option, const char *value)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (option, FALSE);
+	g_return_val_if_fail (value, FALSE);
+	g_return_val_if_fail (klass->master_set_option, FALSE);
+
+	return klass->master_set_option (platform, ifindex, option, value);
+}
+
+char *
+nm_platform_master_get_option (int ifindex, const char *option)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (option, FALSE);
+	g_return_val_if_fail (klass->master_set_option, FALSE);
+
+	return klass->master_get_option (platform, ifindex, option);
+}
+
+gboolean
+nm_platform_slave_set_option (int ifindex, const char *option, const char *value)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (option, FALSE);
+	g_return_val_if_fail (value, FALSE);
+	g_return_val_if_fail (klass->slave_set_option, FALSE);
+
+	return klass->slave_set_option (platform, ifindex, option, value);
+}
+
+char *
+nm_platform_slave_get_option (int ifindex, const char *option)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (option, FALSE);
+	g_return_val_if_fail (klass->slave_set_option, FALSE);
+
+	return klass->slave_get_option (platform, ifindex, option);
+}
+
+gboolean
+nm_platform_vlan_get_info (int ifindex, int *parent, int *vlanid)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (klass->vlan_get_info, FALSE);
+
+	if (parent)
+		*parent = 0;
+	if (vlanid)
+		*vlanid = 0;
+
+	if (nm_platform_link_get_type (ifindex) != NM_LINK_TYPE_VLAN)
+		return FALSE;
+
+	return klass->vlan_get_info (platform, ifindex, parent, vlanid);
+}
+
+gboolean
+nm_platform_vlan_set_ingress_map (int ifindex, int from, int to)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (klass->vlan_set_ingress_map, FALSE);
+
+	debug ("link: setting vlan ingress map for %d from %d to %d", ifindex, from, to);
+	return klass->vlan_set_ingress_map (platform, ifindex, from, to);
+}
+
+gboolean
+nm_platform_vlan_set_egress_map (int ifindex, int from, int to)
+{
+	reset_error ();
+
+	g_assert (platform);
+	g_return_val_if_fail (klass->vlan_set_egress_map, FALSE);
+
+	debug ("link: setting vlan egress map for %d from %d to %d", ifindex, from, to);
+	return klass->vlan_set_egress_map (platform, ifindex, from, to);
+}
+
+gboolean
+nm_platform_infiniband_partition_add (int parent, int p_key)
+{
+	const char *parent_name;
+	char *name;
+
+	reset_error ();
+
+	g_return_val_if_fail (parent >= 0, FALSE);
+	g_return_val_if_fail (p_key >= 0, FALSE);
+	g_return_val_if_fail (klass->infiniband_partition_add, FALSE);
+
+	if (nm_platform_link_get_type (parent) != NM_LINK_TYPE_INFINIBAND) {
+		platform->error = NM_PLATFORM_ERROR_WRONG_TYPE;
+		return FALSE;
+	}
+
+	parent_name = nm_platform_link_get_name (parent);
+	name = g_strdup_printf ("%s.%04x", parent_name, p_key);
+	if (nm_platform_link_exists (name)) {
+		debug ("infiniband: already exists");
+		platform->error = NM_PLATFORM_ERROR_EXISTS;
+		g_free (name);
+		return FALSE;
+	}
+	g_free (name);
+
+	return klass->infiniband_partition_add (platform, parent, p_key);
+}
+
+gboolean
+nm_platform_veth_get_properties (int ifindex, NMPlatformVethProperties *props)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (props != NULL, FALSE);
+
+	return klass->veth_get_properties (platform, ifindex, props);
+}
+
+gboolean
+nm_platform_tun_get_properties (int ifindex, NMPlatformTunProperties *props)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (props != NULL, FALSE);
+
+	return klass->tun_get_properties (platform, ifindex, props);
+}
+
+gboolean
+nm_platform_macvlan_get_properties (int ifindex, NMPlatformMacvlanProperties *props)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (props != NULL, FALSE);
+
+	return klass->macvlan_get_properties (platform, ifindex, props);
+}
+
+gboolean
+nm_platform_vxlan_get_properties (int ifindex, NMPlatformVxlanProperties *props)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (props != NULL, FALSE);
+
+	return klass->vxlan_get_properties (platform, ifindex, props);
+}
+
+gboolean
+nm_platform_gre_get_properties (int ifindex, NMPlatformGreProperties *props)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (props != NULL, FALSE);
+
+	return klass->gre_get_properties (platform, ifindex, props);
+}
+
+gboolean
+nm_platform_wifi_get_capabilities (int ifindex, NMDeviceWifiCapabilities *caps)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wifi_get_capabilities (platform, ifindex, caps);
+}
+
+gboolean
+nm_platform_wifi_get_bssid (int ifindex, struct ether_addr *bssid)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wifi_get_bssid (platform, ifindex, bssid);
+}
+
+GByteArray *
+nm_platform_wifi_get_ssid (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	return klass->wifi_get_ssid (platform, ifindex);
+}
+
+guint32
+nm_platform_wifi_get_frequency (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, 0);
+
+	return klass->wifi_get_frequency (platform, ifindex);
+}
+
+int
+nm_platform_wifi_get_quality (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, 0);
+
+	return klass->wifi_get_quality (platform, ifindex);
+}
+
+guint32
+nm_platform_wifi_get_rate (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, 0);
+
+	return klass->wifi_get_rate (platform, ifindex);
+}
+
+NM80211Mode
+nm_platform_wifi_get_mode (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, NM_802_11_MODE_UNKNOWN);
+
+	return klass->wifi_get_mode (platform, ifindex);
+}
+
+void
+nm_platform_wifi_set_mode (int ifindex, NM80211Mode mode)
+{
+	reset_error ();
+
+	g_return_if_fail (ifindex > 0);
+
+	klass->wifi_set_mode (platform, ifindex, mode);
+}
+
+guint32
+nm_platform_wifi_find_frequency (int ifindex, const guint32 *freqs)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, 0);
+	g_return_val_if_fail (freqs != NULL, 0);
+
+	return klass->wifi_find_frequency (platform, ifindex, freqs);
+}
+
+void
+nm_platform_wifi_indicate_addressing_running (int ifindex, gboolean running)
+{
+	reset_error ();
+
+	g_return_if_fail (ifindex > 0);
+
+	klass->wifi_indicate_addressing_running (platform, ifindex, running);
+}
+
+guint32
+nm_platform_mesh_get_channel (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, 0);
+
+	return klass->mesh_get_channel (platform, ifindex);
+}
+
+gboolean
+nm_platform_mesh_set_channel (int ifindex, guint32 channel)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->mesh_set_channel (platform, ifindex, channel);
+}
+
+gboolean
+nm_platform_mesh_set_ssid (int ifindex, const GByteArray *ssid)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (ssid != NULL, FALSE);
+
+	return klass->mesh_set_ssid (platform, ifindex, ssid);
+}
+
+/******************************************************************/
+
+GArray *
+nm_platform_ip4_address_get_all (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (klass->ip4_address_get_all, NULL);
+
+	return klass->ip4_address_get_all (platform, ifindex);
+}
+
+GArray *
+nm_platform_ip6_address_get_all (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (klass->ip6_address_get_all, NULL);
+
+	return klass->ip6_address_get_all (platform, ifindex);
+}
+
+gboolean
+nm_platform_ip4_address_add (int ifindex,
+                             in_addr_t address,
+                             in_addr_t peer_address,
+                             int plen,
+                             guint32 lifetime,
+                             guint32 preferred,
+                             const char *label)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (lifetime > 0, FALSE);
+	g_return_val_if_fail (preferred <= lifetime, FALSE);
+	g_return_val_if_fail (klass->ip4_address_add, FALSE);
+	g_return_val_if_fail (!label || strlen (label) < sizeof (((NMPlatformIP4Address *) NULL)->label), FALSE);
+
+	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+		NMPlatformIP4Address addr = { 0 };
+
+		addr.ifindex = ifindex;
+		addr.address = address;
+		addr.peer_address = peer_address;
+		addr.plen = plen;
+		addr.timestamp = 0; /* set it at zero, which to_string will treat as *now* */
+		addr.lifetime = lifetime;
+		addr.preferred = preferred;
+		if (label)
+			g_strlcpy (addr.label, label, sizeof (addr.label));
+
+		debug ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr));
+	}
+	return klass->ip4_address_add (platform, ifindex, address, peer_address, plen, lifetime, preferred, label);
+}
+
+gboolean
+nm_platform_ip6_address_add (int ifindex,
+                             struct in6_addr address,
+                             struct in6_addr peer_address,
+                             int plen,
+                             guint32 lifetime,
+                             guint32 preferred,
+                             guint flags)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (lifetime > 0, FALSE);
+	g_return_val_if_fail (preferred <= lifetime, FALSE);
+	g_return_val_if_fail (klass->ip6_address_add, FALSE);
+
+	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+		NMPlatformIP6Address addr = { 0 };
+
+		addr.ifindex = ifindex;
+		addr.address = address;
+		addr.peer_address = peer_address;
+		addr.plen = plen;
+		addr.timestamp = 0; /* set it to zero, which to_string will treat as *now* */
+		addr.lifetime = lifetime;
+		addr.preferred = preferred;
+		addr.flags = flags;
+
+		debug ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr));
+	}
+	return klass->ip6_address_add (platform, ifindex, address, peer_address, plen, lifetime, preferred, flags);
+}
+
+gboolean
+nm_platform_ip4_address_delete (int ifindex, in_addr_t address, int plen)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (klass->ip4_address_delete, FALSE);
+
+	debug ("address: deleting IPv4 address %s/%d", nm_utils_inet4_ntop (address, NULL), plen);
+	return klass->ip4_address_delete (platform, ifindex, address, plen);
+}
+
+gboolean
+nm_platform_ip6_address_delete (int ifindex, struct in6_addr address, int plen)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (klass->ip6_address_delete, FALSE);
+
+	debug ("address: deleting IPv6 address %s/%d", nm_utils_inet6_ntop (&address, NULL), plen);
+	return klass->ip6_address_delete (platform, ifindex, address, plen);
+}
+
+gboolean
+nm_platform_ip4_address_exists (int ifindex, in_addr_t address, int plen)
+{
+	reset_error ();
+
+	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (klass->ip4_address_exists, FALSE);
+
+	return klass->ip4_address_exists (platform, ifindex, address, plen);
+}
+
+gboolean
+nm_platform_ip6_address_exists (int ifindex, struct in6_addr address, int plen)
+{
+	reset_error ();
+
+	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (klass->ip6_address_exists, FALSE);
+
+	return klass->ip6_address_exists (platform, ifindex, address, plen);
+}
+
+static gboolean
+array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address)
+{
+	guint len = addresses ? addresses->len : 0;
+	guint i;
+
+	for (i = 0; i < len; i++) {
+		NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
+
+		if (candidate->address == address->address && candidate->plen == address->plen)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address *address)
+{
+	guint len = addresses ? addresses->len : 0;
+	guint i;
+
+	for (i = 0; i < len; i++) {
+		NMPlatformIP6Address *candidate = &g_array_index (addresses, NMPlatformIP6Address, i);
+
+		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**
+ * Takes a pair @timestamp and @duration, and returns the remaining duration based
+ * on the new timestamp @now.
+ */
+static guint32
+_rebase_relative_time_on_now (guint32 timestamp, guint32 duration, guint32 now, guint32 padding)
+{
+	gint64 t;
+
+	if (duration == NM_PLATFORM_LIFETIME_PERMANENT)
+		return NM_PLATFORM_LIFETIME_PERMANENT;
+
+	if (timestamp == 0) {
+		/* if the @timestamp is zero, assume it was just left unset and that the relative
+		 * @duration starts counting from @now. This is convenient to construct an address
+		 * and print it in nm_platform_ip4_address_to_string().
+		 *
+		 * In general it does not make sense to set the @duration without anchoring at
+		 * @timestamp because you don't know the absolute expiration time when looking
+		 * at the address at a later moment. */
+		timestamp = now;
+	}
+
+	/* For timestamp > now, just accept it and calculate the expected(?) result. */
+	t = (gint64) timestamp + (gint64) duration - (gint64) now;
+
+	/* Optional padding to avoid potential races. */
+	t += (gint64) padding;
+
+	if (t <= 0)
+		return 0;
+	if (t >= NM_PLATFORM_LIFETIME_PERMANENT)
+		return NM_PLATFORM_LIFETIME_PERMANENT - 1;
+	return t;
+}
+
+static gboolean
+_address_get_lifetime (const NMPlatformIPAddress *address, guint32 now, guint32 padding, guint32 *out_lifetime, guint32 *out_preferred)
+{
+	gint32 lifetime, preferred;
+
+	if (address->lifetime == 0) {
+		*out_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
+		*out_preferred = NM_PLATFORM_LIFETIME_PERMANENT;
+	} else {
+		lifetime = _rebase_relative_time_on_now (address->timestamp, address->lifetime, now, padding);
+		if (!lifetime)
+			return FALSE;
+		preferred = _rebase_relative_time_on_now (address->timestamp, address->preferred, now, padding);
+
+		*out_lifetime = lifetime;
+		*out_preferred = MIN (preferred, lifetime);
+
+		/* Assert that non-permanent addresses have a (positive) @timestamp. _rebase_relative_time_on_now()
+		 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
+		 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
+		 */
+		g_return_val_if_fail (   address->timestamp != 0
+		                      || (   address->lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
+		                          && address->preferred == NM_PLATFORM_LIFETIME_PERMANENT), TRUE);
+		g_return_val_if_fail (preferred <= lifetime, TRUE);
+	}
+	return TRUE;
+}
+
+/**
+ * nm_platform_ip4_address_sync:
+ * @ifindex: Interface index
+ * @known_addresses: List of addresses
+ *
+ * A convenience function to synchronize addresses for a specific interface
+ * with the least possible disturbance. It simply removes addresses that are
+ * not listed and adds addresses that are.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses)
+{
+	GArray *addresses;
+	NMPlatformIP4Address *address;
+	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	int i;
+
+	/* Delete unknown addresses */
+	addresses = nm_platform_ip4_address_get_all (ifindex);
+	for (i = 0; i < addresses->len; i++) {
+		address = &g_array_index (addresses, NMPlatformIP4Address, i);
+
+		if (!array_contains_ip4_address (known_addresses, address))
+			nm_platform_ip4_address_delete (ifindex, address->address, address->plen);
+	}
+	g_array_free (addresses, TRUE);
+
+	if (!known_addresses)
+		return TRUE;
+
+	/* Add missing addresses */
+	for (i = 0; i < known_addresses->len; i++) {
+		const NMPlatformIP4Address *known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
+		guint32 lifetime, preferred;
+
+		/* add a padding of 5 seconds to avoid potential races. */
+		if (!_address_get_lifetime ((NMPlatformIPAddress *) known_address, now, 5, &lifetime, &preferred))
+			continue;
+
+		if (!nm_platform_ip4_address_add (ifindex, known_address->address, known_address->peer_address, known_address->plen, lifetime, preferred, known_address->label))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**
+ * nm_platform_ip6_address_sync:
+ * @ifindex: Interface index
+ * @known_addresses: List of addresses
+ *
+ * A convenience function to synchronize addresses for a specific interface
+ * with the least possible disturbance. It simply removes addresses that are
+ * not listed and adds addresses that are.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses)
+{
+	GArray *addresses;
+	NMPlatformIP6Address *address;
+	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	int i;
+
+	/* Delete unknown addresses */
+	addresses = nm_platform_ip6_address_get_all (ifindex);
+	for (i = 0; i < addresses->len; i++) {
+		address = &g_array_index (addresses, NMPlatformIP6Address, i);
+
+		/* Leave link local address management to the kernel */
+		if (IN6_IS_ADDR_LINKLOCAL (&address->address))
+			continue;
+
+		if (!array_contains_ip6_address (known_addresses, address))
+			nm_platform_ip6_address_delete (ifindex, address->address, address->plen);
+	}
+	g_array_free (addresses, TRUE);
+
+	if (!known_addresses)
+		return TRUE;
+
+	/* Add missing addresses */
+	for (i = 0; i < known_addresses->len; i++) {
+		const NMPlatformIP6Address *known_address = &g_array_index (known_addresses, NMPlatformIP6Address, i);
+		guint32 lifetime, preferred;
+
+		/* add a padding of 5 seconds to avoid potential races. */
+		if (!_address_get_lifetime ((NMPlatformIPAddress *) known_address, now, 5, &lifetime, &preferred))
+			continue;
+
+		if (!nm_platform_ip6_address_add (ifindex, known_address->address,
+		                                  known_address->peer_address, known_address->plen,
+		                                  lifetime, preferred, known_address->flags))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_platform_address_flush (int ifindex)
+{
+	return nm_platform_ip4_address_sync (ifindex, NULL)
+			&& nm_platform_ip6_address_sync (ifindex, NULL);
+}
+
+/******************************************************************/
+
+GArray *
+nm_platform_ip4_route_get_all (int ifindex, gboolean include_default)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (klass->ip4_route_get_all, NULL);
+
+	return klass->ip4_route_get_all (platform, ifindex, include_default);
+}
+
+GArray *
+nm_platform_ip6_route_get_all (int ifindex, gboolean include_default)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (klass->ip6_route_get_all, NULL);
+
+	return klass->ip6_route_get_all (platform, ifindex, include_default);
+}
+
+gboolean
+nm_platform_ip4_route_add (int ifindex, NMPlatformSource source,
+                           in_addr_t network, int plen,
+                           in_addr_t gateway, int metric, int mss)
+{
+	reset_error ();
+
+	g_return_val_if_fail (platform, FALSE);
+	g_return_val_if_fail (0 <= plen && plen <= 32, FALSE);
+	g_return_val_if_fail (metric >= 0, FALSE);
+	g_return_val_if_fail (mss >= 0, FALSE);
+	g_return_val_if_fail (klass->ip4_route_add, FALSE);
+
+	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+		NMPlatformIP4Route route = { 0 };
+
+		route.ifindex = ifindex;
+		route.source = source;
+		route.network = network;
+		route.plen = plen;
+		route.gateway = gateway;
+		route.metric = metric;
+		route.mss = mss;
+
+		debug ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (&route));
+	}
+	return klass->ip4_route_add (platform, ifindex, source, network, plen, gateway, metric, mss);
+}
+
+gboolean
+nm_platform_ip6_route_add (int ifindex, NMPlatformSource source,
+                           struct in6_addr network, int plen, struct in6_addr gateway,
+                           int metric, int mss)
+{
+	g_return_val_if_fail (platform, FALSE);
+	g_return_val_if_fail (0 <= plen && plen <= 128, FALSE);
+	g_return_val_if_fail (metric >= 0, FALSE);
+	g_return_val_if_fail (mss >= 0, FALSE);
+	g_return_val_if_fail (klass->ip6_route_add, FALSE);
+
+	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+		NMPlatformIP6Route route = { 0 };
+
+		route.ifindex = ifindex;
+		route.source = source;
+		route.network = network;
+		route.plen = plen;
+		route.gateway = gateway;
+		route.metric = metric;
+		route.mss = mss;
+
+		debug ("route: adding or updating IPv6 route: %s", nm_platform_ip6_route_to_string (&route));
+	}
+	return klass->ip6_route_add (platform, ifindex, source, network, plen, gateway, metric, mss);
+}
+
+gboolean
+nm_platform_ip4_route_delete (int ifindex, in_addr_t network, int plen, int metric)
+{
+	reset_error ();
+
+	g_return_val_if_fail (platform, FALSE);
+	g_return_val_if_fail (klass->ip4_route_delete, FALSE);
+
+	debug ("route: deleting IPv4 route %s/%d, metric=%d", nm_utils_inet4_ntop (network, NULL), plen, metric);
+	return klass->ip4_route_delete (platform, ifindex, network, plen, metric);
+}
+
+gboolean
+nm_platform_ip6_route_delete (int ifindex, struct in6_addr network, int plen, int metric)
+{
+	reset_error ();
+
+	g_return_val_if_fail (platform, FALSE);
+	g_return_val_if_fail (klass->ip6_route_delete, FALSE);
+
+	debug ("route: deleting IPv6 route %s/%d, metric=%d", nm_utils_inet6_ntop (&network, NULL), plen, metric);
+	return klass->ip6_route_delete (platform, ifindex, network, plen, metric);
+}
+
+gboolean
+nm_platform_ip4_route_exists (int ifindex, in_addr_t network, int plen, int metric)
+{
+	reset_error ();
+
+	g_return_val_if_fail (platform, FALSE);
+	g_return_val_if_fail (klass->ip4_route_exists, FALSE);
+
+	return klass->ip4_route_exists (platform,ifindex, network, plen, metric);
+}
+
+gboolean
+nm_platform_ip6_route_exists (int ifindex, struct in6_addr network, int plen, int metric)
+{
+	reset_error ();
+
+	g_return_val_if_fail (platform, FALSE);
+	g_return_val_if_fail (klass->ip6_route_exists, FALSE);
+
+	return klass->ip6_route_exists (platform, ifindex, network, plen, metric);
+}
+
+static gboolean
+array_contains_ip4_route (const GArray *routes, const NMPlatformIP4Route *route)
+{
+	guint len = routes ? routes->len : 0;
+	guint i;
+
+	for (i = 0; i < len; i++) {
+		NMPlatformIP4Route *c = &g_array_index (routes, NMPlatformIP4Route, i);
+
+		if (route->network == c->network &&
+		    route->plen == c->plen &&
+		    route->gateway == c->gateway &&
+		    route->metric == c->metric)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+array_contains_ip6_route (const GArray *routes, const NMPlatformIP6Route *route)
+{
+	guint len = routes ? routes->len : 0;
+	guint i;
+
+	for (i = 0; i < len; i++) {
+		NMPlatformIP6Route *c = &g_array_index (routes, NMPlatformIP6Route, i);
+
+		if (IN6_ARE_ADDR_EQUAL (&route->network, &c->network) &&
+		    route->plen == c->plen &&
+		    IN6_ARE_ADDR_EQUAL (&route->gateway, &c->gateway) &&
+		    route->metric == c->metric)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**
+ * nm_platform_ip4_route_sync:
+ * @ifindex: Interface index
+ * @known_routes: List of routes
+ *
+ * A convenience function to synchronize routes for a specific interface
+ * with the least possible disturbance. It simply removes routes that are
+ * not listed and adds routes that are.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nm_platform_ip4_route_sync (int ifindex, const GArray *known_routes)
+{
+	GArray *routes;
+	NMPlatformIP4Route *route;
+	const NMPlatformIP4Route *known_route;
+	gboolean success;
+	int i, i_type;
+
+	/* Delete unknown routes */
+	routes = nm_platform_ip4_route_get_all (ifindex, FALSE);
+	for (i = 0; i < routes->len; i++) {
+		route = &g_array_index (routes, NMPlatformIP4Route, i);
+
+		if (!array_contains_ip4_route (known_routes, route))
+			nm_platform_ip4_route_delete (ifindex, route->network, route->plen, route->metric);
+	}
+
+	if (!known_routes) {
+		g_array_free (routes, TRUE);
+		return TRUE;
+	}
+
+	/* Add missing routes */
+	for (i_type = 0, success = TRUE; i_type < 2 && success; i_type++) {
+		for (i = 0; i < known_routes->len && success; i++) {
+			known_route = &g_array_index (known_routes, NMPlatformIP4Route, i);
+
+			if ((known_route->gateway == 0) ^ (i_type != 0)) {
+				/* Make two runs over the list of routes. On the first, only add
+				 * device routes, on the second the others (gateway routes). */
+				continue;
+			}
+
+			/* Ignore routes that already exist */
+			if (!array_contains_ip4_route (routes, known_route)) {
+				success = nm_platform_ip4_route_add (ifindex,
+				                                     known_route->source,
+				                                     known_route->network,
+				                                     known_route->plen,
+				                                     known_route->gateway,
+				                                     known_route->metric,
+				                                     known_route->mss);
+				if (!success && known_route->source < NM_PLATFORM_SOURCE_USER) {
+					nm_log_dbg (LOGD_PLATFORM, "ignore error adding IPv4 route to kernel: %s",
+					                           nm_platform_ip4_route_to_string (known_route));
+					success = TRUE;
+				}
+			}
+		}
+	}
+
+	g_array_free (routes, TRUE);
+	return success;
+}
+
+/**
+ * nm_platform_ip6_route_sync:
+ * @ifindex: Interface index
+ * @known_routes: List of routes
+ *
+ * A convenience function to synchronize routes for a specific interface
+ * with the least possible disturbance. It simply removes routes that are
+ * not listed and adds routes that are.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nm_platform_ip6_route_sync (int ifindex, const GArray *known_routes)
+{
+	GArray *routes;
+	NMPlatformIP6Route *route;
+	const NMPlatformIP6Route *known_route;
+	gboolean success;
+	int i, i_type;
+
+	/* Delete unknown routes */
+	routes = nm_platform_ip6_route_get_all (ifindex, FALSE);
+	for (i = 0; i < routes->len; i++) {
+		route = &g_array_index (routes, NMPlatformIP6Route, i);
+		route->ifindex = 0;
+
+		if (!array_contains_ip6_route (known_routes, route))
+			nm_platform_ip6_route_delete (ifindex, route->network, route->plen, route->metric);
+	}
+
+	if (!known_routes) {
+		g_array_free (routes, TRUE);
+		return TRUE;
+	}
+
+	/* Add missing routes */
+	for (i_type = 0, success = TRUE; i_type < 2 && success; i_type++) {
+		for (i = 0; i < known_routes->len && success; i++) {
+			known_route = &g_array_index (known_routes, NMPlatformIP6Route, i);
+
+			if (IN6_IS_ADDR_UNSPECIFIED (&known_route->gateway) ^ (i_type != 0)) {
+				/* Make two runs over the list of routes. On the first, only add
+				 * device routes, on the second the others (gateway routes). */
+				continue;
+			}
+
+			/* Ignore routes that already exist */
+			if (!array_contains_ip6_route (routes, known_route)) {
+				success = nm_platform_ip6_route_add (ifindex,
+				                                     known_route->source,
+				                                     known_route->network,
+				                                     known_route->plen,
+				                                     known_route->gateway,
+				                                     known_route->metric,
+				                                     known_route->mss);
+				if (!success && known_route->source < NM_PLATFORM_SOURCE_USER) {
+					nm_log_dbg (LOGD_PLATFORM, "ignore error adding IPv6 route to kernel: %s",
+					                           nm_platform_ip6_route_to_string (known_route));
+					success = TRUE;
+				}
+			}
+		}
+	}
+
+	g_array_free (routes, TRUE);
+	return success;
+}
+
+gboolean
+nm_platform_route_flush (int ifindex)
+{
+	return nm_platform_ip4_route_sync (ifindex, NULL)
+			&& nm_platform_ip6_route_sync (ifindex, NULL);
+}
+
+/******************************************************************/
+
+static const char *
+source_to_string (NMPlatformSource source)
+{
+	switch (source) {
+	case NM_PLATFORM_SOURCE_KERNEL:
+		return "kernel";
+	case NM_PLATFORM_SOURCE_SHARED:
+		return "shared";
+	case NM_PLATFORM_SOURCE_IP4LL:
+		return "ipv4ll";
+	case NM_PLATFORM_SOURCE_PPP:
+		return "ppp";
+	case NM_PLATFORM_SOURCE_WWAN:
+		return "wwan";
+	case NM_PLATFORM_SOURCE_VPN:
+		return "vpn";
+	case NM_PLATFORM_SOURCE_DHCP:
+		return "dhcp";
+	case NM_PLATFORM_SOURCE_RDISC:
+		return "rdisc";
+	case NM_PLATFORM_SOURCE_USER:
+		return "user";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
+#define TO_STRING_DEV_BUF_SIZE (5+15+1)
+static void
+_to_string_dev (int ifindex, char *buf, size_t size)
+{
+	g_assert (buf && size >= TO_STRING_DEV_BUF_SIZE);
+
+	if (ifindex){
+		const char *name = ifindex > 0 ? nm_platform_link_get_name (ifindex) : NULL;
+
+		strcpy (buf, " dev ");
+		buf += 5;
+		size -= 5;
+
+		if (name)
+			g_strlcpy (buf, name, size);
+		else
+			g_snprintf (buf, size, "%d", ifindex);
+	} else
+		buf[0] = 0;
+}
+
+static const char *
+_lifetime_to_string (guint32 timestamp, guint32 lifetime, gint32 now, char *buf, size_t buf_size)
+{
+	if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT)
+		return "forever";
+
+	g_snprintf (buf, buf_size, "%usec",
+	            _rebase_relative_time_on_now (timestamp, lifetime, now, 0));
+	return buf;
+}
+
+
+static const char *
+_lifetime_summary_to_string (gint32 now, guint32 timestamp, guint32 preferred, guint32 lifetime, char *buf, size_t buf_size)
+{
+	g_snprintf (buf, buf_size, " lifetime %d-%u[%u,%u]",
+	            (signed) now, (unsigned) timestamp, (unsigned) preferred, (unsigned) lifetime);
+	return buf;
+}
+
+static char to_string_buffer[256];
+
+const char *
+nm_platform_link_to_string (const NMPlatformLink *link)
+{
+	char master[20];
+	char parent[20];
+	char *driver, *udi, *type;
+	GString *str;
+
+	if (!link)
+		return "(unknown link)";
+
+	str = g_string_new (NULL);
+	if (!link->arp)
+		g_string_append (str, "NOARP,");
+	if (link->up)
+		g_string_append (str, "UP");
+	else
+		g_string_append (str, "DOWN");
+	if (link->connected)
+		g_string_append (str, ",LOWER_UP");
+
+	if (link->master)
+		g_snprintf (master, sizeof (master), " master %d", link->master);
+	else
+		master[0] = 0;
+
+	if (link->parent)
+		g_snprintf (parent, sizeof (master), "@%d", link->parent);
+	else
+		parent[0] = 0;
+
+	driver = link->driver ? g_strdup_printf (" driver '%s'", link->driver) : NULL;
+	udi = link->udi ? g_strdup_printf (" udi '%s'", link->udi) : NULL;
+	type = link->type_name ? NULL : g_strdup_printf ("(%d)", link->type);
+
+	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%d: %s%s <%s> mtu %d%s %s%s%s",
+	            link->ifindex, link->name, parent, str->str,
+	            link->mtu, master, link->type_name ? link->type_name : type,
+	            driver ? driver : "", udi ? udi : "");
+	g_string_free (str, TRUE);
+	g_free (driver);
+	g_free (udi);
+	g_free (type);
+	return to_string_buffer;
+}
+
+/**
+ * nm_platform_ip4_address_to_string:
+ * @route: pointer to NMPlatformIP4Address address structure
+ *
+ * A method for converting an address struct into a string representation.
+ *
+ * Example output: ""
+ *
+ * Returns: a string representation of the address. The returned string
+ * is an internal buffer, so do not keep or free the returned string.
+ * Also, this function is not thread safe.
+ */
+const char *
+nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
+{
+	char s_address[INET_ADDRSTRLEN];
+	char s_peer[INET_ADDRSTRLEN];
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char str_label[32];
+	char str_lft[30], str_pref[30], str_time[50];
+	char *str_peer = NULL;
+	const char *str_lft_p, *str_pref_p, *str_time_p;
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+
+	g_return_val_if_fail (address, "(unknown)");
+
+	inet_ntop (AF_INET, &address->address, s_address, sizeof (s_address));
+
+	if (address->peer_address) {
+		inet_ntop (AF_INET, &address->peer_address, s_peer, sizeof (s_peer));
+		str_peer = g_strconcat (" ptp ", s_peer, NULL);
+	}
+
+	_to_string_dev (address->ifindex, str_dev, sizeof (str_dev));
+
+	if (*address->label)
+		g_snprintf (str_label, sizeof (str_label), " label %s", address->label);
+	else
+		str_label[0] = 0;
+
+	str_lft_p = _lifetime_to_string (address->timestamp,
+	                                 address->lifetime ? address->lifetime : NM_PLATFORM_LIFETIME_PERMANENT,
+	                                 now, str_lft, sizeof (str_lft)),
+	str_pref_p = (address->lifetime == address->preferred)
+	             ? str_lft_p
+	             : ( _lifetime_to_string (address->timestamp,
+	                                      address->lifetime ? MIN (address->preferred, address->lifetime) : NM_PLATFORM_LIFETIME_PERMANENT,
+	                                      now, str_pref, sizeof (str_pref)) );
+	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
+
+	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
+	            str_peer ? str_peer : "",
+	            str_dev,
+	            str_label,
+	            source_to_string (address->source));
+	g_free (str_peer);
+	return to_string_buffer;
+}
+
+/**
+ * nm_platform_addr_flags2str: wrapper for rtnl_addr_flags2str(),
+ * which might not yet support some recent address flags.
+ **/
+void
+nm_platform_addr_flags2str (int flags, char *buf, size_t size)
+{
+	rtnl_addr_flags2str(flags, buf, size);
+
+	/* There are two recent flags IFA_F_MANAGETEMPADDR and IFA_F_NOPREFIXROUTE.
+	 * If libnl does not yet support them, add them by hand.
+	 * These two flags were introduced together with the extended ifa_flags,
+	 * so, check for that.
+	 */
+	if ((flags & IFA_F_MANAGETEMPADDR) && !nm_platform_check_support_libnl_extended_ifa_flags ()) {
+		strncat (buf, buf[0] ? "," IFA_F_MANAGETEMPADDR_STR : IFA_F_MANAGETEMPADDR_STR,
+		         size - strlen (buf) - 1);
+	}
+	if ((flags & IFA_F_NOPREFIXROUTE) && !nm_platform_check_support_libnl_extended_ifa_flags ()) {
+		strncat (buf, buf[0] ? "," IFA_F_NOPREFIXROUTE_STR : IFA_F_NOPREFIXROUTE_STR,
+		         size - strlen (buf) - 1);
+	}
+}
+
+/**
+ * nm_platform_ip6_address_to_string:
+ * @route: pointer to NMPlatformIP6Address address structure
+ *
+ * A method for converting an address struct into a string representation.
+ *
+ * Example output: "2001:db8:0:f101::1/64 lft 4294967295 pref 4294967295 time 16922666 on dev em1"
+ *
+ * Returns: a string representation of the address. The returned string
+ * is an internal buffer, so do not keep or free the returned string.
+ * Also, this function is not thread safe.
+ */
+const char *
+nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
+{
+	char s_flags[256];
+	char s_address[INET6_ADDRSTRLEN];
+	char s_peer[INET6_ADDRSTRLEN];
+	char str_lft[30], str_pref[30], str_time[50];
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char *str_flags;
+	char *str_peer = NULL;
+	const char *str_lft_p, *str_pref_p, *str_time_p;
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+
+	g_return_val_if_fail (address, "(unknown)");
+
+	inet_ntop (AF_INET6, &address->address, s_address, sizeof (s_address));
+
+	if (!IN6_IS_ADDR_UNSPECIFIED (&address->peer_address)) {
+		inet_ntop (AF_INET6, &address->peer_address, s_peer, sizeof (s_peer));
+		str_peer = g_strconcat (" ptp ", s_peer, NULL);
+	}
+
+	_to_string_dev (address->ifindex, str_dev, sizeof (str_dev));
+
+	nm_platform_addr_flags2str (address->flags, s_flags, sizeof (s_flags));
+
+	str_flags = s_flags[0] ? g_strconcat (" flags ", s_flags, NULL) : NULL;
+
+	str_lft_p = _lifetime_to_string (address->timestamp,
+	                                 address->lifetime ? address->lifetime : NM_PLATFORM_LIFETIME_PERMANENT,
+	                                 now, str_lft, sizeof (str_lft)),
+	str_pref_p = (address->lifetime == address->preferred)
+	             ? str_lft_p
+	             : ( _lifetime_to_string (address->timestamp,
+	                                      address->lifetime ? MIN (address->preferred, address->lifetime) : NM_PLATFORM_LIFETIME_PERMANENT,
+	                                      now, str_pref, sizeof (str_pref)) );
+	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
+
+	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
+	            str_peer ? str_peer : "",
+	            str_dev,
+	            str_flags ? str_flags : "",
+	            source_to_string (address->source));
+	g_free (str_flags);
+	g_free (str_peer);
+	return to_string_buffer;
+}
+
+/**
+ * nm_platform_ip4_route_to_string:
+ * @route: pointer to NMPlatformIP4Route route structure
+ *
+ * A method for converting a route struct into a string representation.
+ *
+ * Example output: "192.168.1.0/24 via 0.0.0.0 dev em1 metric 0 mss 0"
+ *
+ * Returns: a string representation of the route. The returned string
+ * is an internal buffer, so do not keep or free the returned string.
+ * Also, this function is not thread safe.
+ */
+const char *
+nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route)
+{
+	char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+
+	g_return_val_if_fail (route, "(unknown)");
+
+	inet_ntop (AF_INET, &route->network, s_network, sizeof(s_network));
+	inet_ntop (AF_INET, &route->gateway, s_gateway, sizeof(s_gateway));
+
+	_to_string_dev (route->ifindex, str_dev, sizeof (str_dev));
+
+	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%s/%d via %s%s metric %u mss %u src %s",
+	            s_network, route->plen, s_gateway,
+	            str_dev,
+	            route->metric, route->mss,
+	            source_to_string (route->source));
+	return to_string_buffer;
+}
+
+/**
+ * nm_platform_ip6_route_to_string:
+ * @route: pointer to NMPlatformIP6Route route structure
+ *
+ * A method for converting a route struct into a string representation.
+ *
+ * Example output: "ff02::fb/128 via :: dev em1 metric 0"
+ *
+ * Returns: a string representation of the route. The returned string
+ * is an internal buffer, so do not keep or free the returned string.
+ * Also, this function is not thread safe.
+ */
+const char *
+nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
+{
+	char s_network[INET6_ADDRSTRLEN], s_gateway[INET6_ADDRSTRLEN];
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+
+	g_return_val_if_fail (route, "(unknown)");
+
+	inet_ntop (AF_INET6, &route->network, s_network, sizeof(s_network));
+	inet_ntop (AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway));
+
+	_to_string_dev (route->ifindex, str_dev, sizeof (str_dev));
+
+	g_snprintf (to_string_buffer, sizeof (to_string_buffer), "%s/%d via %s%s metric %u mss %u src %s",
+	            s_network, route->plen, s_gateway,
+	            str_dev,
+	            route->metric, route->mss,
+	            source_to_string (route->source));
+	return to_string_buffer;
+}
+
+#define _CMP_POINTER(a, b)                                  \
+    G_STMT_START {                                          \
+        if ((a) == (b))                                     \
+            return 0;                                       \
+        if (!(a))                                           \
+            return -1;                                      \
+        if (!(b))                                           \
+            return 1;                                       \
+    } G_STMT_END
+
+#define _CMP_FIELD(a, b, field)                             \
+    G_STMT_START {                                          \
+        if (((a)->field) != ((b)->field))                   \
+            return (((a)->field) < ((b)->field)) ? -1 : 1;  \
+    } G_STMT_END
+
+#define _CMP_FIELD_STR(a, b, field)                         \
+    G_STMT_START {                                          \
+        int c = strcmp ((a)->field, (b)->field);            \
+        if (c != 0)                                         \
+            return c < 0 ? -1 : 1;                          \
+    } G_STMT_END
+
+#define _CMP_FIELD_STR0(a, b, field)                        \
+    G_STMT_START {                                          \
+        int c = g_strcmp0 ((a)->field, (b)->field);         \
+        if (c != 0)                                         \
+            return c < 0 ? -1 : 1;                          \
+    } G_STMT_END
+
+#define _CMP_FIELD_MEMCMP(a, b, field)                      \
+    G_STMT_START {                                          \
+        int c = memcmp (&((a)->field), &((b)->field),       \
+                        sizeof ((a)->field));               \
+        if (c != 0)                                         \
+            return c < 0 ? -1 : 1;                          \
+    } G_STMT_END
+
+int
+nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
+{
+	_CMP_POINTER (a, b);
+	_CMP_FIELD (a, b, type);
+	_CMP_FIELD_STR (a, b, name);
+	_CMP_FIELD (a, b, master);
+	_CMP_FIELD (a, b, parent);
+	_CMP_FIELD (a, b, up);
+	_CMP_FIELD (a, b, connected);
+	_CMP_FIELD (a, b, arp);
+	_CMP_FIELD (a, b, mtu);
+	_CMP_FIELD_STR0 (a, b, type_name);
+	_CMP_FIELD_STR0 (a, b, udi);
+	_CMP_FIELD_STR0 (a, b, driver);
+	return 0;
+}
+
+int
+nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
+{
+	_CMP_POINTER (a, b);
+	_CMP_FIELD (a, b, ifindex);
+	_CMP_FIELD (a, b, source);
+	_CMP_FIELD (a, b, address);
+	_CMP_FIELD (a, b, peer_address);
+	_CMP_FIELD (a, b, plen);
+	_CMP_FIELD (a, b, timestamp);
+	_CMP_FIELD (a, b, lifetime);
+	_CMP_FIELD (a, b, preferred);
+	_CMP_FIELD_STR (a, b, label);
+	return 0;
+}
+
+int
+nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
+{
+	_CMP_POINTER (a, b);
+	_CMP_FIELD (a, b, ifindex);
+	_CMP_FIELD (a, b, source);
+	_CMP_FIELD_MEMCMP (a, b, address);
+	_CMP_FIELD_MEMCMP (a, b, peer_address);
+	_CMP_FIELD (a, b, plen);
+	_CMP_FIELD (a, b, timestamp);
+	_CMP_FIELD (a, b, lifetime);
+	_CMP_FIELD (a, b, preferred);
+	_CMP_FIELD (a, b, flags);
+	return 0;
+}
+
+int
+nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b)
+{
+	_CMP_POINTER (a, b);
+	_CMP_FIELD (a, b, ifindex);
+	_CMP_FIELD (a, b, source);
+	_CMP_FIELD (a, b, network);
+	_CMP_FIELD (a, b, plen);
+	_CMP_FIELD (a, b, gateway);
+	_CMP_FIELD (a, b, metric);
+	_CMP_FIELD (a, b, mss);
+	return 0;
+}
+
+int
+nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b)
+{
+	_CMP_POINTER (a, b);
+	_CMP_FIELD (a, b, ifindex);
+	_CMP_FIELD (a, b, source);
+	_CMP_FIELD_MEMCMP (a, b, network);
+	_CMP_FIELD (a, b, plen);
+	_CMP_FIELD_MEMCMP (a, b, gateway);
+	_CMP_FIELD (a, b, metric);
+	_CMP_FIELD (a, b, mss);
+	return 0;
+}
+
+#undef _CMP_FIELD
+#undef _CMP_FIELD_MEMCMP
+
+/**
+ * nm_platform_ip_address_cmp_expiry:
+ * @a: a NMPlatformIPAddress to compare
+ * @b: the other NMPlatformIPAddress to compare
+ *
+ * Compares two addresses and returns which one has a longer remaining lifetime.
+ * If both addresses have the same lifetime, look at the remaining preferred time.
+ *
+ * For comparison, only the timestamp, lifetime and preferred fields are considered.
+ * If they compare equal (== 0), their other fields were not considered.
+ *
+ * Returns: -1, 0, or 1 according to the comparison
+ **/
+int
+nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatformIPAddress *b)
+{
+	gint64 ta = 0, tb = 0;
+
+	_CMP_POINTER (a, b);
+
+	if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0)
+		ta = G_MAXINT64;
+	else if (a->timestamp)
+		ta = ((gint64) a->timestamp) + a->lifetime;
+
+	if (b->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0)
+		tb = G_MAXINT64;
+	else if (b->timestamp)
+		tb = ((gint64) b->timestamp) + b->lifetime;
+
+	if (ta == tb) {
+		/* if the lifetime is equal, compare the preferred time. */
+		ta = tb = 0;
+
+		if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0 /* liftime==0 means permanent! */)
+			ta = G_MAXINT64;
+		else if (a->timestamp)
+			ta = ((gint64) a->timestamp) + a->preferred;
+
+		if (b->preferred == NM_PLATFORM_LIFETIME_PERMANENT|| b->lifetime == 0)
+			tb = G_MAXINT64;
+		else if (b->timestamp)
+			tb = ((gint64) b->timestamp) + b->preferred;
+
+		if (ta == tb)
+			return 0;
+	}
+
+	return ta < tb ? -1 : 1;
+}
+
+#undef _CMP_POINTER
+
+static const char *
+_change_type_to_string (NMPlatformSignalChangeType change_type)
+{
+	switch (change_type) {
+	case NM_PLATFORM_SIGNAL_ADDED:
+		return "added";
+	case NM_PLATFORM_SIGNAL_CHANGED:
+		return "changed";
+	case NM_PLATFORM_SIGNAL_REMOVED:
+		return "removed";
+	default:
+		g_return_val_if_reached ("UNKNOWN");
+	}
+}
+
+static void
+log_link (NMPlatform *p, int ifindex, NMPlatformLink *device, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+
+	debug ("signal: link %7s: %s", _change_type_to_string (change_type), nm_platform_link_to_string (device));
+}
+
+static void
+log_ip4_address (NMPlatform *p, int ifindex, NMPlatformIP4Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	debug ("signal: address 4 %7s: %s", _change_type_to_string (change_type), nm_platform_ip4_address_to_string (address));
+}
+
+static void
+log_ip6_address (NMPlatform *p, int ifindex, NMPlatformIP6Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	debug ("signal: address 6 %7s: %s", _change_type_to_string (change_type), nm_platform_ip6_address_to_string (address));
+}
+
+static void
+log_ip4_route (NMPlatform *p, int ifindex, NMPlatformIP4Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	debug ("signal: route   4 %7s: %s", _change_type_to_string (change_type), nm_platform_ip4_route_to_string (route));
+}
+
+static void
+log_ip6_route (NMPlatform *p, int ifindex, NMPlatformIP6Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	debug ("signal: route   6 %7s: %s", _change_type_to_string (change_type), nm_platform_ip6_route_to_string (route));
+}
+
+/******************************************************************/
+
+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, NULL, \
+		G_TYPE_NONE, 4, G_TYPE_INT, G_TYPE_POINTER, NM_TYPE_PLATFORM_SIGNAL_CHANGE_TYPE, NM_TYPE_PLATFORM_REASON);
+
+static void
+nm_platform_class_init (NMPlatformClass *platform_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (platform_class);
+
+	/* Signals */
+	SIGNAL (SIGNAL_LINK_CHANGED, log_link)
+	SIGNAL (SIGNAL_IP4_ADDRESS_CHANGED, log_ip4_address)
+	SIGNAL (SIGNAL_IP6_ADDRESS_CHANGED, log_ip6_address)
+	SIGNAL (SIGNAL_IP4_ROUTE_CHANGED, log_ip4_route)
+	SIGNAL (SIGNAL_IP6_ROUTE_CHANGED, log_ip6_route)
+}