summary refs log tree commit diff
path: root/src/platform/nm-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
committerMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
commit494f296a3baab08522617b24b1f126d8f9a17502 (patch)
treec8ef32fb0dd1c4ff35a0b38e787abb58692de0cd /src/platform/nm-platform.c
parent54f6333410ffd570e62717d9e77c5c987175e397 (diff)
Imported Upstream version 1.1.90 upstream/1.1.90
Diffstat (limited to 'src/platform/nm-platform.c')
-rw-r--r--src/platform/nm-platform.c1731
1 files changed, 1409 insertions, 322 deletions
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 9948ac8b..3bb44e1a 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -28,20 +28,27 @@
 #include <string.h>
 #include <netlink/route/addr.h>
 #include <netlink/route/rtnl.h>
+#include <linux/ip.h>
+#include <linux/if_tun.h>
+#include <linux/if_tunnel.h>
 
-#include "gsystem-local-alloc.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-platform.h"
 #include "nm-platform-utils.h"
+#include "nmp-object.h"
 #include "NetworkManagerUtils.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "nm-enum-types.h"
 #include "nm-core-internal.h"
 
 #define ADDRESS_LIFETIME_PADDING 5
 
 G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWADDR_LEN_MAX);
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
 
 #define _NMLOG_DOMAIN           LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME      "platform"
@@ -64,21 +71,15 @@ G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWA
         } \
     } G_STMT_END
 
+#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
+
+/*****************************************************************************/
+
 #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 };
+static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
 
 enum {
 	PROP_0,
@@ -92,10 +93,22 @@ typedef struct {
 
 /******************************************************************/
 
+guint
+_nm_platform_signal_id_get (NMPlatformSignalIdType signal_type)
+{
+	nm_assert (   signal_type > 0
+	           && signal_type != NM_PLATFORM_SIGNAL_ID_NONE
+	           && signal_type < _NM_PLATFORM_SIGNAL_ID_LAST);
+
+	return signals[signal_type];
+}
+
+/******************************************************************/
+
 /* Singleton NMPlatform subclass instance and cached class object */
 NM_DEFINE_SINGLETON_INSTANCE (NMPlatform);
 
-NM_DEFINE_SINGLETON_WEAK_REF (NMPlatform);
+NM_DEFINE_SINGLETON_REGISTER (NMPlatform);
 
 /* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS()
  * is only a plain read on the self instance, which the compiler
@@ -137,9 +150,9 @@ nm_platform_setup (NMPlatform *instance)
 
 	singleton_instance = instance;
 
-	nm_singleton_instance_weak_ref_register ();
+	nm_singleton_instance_register ();
 
-	nm_log_dbg (LOGD_CORE, "setup NMPlatform singleton (%p, %s)", instance,  G_OBJECT_TYPE_NAME (instance));
+	nm_log_dbg (LOGD_CORE, "setup %s singleton (%p, %s)", "NMPlatform", singleton_instance, G_OBJECT_TYPE_NAME (instance));
 }
 
 /**
@@ -204,22 +217,6 @@ nm_platform_error_to_string (NMPlatformError error)
 
 /******************************************************************/
 
-#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 (NMPlatform *self)
 {
@@ -284,7 +281,6 @@ nm_platform_sysctl_set (NMPlatform *self, const char *path, const char *value)
 
 	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 (self, path, value);
 }
@@ -336,7 +332,6 @@ nm_platform_sysctl_get (NMPlatform *self, const char *path)
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (path, NULL);
-	g_return_val_if_fail (klass->sysctl_get, NULL);
 
 	return klass->sysctl_get (self, path);
 }
@@ -416,8 +411,6 @@ nm_platform_link_get_all (NMPlatform *self)
 
 	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (klass->link_get_all, NULL);
-
 	links = klass->link_get_all (self);
 
 	if (!links || links->len == 0)
@@ -427,7 +420,7 @@ nm_platform_link_get_all (NMPlatform *self)
 	for (i = 0; i < links->len; i++) {
 		item = &g_array_index (links, NMPlatformLink, i);
 
-		_LOGt ("link-get: %3d: %s", i, nm_platform_link_to_string (item));
+		_LOGt ("link-get: %3d: %s", i, nm_platform_link_to_string (item, NULL, 0));
 
 		nm_assert (item->ifindex > 0 && !g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));
 
@@ -450,8 +443,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			if (item->parent != NM_PLATFORM_LINK_OTHER_NETNS) {
 				g_warn_if_fail (item->parent > 0);
 				g_warn_if_fail (item->parent != item->ifindex);
-				g_warn_if_fail (   !nm_platform_check_support_libnl_link_netnsid ()
-				                || g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)));
+				g_warn_if_fail (g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)));
 			}
 		}
 	}
@@ -483,7 +475,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			if (item->parent > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)))
 				continue;
 
-			_LOGt ("link-get: add %3d -> %3d: %s", i, j, nm_platform_link_to_string (item));
+			_LOGt ("link-get: add %3d -> %3d: %s", i, j, nm_platform_link_to_string (item, NULL, 0));
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
@@ -496,7 +488,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			 * This can happen for veth pairs where each peer is parent of the other end. */
 			item = &g_array_index (links, NMPlatformLink, first_idx);
 
-			_LOGt ("link-get: add (loop) %3d -> %3d: %s", first_idx, j, nm_platform_link_to_string (item));
+			_LOGt ("link-get: add (loop) %3d -> %3d: %s", first_idx, j, nm_platform_link_to_string (item, NULL, 0));
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
@@ -575,7 +567,7 @@ nm_platform_link_get_by_address (NMPlatform *self,
 }
 
 static NMPlatformError
-_link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, NMPlatformLink *out_link)
+_link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, const NMPlatformLink **out_link)
 {
 	const NMPlatformLink *pllink;
 
@@ -590,11 +582,13 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, N
 		       wrong_type ? ", expected " : "",
 		       wrong_type ? nm_link_type_to_string (type) : "");
 		if (out_link)
-			*out_link = *pllink;
+			*out_link = pllink;
 		if (wrong_type)
 			return NM_PLATFORM_ERROR_WRONG_TYPE;
 		return NM_PLATFORM_ERROR_EXISTS;
 	}
+	if (out_link)
+		*out_link = NULL;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
@@ -623,14 +617,13 @@ nm_platform_link_add (NMPlatform *self,
                       NMLinkType type,
                       const void *address,
                       size_t address_len,
-                      NMPlatformLink *out_link)
+                      const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (klass->link_add, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail ( (address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, type, out_link);
@@ -644,7 +637,7 @@ nm_platform_link_add (NMPlatform *self,
 }
 
 /**
- * nm_platform_dummy_add:
+ * nm_platform_link_dummy_add:
  * @self: platform instance
  * @name: New interface name
  * @out_link: on success, the link object
@@ -652,7 +645,9 @@ nm_platform_link_add (NMPlatform *self,
  * Create a software ethernet-like interface
  */
 NMPlatformError
-nm_platform_dummy_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
+nm_platform_link_dummy_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, 0, out_link);
 }
@@ -746,8 +741,6 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 {
 	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (klass->link_get_type_name, NULL);
-
 	return klass->link_get_type_name (self, ifindex);
 }
 
@@ -890,22 +883,19 @@ nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_get_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId *iid)
 {
+	const NMPlatformLink *pllink;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (iid, FALSE);
 
-#if HAVE_LIBNL_INET6_TOKEN
-	{
-		const NMPlatformLink *pllink;
 
-		pllink = nm_platform_link_get (self, ifindex);
-		if (pllink && pllink->inet6_token.is_valid) {
-			*iid = pllink->inet6_token.iid;
-			return TRUE;
-		}
+	pllink = nm_platform_link_get (self, ifindex);
+	if (pllink && pllink->inet6_token.is_valid) {
+		*iid = pllink->inet6_token.iid;
+		return TRUE;
 	}
-#endif
 	return FALSE;
 }
 
@@ -947,23 +937,18 @@ nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
 {
+	const NMPlatformLink *pllink;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	{
-		const NMPlatformLink *pllink;
-
-		pllink = nm_platform_link_get (self, ifindex);
-		if (pllink && pllink->inet6_addr_gen_mode_inv)
-			return _nm_platform_uint8_inv (pllink->inet6_addr_gen_mode_inv) == IN6_ADDR_GEN_MODE_NONE;
-	}
-#endif
+	pllink = nm_platform_link_get (self, ifindex);
+	if (pllink && pllink->inet6_addr_gen_mode_inv)
+		return _nm_platform_uint8_inv (pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE;
 	return FALSE;
 }
 
-
 /**
  * nm_platform_link_set_user_ip6vll_enabled:
  * @self: platform instance
@@ -981,7 +966,6 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->check_support_user_ipv6ll, FALSE);
 
 	if (klass->link_set_user_ipv6ll_enabled)
 		return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
@@ -1004,7 +988,6 @@ nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer addre
 	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);
 
 	_LOGD ("link: setting '%s' (%d) hardware address", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_address (self, ifindex, address, length);
@@ -1083,7 +1066,6 @@ nm_platform_link_supports_carrier_detect (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	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 (self, ifindex);
 }
@@ -1094,7 +1076,6 @@ nm_platform_link_supports_vlans (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_supports_vlans, FALSE);
 
 	return klass->link_supports_vlans (self, ifindex);
 }
@@ -1113,7 +1094,6 @@ nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmwar
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (klass->link_set_up, FALSE);
 
 	_LOGD ("link: setting up '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_up (self, ifindex, out_no_firmware);
@@ -1132,7 +1112,6 @@ nm_platform_link_set_down (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (klass->link_set_down, FALSE);
 
 	_LOGD ("link: setting down '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_down (self, ifindex);
@@ -1151,7 +1130,6 @@ nm_platform_link_set_arp (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_set_arp, FALSE);
 
 	_LOGD ("link: setting arp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_arp (self, ifindex);
@@ -1170,7 +1148,6 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_set_noarp, FALSE);
 
 	_LOGD ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
 	return klass->link_set_noarp (self, ifindex);
@@ -1191,7 +1168,6 @@ nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 
 	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);
 
 	_LOGD ("link: setting '%s' (%d) mtu %"G_GUINT32_FORMAT, nm_platform_link_get_name (self, ifindex), ifindex, mtu);
 	return klass->link_set_mtu (self, ifindex, mtu);
@@ -1306,7 +1282,6 @@ nm_platform_link_get_driver_info (NMPlatform *self,
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (klass->link_get_driver_info, FALSE);
 
 	return klass->link_get_driver_info (self,
 	                                    ifindex,
@@ -1330,7 +1305,6 @@ nm_platform_link_enslave (NMPlatform *self, int master, int slave)
 
 	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);
 
 	_LOGD ("link: enslaving '%s' (%d) to master '%s' (%d)",
 	       nm_platform_link_get_name (self, slave), slave,
@@ -1353,7 +1327,6 @@ nm_platform_link_release (NMPlatform *self, int master, int slave)
 
 	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 (self, slave) != master)
 		return FALSE;
@@ -1384,8 +1357,109 @@ nm_platform_link_get_master (NMPlatform *self, int slave)
 	return pllink ? pllink->master : 0;
 }
 
+/*****************************************************************************/
+
 /**
- * nm_platform_bridge_add:
+ * nm_platform_link_get_lnk:
+ * @self: the platform instance
+ * @ifindex: the link ifindex to lookup
+ * @link_type: filter by link-type.
+ * @out_link: (allow-none): returns the platform link instance
+ *
+ * If the function returns %NULL, that could mean that no such ifindex
+ * exists, of that the link has no lnk data. You can find that out
+ * by checking @out_link. @out_link will always be set if a link
+ * with @ifindex exists.
+ *
+ * If @link_type is %NM_LINK_TYPE_NONE, the function returns the lnk
+ * object if it is present. If you set link-type, you can be sure
+ * that only a link type of the matching type is returned (or %NULL).
+ *
+ * Returns: the internal link lnk object. The returned object
+ * is owned by the platform cache and must not be modified. Note
+ * however, that the object is guaranteed to be immutable, so
+ * you can savely take a reference and keep it for yourself
+ * (but don't modify it).
+ */
+const NMPObject *
+nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	NM_SET_OUT (out_link, NULL);
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	return klass->link_get_lnk (self, ifindex, link_type, out_link);
+}
+
+static gconstpointer
+_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
+{
+	const NMPObject *lnk;
+
+	lnk = nm_platform_link_get_lnk (self, ifindex, link_type, out_link);
+	return lnk ? &lnk->object : NULL;
+}
+
+const NMPlatformLnkGre *
+nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_GRE, out_link);
+}
+
+const NMPlatformLnkInfiniband *
+nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_INFINIBAND, out_link);
+}
+
+const NMPlatformLnkIp6Tnl *
+nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6TNL, out_link);
+}
+
+const NMPlatformLnkIpIp *
+nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IPIP, out_link);
+}
+
+const NMPlatformLnkMacvlan *
+nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVLAN, out_link);
+}
+
+const NMPlatformLnkMacvtap *
+nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVTAP, out_link);
+}
+
+const NMPlatformLnkSit *
+nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_SIT, out_link);
+}
+
+const NMPlatformLnkVlan *
+nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VLAN, out_link);
+}
+
+const NMPlatformLnkVxlan *
+nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VXLAN, out_link);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_platform_link_bridge_add:
  * @self: platform instance
  * @name: New interface name
  * @address: (allow-none): set the mac address of the new bridge
@@ -1395,17 +1469,17 @@ nm_platform_link_get_master (NMPlatform *self, int slave)
  * Create a software bridge.
  */
 NMPlatformError
-nm_platform_bridge_add (NMPlatform *self,
-                        const char *name,
-                        const void *address,
-                        size_t address_len,
-                        NMPlatformLink *out_link)
+nm_platform_link_bridge_add (NMPlatform *self,
+                             const char *name,
+                             const void *address,
+                             size_t address_len,
+                             const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, address, address_len, out_link);
 }
 
 /**
- * nm_platform_bond_add:
+ * nm_platform_link_bond_add:
  * @self: platform instance
  * @name: New interface name
  * @out_link: on success, the link object
@@ -1413,13 +1487,15 @@ nm_platform_bridge_add (NMPlatform *self,
  * Create a software bonding device.
  */
 NMPlatformError
-nm_platform_bond_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
+nm_platform_link_bond_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, 0, out_link);
 }
 
 /**
- * nm_platform_team_add:
+ * nm_platform_link_team_add:
  * @self: platform instance
  * @name: New interface name
  * @out_link: on success, the link object
@@ -1427,13 +1503,15 @@ nm_platform_bond_add (NMPlatform *self, const char *name, NMPlatformLink *out_li
  * Create a software teaming device.
  */
 NMPlatformError
-nm_platform_team_add (NMPlatform *self, const char *name, NMPlatformLink *out_link)
+nm_platform_link_team_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
 {
 	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, 0, out_link);
 }
 
 /**
- * nm_platform_vlan_add:
+ * nm_platform_link_vlan_add:
  * @self: platform instance
  * @name: New interface name
  * @vlanid: VLAN identifier
@@ -1443,12 +1521,12 @@ nm_platform_team_add (NMPlatform *self, const char *name, NMPlatformLink *out_li
  * Create a software VLAN device.
  */
 NMPlatformError
-nm_platform_vlan_add (NMPlatform *self,
-                      const char *name,
-                      int parent,
-                      int vlanid,
-                      guint32 vlanflags,
-                      NMPlatformLink *out_link)
+nm_platform_link_vlan_add (NMPlatform *self,
+                           const char *name,
+                           int parent,
+                           int vlanid,
+                           guint32 vlanflags,
+                           const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
 
@@ -1457,7 +1535,6 @@ nm_platform_vlan_add (NMPlatform *self,
 	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (vlanid >= 0, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (klass->vlan_add, NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
@@ -1470,98 +1547,321 @@ nm_platform_vlan_add (NMPlatform *self,
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
+/**
+ * nm_platform_link_vxlan_add:
+ * @self: platform instance
+ * @name: New interface name
+ * @props: properties of the new link
+ * @out_link: on success, the link object
+ *
+ * Create a VXLAN device.
+ */
+NMPlatformError
+nm_platform_link_vxlan_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLnkVxlan *props,
+                            const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("link: adding vxlan '%s' parent %d id %d",
+	       name, props->parent_ifindex, props->id);
+	if (!klass->link_vxlan_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
+ * nm_platform_link_tun_add:
+ * @self: platform instance
+ * @name: new interface name
+ * @tap: whether the interface is a TAP
+ * @owner: interface owner or -1
+ * @group: interface group or -1
+ * @pi: whether to clear the IFF_NO_PI flag
+ * @vnet_hdr: whether to set the IFF_VNET_HDR flag
+ * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
+ * @out_link: on success, the link object
+ *
+ * Create a TUN or TAP interface.
+ */
+NMPlatformError
+nm_platform_link_tun_add (NMPlatform *self,
+                          const char *name,
+                          gboolean tap,
+                          gint64 owner,
+                          gint64 group,
+                          gboolean pi,
+                          gboolean vnet_hdr,
+                          gboolean multi_queue,
+                          const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("link: adding %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
+	       tap ? "tap" : "tun", name, owner, group);
+	if (!klass->tun_add (self, name, tap, owner, group, pi, vnet_hdr, multi_queue, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/*****************************************************************************/
+
+static char *
+link_option_path (NMPlatform *self, int master, const char *category, const char *option)
+{
+	const char *name = nm_platform_link_get_name (self, master);
+
+	if (!name || !category || !option)
+		return NULL;
+
+	return g_strdup_printf ("/sys/class/net/%s/%s/%s",
+	                        ASSERT_VALID_PATH_COMPONENT (name),
+	                        ASSERT_VALID_PATH_COMPONENT (category),
+	                        ASSERT_VALID_PATH_COMPONENT (option));
+}
+
+static gboolean
+link_set_option (NMPlatform *self, int master, const char *category, const char *option, const char *value)
+{
+	gs_free char *path = link_option_path (self, master, category, option);
+
+	return path && nm_platform_sysctl_set (self, path, value);
+}
+
+static char *
+link_get_option (NMPlatform *self, int master, const char *category, const char *option)
+{
+	gs_free char *path = link_option_path (self, master, category, option);
+
+	return path ? nm_platform_sysctl_get (self, path) : NULL;
+}
+
+static const char *
+master_category (NMPlatform *self, int master)
+{
+	switch (nm_platform_link_get_type (self, master)) {
+	case NM_LINK_TYPE_BRIDGE:
+		return "bridge";
+	case NM_LINK_TYPE_BOND:
+		return "bonding";
+	default:
+		return NULL;
+	}
+}
+
+static const char *
+slave_category (NMPlatform *self, int slave)
+{
+	int master = nm_platform_link_get_master (self, slave);
+
+	if (master <= 0)
+		return NULL;
+
+	switch (nm_platform_link_get_type (self, master)) {
+	case NM_LINK_TYPE_BRIDGE:
+		return "brport";
+	default:
+		return NULL;
+	}
+}
+
 gboolean
-nm_platform_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
+nm_platform_sysctl_master_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	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 (self, ifindex, option, value);
+	return link_set_option (self, ifindex, master_category (self, ifindex), option, value);
 }
 
 char *
-nm_platform_master_get_option (NMPlatform *self, int ifindex, const char *option)
+nm_platform_sysctl_master_get_option (NMPlatform *self, int ifindex, const char *option)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	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 (self, ifindex, option);
+	return link_get_option (self, ifindex, master_category (self, ifindex), option);
 }
 
 gboolean
-nm_platform_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
+nm_platform_sysctl_slave_set_option (NMPlatform *self, int ifindex, const char *option, const char *value)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	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 (self, ifindex, option, value);
+	return link_set_option (self, ifindex, slave_category (self, ifindex), option, value);
 }
 
 char *
-nm_platform_slave_get_option (NMPlatform *self, int ifindex, const char *option)
+nm_platform_sysctl_slave_get_option (NMPlatform *self, int ifindex, const char *option)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	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 (self, ifindex, option);
+	return link_get_option (self, ifindex, slave_category (self, ifindex), option);
 }
 
+/******************************************************************************/
+
 gboolean
-nm_platform_vlan_get_info (NMPlatform *self, int ifindex, int *parent, int *vlanid)
+nm_platform_link_vlan_change (NMPlatform *self,
+                              int ifindex,
+                              NMVlanFlags flags_mask,
+                              NMVlanFlags flags_set,
+                              gboolean ingress_reset_all,
+                              const NMVlanQosMapping *ingress_map,
+                              gsize n_ingress_map,
+                              gboolean egress_reset_all,
+                              const NMVlanQosMapping *egress_map,
+                              gsize n_egress_map)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->vlan_get_info, FALSE);
+	nm_assert (klass->link_vlan_change);
 
-	if (parent)
-		*parent = 0;
-	if (vlanid)
-		*vlanid = 0;
+	g_return_val_if_fail (!n_ingress_map || ingress_map, FALSE);
+	g_return_val_if_fail (!n_egress_map || egress_map, FALSE);
 
-	if (nm_platform_link_get_type (self, ifindex) != NM_LINK_TYPE_VLAN)
-		return FALSE;
+	flags_set &= flags_mask;
+
+	if (_LOGD_ENABLED ()) {
+		char buf[512];
+		char *b = buf;
+		gsize len, i;
+
+		b[0] = '\0';
+		len = sizeof (buf);
+
+		if (flags_mask)
+			nm_utils_strbuf_append (&b, &len, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask);
+
+		if (ingress_reset_all || n_ingress_map) {
+			nm_utils_strbuf_append_str (&b, &len, " ingress-qos-map");
+			nm_platform_vlan_qos_mapping_to_string ("", ingress_map, n_ingress_map, b, len);
+			i = strlen (b);
+			b += i;
+			len -= i;
+			if (ingress_reset_all)
+				nm_utils_strbuf_append_str (&b, &len, " (reset-all)");
+		}
+
+		if (egress_reset_all || n_egress_map) {
+			nm_utils_strbuf_append_str (&b, &len, " egress-qos-map");
+			nm_platform_vlan_qos_mapping_to_string ("", egress_map, n_egress_map, b, len);
+			i = strlen (b);
+			b += i;
+			len -= i;
+			if (egress_reset_all)
+				nm_utils_strbuf_append_str (&b, &len, " (reset-all)");
+		}
 
-	return klass->vlan_get_info (self, ifindex, parent, vlanid);
+		_LOGD ("link: change vlan %d:%s", ifindex, buf);
+	}
+	return klass->link_vlan_change (self,
+	                                ifindex,
+	                                flags_mask,
+	                                flags_set,
+	                                ingress_reset_all,
+	                                ingress_map,
+	                                n_ingress_map,
+	                                egress_reset_all,
+	                                egress_map,
+	                                n_egress_map);
 }
 
 gboolean
-nm_platform_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to)
+nm_platform_link_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to)
 {
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (klass->vlan_set_ingress_map, FALSE);
+	NMVlanQosMapping map = {
+		.from = from,
+		.to = to,
+	};
 
-	_LOGD ("link: setting vlan ingress map for %d from %d to %d", ifindex, from, to);
-	return klass->vlan_set_ingress_map (self, ifindex, from, to);
+	return nm_platform_link_vlan_change (self, ifindex, 0, 0, FALSE, &map, 1, FALSE, NULL, 0);
 }
 
 gboolean
-nm_platform_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to)
+nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMVlanQosMapping map = {
+		.from = from,
+		.to = to,
+	};
 
-	g_return_val_if_fail (klass->vlan_set_egress_map, FALSE);
+	return nm_platform_link_vlan_change (self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1);
+}
 
-	_LOGD ("link: setting vlan egress map for %d from %d to %d", ifindex, from, to);
-	return klass->vlan_set_egress_map (self, ifindex, from, to);
+/**
+ * nm_platform_link_gre_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a software GRE device.
+ */
+NMPlatformError
+nm_platform_link_gre_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkGre *props,
+                          const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+	char buffer[INET_ADDRSTRLEN];
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_GRE, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "gre",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	if (!klass->link_gre_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 NMPlatformError
-nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, NMPlatformLink *out_link)
+nm_platform_link_infiniband_add (NMPlatform *self,
+                                 int parent,
+                                 int p_key,
+                                 const NMPlatformLink **out_link)
 {
 	gs_free char *parent_name = NULL;
 	gs_free char *name = NULL;
@@ -1571,7 +1871,6 @@ nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, N
 
 	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (p_key >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (klass->infiniband_partition_add, NM_PLATFORM_ERROR_BUG);
 
 	parent_name = g_strdup (nm_platform_link_get_name (self, parent));
 	if (   !parent_name
@@ -1591,73 +1890,330 @@ nm_platform_infiniband_partition_add (NMPlatform *self, int parent, int p_key, N
 }
 
 gboolean
-nm_platform_infiniband_get_info (NMPlatform *self,
-                                 int ifindex,
-                                 int *parent,
-                                 int *p_key,
-                                 const char **mode)
-{
+nm_platform_link_infiniband_get_properties (NMPlatform *self,
+                                            int ifindex,
+                                            int *out_parent,
+                                            int *out_p_key,
+                                            const char **out_mode)
+{
+	const NMPlatformLnkInfiniband *plnk;
+	const NMPlatformLink *plink;
+	const char *iface;
+	char *path, *contents;
+	const char *mode;
+	int p_key = 0;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (klass->infiniband_get_info, FALSE);
 
-	return klass->infiniband_get_info (self, ifindex, parent, p_key, mode);
+	plnk = nm_platform_link_get_lnk_infiniband (self, ifindex, &plink);
+
+	if (   !plink
+	    || plink->type != NM_LINK_TYPE_INFINIBAND)
+		return FALSE;
+
+	if (plnk) {
+		NM_SET_OUT (out_parent, plink->parent);
+		NM_SET_OUT (out_p_key, plnk->p_key);
+		NM_SET_OUT (out_mode, plnk->mode);
+		return TRUE;
+	}
+
+	/* Could not get the link information via netlink. To support older kernels,
+	 * fallback to reading sysfs. */
+
+	iface = ASSERT_VALID_PATH_COMPONENT (plink->name);
+
+	/* Fall back to reading sysfs */
+	path = g_strdup_printf ("/sys/class/net/%s/mode", iface);
+	contents = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (!contents)
+		return FALSE;
+
+	if (strstr (contents, "datagram"))
+		mode = "datagram";
+	else if (strstr (contents, "connected"))
+		mode = "connected";
+	else
+		mode = NULL;
+	g_free (contents);
+
+	path = g_strdup_printf ("/sys/class/net/%s/pkey", iface);
+	contents = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (!contents)
+		return FALSE;
+	p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
+	g_free (contents);
+	if (p_key < 0)
+		return FALSE;
+
+	NM_SET_OUT (out_parent, plink->parent);
+	NM_SET_OUT (out_p_key, p_key);
+	NM_SET_OUT (out_mode, mode);
+	return TRUE;
 }
 
-gboolean
-nm_platform_veth_get_properties (NMPlatform *self, int ifindex, NMPlatformVethProperties *props)
+/**
+ * nm_platform_ip6tnl_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create an IPv6 tunnel.
+ */
+NMPlatformError
+nm_platform_link_ip6tnl_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMPlatformError plerr;
+	char buffer[INET6_ADDRSTRLEN];
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
 
-	return klass->veth_get_properties (self, ifindex, props);
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ip6tnl",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet6_ntop (&props->local, NULL),
+	       nm_utils_inet6_ntop (&props->remote, buffer));
+
+	if (!klass->link_ip6tnl_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
-gboolean
-nm_platform_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
+/**
+ * nm_platform_ipip_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create an IPIP tunnel.
+ */
+NMPlatformError
+nm_platform_link_ipip_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLnkIpIp *props,
+                           const NMPlatformLink **out_link)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMPlatformError plerr;
+	char buffer[INET_ADDRSTRLEN];
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	return klass->tun_get_properties (self, ifindex, props);
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ipip",
+	       name,
+		   props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	if (!klass->link_ipip_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
+ * nm_platform_macvlan_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a MACVLAN or MACVTAP device.
+ */
+NMPlatformError
+nm_platform_link_macvlan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLnkMacvlan *props,
+                              const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+	NMLinkType type;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	type = props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
+
+	plerr = _link_add_check_existing (self, name, type, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("adding %s '%s' parent %u mode %u",
+	       props->tap ? "macvtap" : "macvlan",
+	       name,
+	       parent,
+	       props->mode);
+
+	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
+ * nm_platform_sit_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a software SIT device.
+ */
+NMPlatformError
+nm_platform_link_sit_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkSit *props,
+                          const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+	char buffer[INET_ADDRSTRLEN];
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "sit",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	if (!klass->link_sit_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 gboolean
-nm_platform_macvlan_get_properties (NMPlatform *self, int ifindex, NMPlatformMacvlanProperties *props)
+nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_peer_ifindex)
 {
+	const NMPlatformLink *plink;
+	int peer_ifindex;
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	plink = nm_platform_link_get (self, ifindex);
+
+	if (!plink)
+		return FALSE;
+	if (plink->type != NM_LINK_TYPE_VETH)
+		return FALSE;
+
+	if (plink->parent != 0) {
+		NM_SET_OUT (out_peer_ifindex, plink->parent);
+		return TRUE;
+	}
+
+	/* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */
+	if (out_peer_ifindex) {
+		peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->name);
+		if (peer_ifindex <= 0)
+			return FALSE;
 
-	return klass->macvlan_get_properties (self, ifindex, props);
+		*out_peer_ifindex = peer_ifindex;
+	}
+	return TRUE;
 }
 
 gboolean
-nm_platform_vxlan_get_properties (NMPlatform *self, int ifindex, NMPlatformVxlanProperties *props)
+nm_platform_link_tun_get_properties_ifname (NMPlatform *self, const char *ifname, NMPlatformTunProperties *props)
 {
+	char *path, *val;
+	gboolean success = TRUE;
+
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
+	g_return_val_if_fail (props, FALSE);
+
+	memset (props, 0, sizeof (*props));
+	props->owner = -1;
+	props->group = -1;
 
-	return klass->vxlan_get_properties (self, ifindex, props);
+	if (!ifname || !nm_utils_iface_valid_name (ifname))
+		return FALSE;
+	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+
+	path = g_strdup_printf ("/sys/class/net/%s/owner", ifname);
+	val = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (val) {
+		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
+		if (errno)
+			success = FALSE;
+		g_free (val);
+	} else
+		success = FALSE;
+
+	path = g_strdup_printf ("/sys/class/net/%s/group", ifname);
+	val = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (val) {
+		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
+		if (errno)
+			success = FALSE;
+		g_free (val);
+	} else
+		success = FALSE;
+
+	path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname);
+	val = nm_platform_sysctl_get (self, path);
+	g_free (path);
+	if (val) {
+		gint64 flags;
+
+		flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
+		if (!errno) {
+			props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
+			props->no_pi = !!(flags & IFF_NO_PI);
+			props->vnet_hdr = !!(flags & IFF_VNET_HDR);
+			props->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
+		} else
+			success = FALSE;
+		g_free (val);
+	} else
+		success = FALSE;
+
+	return success;
 }
 
 gboolean
-nm_platform_gre_get_properties (NMPlatform *self, int ifindex, NMPlatformGreProperties *props)
+nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props != NULL, FALSE);
 
-	return klass->gre_get_properties (self, ifindex, props);
+	return nm_platform_link_tun_get_properties_ifname (self, nm_platform_link_get_name (self, ifindex), props);
 }
 
 gboolean
@@ -1680,16 +2236,6 @@ nm_platform_wifi_get_bssid (NMPlatform *self, int ifindex, guint8 *bssid)
 	return klass->wifi_get_bssid (self, ifindex, bssid);
 }
 
-GByteArray *
-nm_platform_wifi_get_ssid (NMPlatform *self, int ifindex)
-{
-	_CHECK_SELF (self, klass, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
-	return klass->wifi_get_ssid (self, ifindex);
-}
-
 guint32
 nm_platform_wifi_get_frequency (NMPlatform *self, int ifindex)
 {
@@ -1740,6 +2286,22 @@ nm_platform_wifi_set_mode (NMPlatform *self, int ifindex, NM80211Mode mode)
 	klass->wifi_set_mode (self, ifindex, mode);
 }
 
+static void
+wifi_set_powersave (NMPlatform *p, int ifindex, guint32 powersave)
+{
+	/* empty */
+}
+
+void
+nm_platform_wifi_set_powersave (NMPlatform *self, int ifindex, guint32 powersave)
+{
+	_CHECK_SELF_VOID (self, klass);
+
+	g_return_if_fail (ifindex > 0);
+
+	klass->wifi_set_powersave (self, ifindex, powersave);
+}
+
 guint32
 nm_platform_wifi_find_frequency (NMPlatform *self, int ifindex, const guint32 *freqs)
 {
@@ -1818,13 +2380,29 @@ _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
 
 /******************************************************************/
 
+void
+nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, int plen)
+{
+	addr->address = address;
+	addr->peer_address = address;
+	addr->plen = plen;
+}
+
+const struct in6_addr *
+nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr)
+{
+	if (   IN6_IS_ADDR_UNSPECIFIED (&addr->peer_address)
+	    || IN6_ARE_ADDR_EQUAL (&addr->peer_address, &addr->address))
+		return &addr->address;
+	return &addr->peer_address;
+}
+
 GArray *
 nm_platform_ip4_address_get_all (NMPlatform *self, int ifindex)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	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 (self, ifindex);
 }
@@ -1835,17 +2413,16 @@ nm_platform_ip6_address_get_all (NMPlatform *self, int ifindex)
 	_CHECK_SELF (self, klass, NULL);
 
 	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 (self, ifindex);
 }
 
 gboolean
 nm_platform_ip4_address_add (NMPlatform *self,
-	                         int ifindex,
+                             int ifindex,
                              in_addr_t address,
-                             in_addr_t peer_address,
                              int plen,
+                             in_addr_t peer_address,
                              guint32 lifetime,
                              guint32 preferred,
                              const char *label)
@@ -1856,7 +2433,6 @@ nm_platform_ip4_address_add (NMPlatform *self,
 	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 (_LOGD_ENABLED ()) {
@@ -1872,17 +2448,17 @@ nm_platform_ip4_address_add (NMPlatform *self,
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
-		_LOGD ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr));
+		_LOGD ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip4_address_add (self, ifindex, address, peer_address, plen, lifetime, preferred, label);
+	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, label);
 }
 
 gboolean
 nm_platform_ip6_address_add (NMPlatform *self,
                              int ifindex,
                              struct in6_addr address,
-                             struct in6_addr peer_address,
                              int plen,
+                             struct in6_addr peer_address,
                              guint32 lifetime,
                              guint32 preferred,
                              guint flags)
@@ -1893,7 +2469,6 @@ nm_platform_ip6_address_add (NMPlatform *self,
 	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 (_LOGD_ENABLED ()) {
 		NMPlatformIP6Address addr = { 0 };
@@ -1907,28 +2482,27 @@ nm_platform_ip6_address_add (NMPlatform *self,
 		addr.preferred = preferred;
 		addr.flags = flags;
 
-		_LOGD ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr));
+		_LOGD ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip6_address_add (self, ifindex, address, peer_address, plen, lifetime, preferred, flags);
+	return klass->ip6_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
 }
 
 gboolean
 nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, int plen, in_addr_t peer_address)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
-	char str_peer[NM_UTILS_INET_ADDRSTRLEN];
+	char str_peer2[NM_UTILS_INET_ADDRSTRLEN];
+	char str_peer[100];
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	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);
 
-	_LOGD ("address: deleting IPv4 address %s/%d, %s%s%sifindex %d%s",
+	_LOGD ("address: deleting IPv4 address %s/%d, %sifindex %d%s",
 	       nm_utils_inet4_ntop (address, NULL), plen,
-	       peer_address ? "peer " : "",
-	       peer_address ? nm_utils_inet4_ntop (peer_address, str_peer) : "",
-	       peer_address ? ", " : "",
+	       peer_address != address
+	           ? nm_sprintf_buf (str_peer, "peer %s, ", nm_utils_inet4_ntop (peer_address, str_peer2)) : "",
 	       ifindex,
 	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
@@ -1943,7 +2517,6 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 
 	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);
 
 	_LOGD ("address: deleting IPv6 address %s/%d, ifindex %d%s",
 	       nm_utils_inet6_ntop (&address, NULL), plen, ifindex,
@@ -1952,21 +2525,21 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 }
 
 const NMPlatformIP4Address *
-nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, int plen)
+nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, int plen, guint32 peer_address)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (plen > 0, NULL);
 
-	return klass->ip4_address_get (self, ifindex, address, plen);
+	return klass->ip4_address_get (self, ifindex, address, plen, peer_address);
 }
 
 const NMPlatformIP6Address *
 nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address, int plen)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (plen > 0, FALSE);
+	g_return_val_if_fail (plen > 0, NULL);
 
 	return klass->ip6_address_get (self, ifindex, address, plen);
 }
@@ -1980,7 +2553,9 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
 	for (i = 0; i < len; i++) {
 		NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
 
-		if (candidate->address == address->address && candidate->plen == address->plen) {
+		if (   candidate->address == address->address
+		    && candidate->plen == address->plen
+		    && ((candidate->peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (address->plen)) == 0) {
 			guint32 lifetime, preferred;
 
 			if (nmp_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
@@ -2064,7 +2639,7 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		                             now, ADDRESS_LIFETIME_PADDING, &lifetime, &preferred))
 			continue;
 
-		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->peer_address, known_address->plen, lifetime, preferred, known_address->label))
+		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen, known_address->peer_address, lifetime, preferred, known_address->label))
 			return FALSE;
 
 		if (out_added_addresses) {
@@ -2125,7 +2700,7 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 			continue;
 
 		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
-		                                  known_address->peer_address, known_address->plen,
+		                                  known_address->plen, known_address->peer_address,
 		                                  lifetime, preferred, known_address->flags))
 			return FALSE;
 	}
@@ -2164,17 +2739,46 @@ nm_platform_ip6_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRoute
 	return klass->ip6_route_get_all (self, ifindex, flags);
 }
 
+/**
+ * nm_platform_ip4_route_add:
+ * @self:
+ * @ifindex:
+ * @source:
+ * network:
+ * plen:
+ * gateway:
+ * pref_src:
+ * metric:
+ * mss:
+ *
+ * For kernel, a gateway can be either explicitly set or left
+ * at zero (0.0.0.0). In addition, there is the scope of the IPv4
+ * route.
+ * When adding a route with
+ *   $ ip route add default dev $IFNAME
+ * the resulting route will have gateway 0.0.0.0 and scope "link".
+ * Contrary to
+ *   $ ip route add default via 0.0.0.0 dev $IFNAME
+ * which adds the route with scope "global".
+ *
+ * NetworkManager's Platform can currently only add on-link-routes with scope
+ * "link" (and gateway 0.0.0.0) or gateway-routes with scope "global" (and
+ * gateway not 0.0.0.0).
+ *
+ * It does not support adding globally scoped routes via 0.0.0.0.
+ *
+ * Returns: %TRUE in case of success.
+ */
 gboolean
 nm_platform_ip4_route_add (NMPlatform *self,
                            int ifindex, NMIPConfigSource source,
                            in_addr_t network, int plen,
-                           in_addr_t gateway, guint32 pref_src,
+                           in_addr_t gateway, in_addr_t pref_src,
                            guint32 metric, guint32 mss)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (0 <= plen && plen <= 32, FALSE);
-	g_return_val_if_fail (klass->ip4_route_add, FALSE);
 
 	if (_LOGD_ENABLED ()) {
 		NMPlatformIP4Route route = { 0 };
@@ -2188,7 +2792,7 @@ nm_platform_ip4_route_add (NMPlatform *self,
 		route.mss = mss;
 		route.pref_src = pref_src;
 
-		_LOGD ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (&route));
+		_LOGD ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (&route, NULL, 0));
 	}
 	return klass->ip4_route_add (self, ifindex, source, network, plen, gateway, pref_src, metric, mss);
 }
@@ -2202,7 +2806,6 @@ nm_platform_ip6_route_add (NMPlatform *self,
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (0 <= plen && plen <= 128, FALSE);
-	g_return_val_if_fail (klass->ip6_route_add, FALSE);
 
 	if (_LOGD_ENABLED ()) {
 		NMPlatformIP6Route route = { 0 };
@@ -2215,7 +2818,7 @@ nm_platform_ip6_route_add (NMPlatform *self,
 		route.metric = metric;
 		route.mss = mss;
 
-		_LOGD ("route: adding or updating IPv6 route: %s", nm_platform_ip6_route_to_string (&route));
+		_LOGD ("route: adding or updating IPv6 route: %s", nm_platform_ip6_route_to_string (&route, NULL, 0));
 	}
 	return klass->ip6_route_add (self, ifindex, source, network, plen, gateway, metric, mss);
 }
@@ -2227,8 +2830,6 @@ nm_platform_ip4_route_delete (NMPlatform *self, int ifindex, in_addr_t network,
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->ip4_route_delete, FALSE);
-
 	_LOGD ("route: deleting IPv4 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
 	       nm_utils_inet4_ntop (network, NULL), plen, metric, ifindex,
 	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
@@ -2242,8 +2843,6 @@ nm_platform_ip6_route_delete (NMPlatform *self, int ifindex, struct in6_addr net
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (klass->ip6_route_delete, FALSE);
-
 	_LOGD ("route: deleting IPv6 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
 	       nm_utils_inet6_ntop (&network, NULL), plen, metric, ifindex,
 	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
@@ -2268,6 +2867,40 @@ nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr networ
 
 /******************************************************************/
 
+const char *
+nm_platform_vlan_qos_mapping_to_string (const char *name,
+                                        const NMVlanQosMapping *map,
+                                        gsize n_map,
+                                        char *buf,
+                                        gsize len)
+{
+	gsize i;
+	char *b;
+
+	nm_utils_to_string_buffer_init (&buf, &len);
+
+	if (!n_map) {
+		nm_utils_strbuf_append_str (&buf, &len, "");
+		return buf;
+	}
+
+	if (!map)
+		g_return_val_if_reached ("");
+
+	b = buf;
+
+	if (name) {
+		nm_utils_strbuf_append_str (&b, &len, name);
+		nm_utils_strbuf_append_str (&b, &len, " {");
+	} else
+		nm_utils_strbuf_append_c (&b, &len, '{');
+
+	for (i = 0; i < n_map; i++)
+		nm_utils_strbuf_append (&b, &len, " %u:%u", map[i].from, map[i].to);
+	nm_utils_strbuf_append_str (&b, &len, " }");
+	return buf;
+}
+
 static const char *
 source_to_string (NMIPConfigSource source)
 {
@@ -2320,21 +2953,29 @@ _lifetime_summary_to_string (gint32 now, guint32 timestamp, guint32 preferred, g
 	return buf;
 }
 
-char _nm_platform_to_string_buffer[256];
-
+/**
+ * nm_platform_link_to_string:
+ * @route: pointer to NMPlatformLink address structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
+ *
+ * A method for converting an link struct into a string representation.
+ *
+ * Returns: a string representation of the link.
+ */
 const char *
-nm_platform_link_to_string (const NMPlatformLink *link)
+nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 {
 	char master[20];
 	char parent[20];
-	char str_vlan[16];
 	GString *str_flags;
 	char str_addrmode[30];
 	gs_free char *str_addr = NULL;
 	gs_free char *str_inet6_token = NULL;
+	const char *str_link_type;
 
-	if (!link)
-		return "(unknown link)";
+	if (!nm_utils_to_string_buffer_init_null (link, &buf, &len))
+		return buf;
 
 	str_flags = g_string_new (NULL);
 	if (NM_FLAGS_HAS (link->flags, IFF_NOARP))
@@ -2349,7 +2990,7 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	if (link->flags) {
 		char str_flags_buf[64];
 
-		rtnl_link_flags2str (link->flags, str_flags_buf, sizeof (str_flags_buf));
+		nm_platform_link_flags2str (link->flags, str_flags_buf, sizeof (str_flags_buf));
 		g_string_append_printf (str_flags, ";%s", str_flags_buf);
 	}
 
@@ -2365,11 +3006,6 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	else
 		parent[0] = 0;
 
-	if (link->vlan_id)
-		g_snprintf (str_vlan, sizeof (str_vlan), " vlan %u", (guint) link->vlan_id);
-	else
-		str_vlan[0] = '\0';
-
 	if (link->inet6_addr_gen_mode_inv) {
 		switch (_nm_platform_uint8_inv (link->inet6_addr_gen_mode_inv)) {
 			case 0:
@@ -2390,14 +3026,15 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	if (link->inet6_token.is_valid)
 		str_inet6_token = nm_utils_hwaddr_ntoa (&link->inet6_token.iid, sizeof (link->inet6_token.iid));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer),
+	str_link_type = nm_link_type_to_string (link->type);
+
+	g_snprintf (buf, len,
 	            "%d: " /* ifindex */
 	            "%s" /* name */
 	            "%s" /* parent */
 	            " <%s>" /* flags */
 	            " mtu %d"
 	            "%s" /* master */
-	            "%s" /* vlan */
 	            " arp %u" /* arptype */
 	            "%s%s" /* link->type */
 	            "%s%s" /* kind */
@@ -2412,12 +3049,11 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	            parent,
 	            str_flags->str,
 	            link->mtu, master,
-	            str_vlan,
 	            link->arptype,
-	            nm_link_type_to_string (link->type) ? " " : "",
-	            str_if_set (nm_link_type_to_string (link->type), "???"),
-	            link->kind ? (g_strcmp0 (nm_link_type_to_string (link->type), link->kind) ? "/" : "*") : "",
-	            link->kind && g_strcmp0 (nm_link_type_to_string (link->type), link->kind) ? link->kind : "",
+	            str_link_type ? " " : "",
+	            str_if_set (str_link_type, "???"),
+	            link->kind ? (g_strcmp0 (str_link_type, link->kind) ? "/" : "*") : "",
+	            link->kind && g_strcmp0 (str_link_type, link->kind) ? link->kind : "",
 	            link->initialized ? " init" : " not-init",
 	            str_addrmode,
 	            str_addr ? " addr " : "",
@@ -2427,23 +3063,313 @@ nm_platform_link_to_string (const NMPlatformLink *link)
 	            link->driver ? " driver " : "",
 	            link->driver ? link->driver : "");
 	g_string_free (str_flags, TRUE);
-	return _nm_platform_to_string_buffer;
+	return buf;
+}
+
+const char *
+nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tos[30];
+	char str_parent_ifindex[30];
+	char str_input_flags[30];
+	char str_output_flags[30];
+	char str_input_key[30];
+	char str_input_key1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_output_key[30];
+	char str_output_key1[NM_UTILS_INET_ADDRSTRLEN];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "gre"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tos */
+	            "%s" /* path_mtu_discovery */
+	            "%s" /* iflags */
+	            "%s" /* oflags */
+	            "%s" /* ikey */
+	            "%s" /* okey */
+	            "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
+	            lnk->path_mtu_discovery ? "" : " nopmtudisc",
+	            lnk->input_flags ? nm_sprintf_buf (str_input_flags, " iflags 0x%x", lnk->input_flags) : "",
+	            lnk->output_flags ? nm_sprintf_buf (str_output_flags, " oflags 0x%x", lnk->output_flags) : "",
+	            NM_FLAGS_HAS (lnk->input_flags, GRE_KEY) || lnk->input_key ? nm_sprintf_buf (str_input_key, " ikey %s", nm_utils_inet4_ntop (lnk->input_key, str_input_key1)) : "",
+	            NM_FLAGS_HAS (lnk->output_flags, GRE_KEY) || lnk->output_key ? nm_sprintf_buf (str_output_key, " okey %s", nm_utils_inet4_ntop (lnk->output_key, str_output_key1)) : "");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_infiniband_to_string (const NMPlatformLnkInfiniband *lnk, char *buf, gsize len)
+{
+	char str_p_key[64];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "infiniband"
+	            "%s" /* p_key */
+	            "%s%s" /* mode */
+	            "",
+	            lnk->p_key ? nm_sprintf_buf (str_p_key, " pkey %d", lnk->p_key) : "",
+	            lnk->mode ? " mode " : "",
+	            lnk->mode ?: "");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tclass[30];
+	char str_flow[30];
+	char str_encap[30];
+	char str_proto[30];
+	char str_parent_ifindex[30];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "ip6tnl"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tclass */
+	            "%s" /* encap limit */
+	            "%s" /* flow label */
+	            "%s" /* proto */
+	            "",
+	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
+	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass),
+	            nm_sprintf_buf (str_encap, " encap-limit %u", lnk->encap_limit),
+	            nm_sprintf_buf (str_flow, " flow-label 0x05%x", lnk->flow_label),
+	            nm_sprintf_buf (str_proto, " proto %u", lnk->proto));
+	return buf;
+}
+
+const char *
+nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tos[30];
+	char str_parent_ifindex[30];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "ipip"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tos */
+	            "%s" /* path_mtu_discovery */
+	            "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
+	            lnk->path_mtu_discovery ? "" : " nopmtudisc");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len)
+{
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "macvlan mode %u %s",
+	            lnk->mode,
+	            lnk->no_promisc ? "not-promisc" : "promisc");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len)
+{
+	char str_local[30];
+	char str_local1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_remote[30];
+	char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
+	char str_ttl[30];
+	char str_tos[30];
+	char str_flags[30];
+	char str_proto[30];
+	char str_parent_ifindex[30];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "sit"
+	            "%s" /* remote */
+	            "%s" /* local */
+	            "%s" /* parent_ifindex */
+	            "%s" /* ttl */
+	            "%s" /* tos */
+	            "%s" /* path_mtu_discovery */
+	            "%s" /* flags */
+	            "%s" /* proto */
+	            "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
+	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
+	            lnk->path_mtu_discovery ? "" : " nopmtudisc",
+	            lnk->flags ? nm_sprintf_buf (str_flags, " flags 0x%x", lnk->flags) : "",
+	            lnk->proto ? nm_sprintf_buf (str_proto, " proto 0x%x", lnk->proto) : "");
+	return buf;
+}
+
+const char *
+nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len)
+{
+	char *b;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	b = buf;
+
+	nm_utils_strbuf_append (&b, &len, "vlan %u", lnk->id);
+	if (lnk->flags)
+		nm_utils_strbuf_append (&b, &len, " flags 0x%x", lnk->flags);
+	return buf;
+}
+
+const char *
+nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len)
+{
+	char str_group[100];
+	char str_group6[100];
+	char str_local[100];
+	char str_local6[100];
+	char str_dev[25];
+	char str_limit[25];
+	char str_src_port[35];
+	char str_dst_port[25];
+	char str_tos[25];
+	char str_ttl[25];
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	if (lnk->group == 0)
+		str_group[0] = '\0';
+	else {
+		g_snprintf (str_group, sizeof (str_group),
+		            " %s %s",
+		            IN_MULTICAST (ntohl (lnk->group)) ? "group" : "remote",
+		            nm_utils_inet4_ntop (lnk->group, NULL));
+	}
+	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->group6))
+		str_group6[0] = '\0';
+	else {
+		g_snprintf (str_group6, sizeof (str_group6),
+		            " %s%s %s",
+		            IN6_IS_ADDR_MULTICAST (&lnk->group6) ? "group" : "remote",
+		            str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
+		            nm_utils_inet6_ntop (&lnk->group6, NULL));
+	}
+
+	if (lnk->local == 0)
+		str_local[0] = '\0';
+	else {
+		g_snprintf (str_local, sizeof (str_local),
+		            " local %s",
+		            nm_utils_inet4_ntop (lnk->local, NULL));
+	}
+	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->local6))
+		str_local6[0] = '\0';
+	else {
+		g_snprintf (str_local6, sizeof (str_local6),
+		            " local%s %s",
+		            str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
+		            nm_utils_inet6_ntop (&lnk->local6, NULL));
+	}
+
+	g_snprintf (buf, len,
+	            "vxlan"
+	            " id %u" /* id */
+	            "%s%s" /* group/group6 */
+	            "%s%s" /* local/local6 */
+	            "%s" /* dev */
+	            "%s" /* src_port_min/src_port_max */
+	            "%s" /* dst_port */
+	            "%s" /* learning */
+	            "%s" /* proxy */
+	            "%s" /* rsc */
+	            "%s" /* l2miss */
+	            "%s" /* l3miss */
+	            "%s" /* tos */
+	            "%s" /* ttl */
+	            " ageing %u" /* ageing */
+	            "%s" /* limit */
+	            "",
+	            (guint) lnk->id,
+	            str_group, str_group6,
+	            str_local, str_local6,
+	            lnk->parent_ifindex ? nm_sprintf_buf (str_dev, " dev %d", lnk->parent_ifindex) : "",
+	            lnk->src_port_min || lnk->src_port_max ? nm_sprintf_buf (str_src_port, " srcport %u %u", lnk->src_port_min, lnk->src_port_max) : "",
+	            lnk->dst_port ? nm_sprintf_buf (str_dst_port, " dstport %u", lnk->dst_port) : "",
+	            !lnk->learning ? " nolearning" : "",
+	            lnk->proxy ? " proxy" : "",
+	            lnk->rsc ? " rsc" : "",
+	            lnk->l2miss ? " l2miss" : "",
+	            lnk->l3miss ? " l3miss" : "",
+	            lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos %#x", lnk->tos),
+	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : "",
+	            lnk->ageing,
+	            lnk->limit ? nm_sprintf_buf (str_limit, " maxaddr %u", lnk->limit) : "");
+	return buf;
 }
 
 /**
  * nm_platform_ip4_address_to_string:
  * @route: pointer to NMPlatformIP4Address address structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * 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.
+ * Returns: a string representation of the address.
  */
 const char *
-nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
+nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len)
 {
 	char s_address[INET_ADDRSTRLEN];
 	char s_peer[INET_ADDRSTRLEN];
@@ -2454,11 +3380,12 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
 	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)");
+	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
+		return buf;
 
 	inet_ntop (AF_INET, &address->address, s_address, sizeof (s_address));
 
-	if (address->peer_address) {
+	if (address->peer_address != address->address) {
 		inet_ntop (AF_INET, &address->peer_address, s_peer, sizeof (s_peer));
 		str_peer = g_strconcat (" ptp ", s_peer, NULL);
 	}
@@ -2480,66 +3407,92 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address)
 	                                      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 (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	g_snprintf (buf, len,
+	            "%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 _nm_platform_to_string_buffer;
+	return buf;
 }
 
-/**
- * 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_UTILS_FLAGS2STR_DEFINE (nm_platform_link_flags2str, unsigned,
+	NM_UTILS_FLAGS2STR (IFF_LOOPBACK, "loopback"),
+	NM_UTILS_FLAGS2STR (IFF_BROADCAST, "broadcast"),
+	NM_UTILS_FLAGS2STR (IFF_POINTOPOINT, "pointopoint"),
+	NM_UTILS_FLAGS2STR (IFF_MULTICAST, "multicast"),
+	NM_UTILS_FLAGS2STR (IFF_NOARP, "noarp"),
+	NM_UTILS_FLAGS2STR (IFF_ALLMULTI, "allmulti"),
+	NM_UTILS_FLAGS2STR (IFF_PROMISC, "promisc"),
+	NM_UTILS_FLAGS2STR (IFF_MASTER, "master"),
+	NM_UTILS_FLAGS2STR (IFF_SLAVE, "slave"),
+	NM_UTILS_FLAGS2STR (IFF_DEBUG, "debug"),
+	NM_UTILS_FLAGS2STR (IFF_DYNAMIC, "dynamic"),
+	NM_UTILS_FLAGS2STR (IFF_AUTOMEDIA, "automedia"),
+	NM_UTILS_FLAGS2STR (IFF_PORTSEL, "portsel"),
+	NM_UTILS_FLAGS2STR (IFF_NOTRAILERS, "notrailers"),
+	NM_UTILS_FLAGS2STR (IFF_UP, "up"),
+	NM_UTILS_FLAGS2STR (IFF_RUNNING, "running"),
+	NM_UTILS_FLAGS2STR (IFF_LOWER_UP, "lowerup"),
+	NM_UTILS_FLAGS2STR (IFF_DORMANT, "dormant"),
+	NM_UTILS_FLAGS2STR (IFF_ECHO, "echo"),
+);
+
+NM_UTILS_ENUM2STR_DEFINE (nm_platform_link_inet6_addrgenmode2str, guint8,
+	NM_UTILS_ENUM2STR (NM_IN6_ADDR_GEN_MODE_NONE, "none"),
+	NM_UTILS_ENUM2STR (NM_IN6_ADDR_GEN_MODE_EUI64, "eui64"),
+	NM_UTILS_ENUM2STR (NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"),
+);
+
+NM_UTILS_FLAGS2STR_DEFINE (nm_platform_addr_flags2str, unsigned,
+	NM_UTILS_FLAGS2STR (IFA_F_SECONDARY, "secondary"),
+	NM_UTILS_FLAGS2STR (IFA_F_NODAD, "nodad"),
+	NM_UTILS_FLAGS2STR (IFA_F_OPTIMISTIC, "optimistic"),
+	NM_UTILS_FLAGS2STR (IFA_F_HOMEADDRESS, "homeaddress"),
+	NM_UTILS_FLAGS2STR (IFA_F_DEPRECATED, "deprecated"),
+	NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"),
+	NM_UTILS_FLAGS2STR (IFA_F_PERMANENT, "permanent"),
+	NM_UTILS_FLAGS2STR (IFA_F_MANAGETEMPADDR, "mngtmpaddr"),
+	NM_UTILS_FLAGS2STR (IFA_F_NOPREFIXROUTE, "noprefixroute"),
+);
+
+NM_UTILS_ENUM2STR_DEFINE (nm_platform_route_scope2str, int,
+	NM_UTILS_ENUM2STR (RT_SCOPE_NOWHERE, "nowhere"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_HOST, "host"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_LINK, "link"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_SITE, "site"),
+	NM_UTILS_ENUM2STR (RT_SCOPE_UNIVERSE, "global"),
+);
 
 /**
  * nm_platform_ip6_address_to_string:
  * @route: pointer to NMPlatformIP6Address address structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * 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.
+ * Returns: a string representation of the address.
  */
 const char *
-nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
+nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len)
 {
+#define S_FLAGS_PREFIX " flags "
 	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)");
+	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
+		return buf;
 
 	inet_ntop (AF_INET6, &address->address, s_address, sizeof (s_address));
 
@@ -2550,9 +3503,11 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
 
 	_to_string_dev (NULL, 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;
+	nm_platform_addr_flags2str (address->flags, &s_flags[STRLEN (S_FLAGS_PREFIX)], sizeof (s_flags) - STRLEN (S_FLAGS_PREFIX));
+	if (s_flags[STRLEN (S_FLAGS_PREFIX)] == '\0')
+		s_flags[0] = '\0';
+	else
+		memcpy (s_flags, S_FLAGS_PREFIX, STRLEN (S_FLAGS_PREFIX));
 
 	str_lft_p = _lifetime_to_string (address->timestamp,
 	                                 address->lifetime ? address->lifetime : NM_PLATFORM_LIFETIME_PERMANENT,
@@ -2564,45 +3519,46 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address)
 	                                      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 (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer), "%s/%d lft %s pref %s%s%s%s%s src %s",
+	g_snprintf (buf, len,
+	            "%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 : "",
+	            s_flags,
 	            source_to_string (address->source));
-	g_free (str_flags);
 	g_free (str_peer);
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
 /**
  * nm_platform_ip4_route_to_string:
  * @route: pointer to NMPlatformIP4Route route structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * 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.
+ * Returns: a string representation of the route.
  */
 const char *
-nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route)
+nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len)
 {
 	char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
 	char s_pref_src[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 	char str_scope[30];
 
-	g_return_val_if_fail (route, "(unknown)");
+	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
+		return buf;
 
 	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 (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer),
+	g_snprintf (buf, len,
 	            "%s/%d"
 	            " via %s"
 	            "%s"
@@ -2619,38 +3575,39 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route)
 	            route->mss,
 	            source_to_string (route->source),
 	            route->scope_inv ? " scope " : "",
-	            route->scope_inv ? (rtnl_scope2str (nm_platform_route_scope_inv (route->scope_inv), str_scope, sizeof (str_scope))) : "",
+	            route->scope_inv ? (nm_platform_route_scope2str (nm_platform_route_scope_inv (route->scope_inv), str_scope, sizeof (str_scope))) : "",
 	            route->pref_src ? " pref-src " : "",
 	            route->pref_src ? inet_ntop (AF_INET, &route->pref_src, s_pref_src, sizeof(s_pref_src)) : "");
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
 /**
  * nm_platform_ip6_route_to_string:
  * @route: pointer to NMPlatformIP6Route route structure
+ * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * 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.
+ * Returns: a string representation of the route.
  */
 const char *
-nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
+nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len)
 {
 	char s_network[INET6_ADDRSTRLEN], s_gateway[INET6_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 
-	g_return_val_if_fail (route, "(unknown)");
+	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
+		return buf;
 
 	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 (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
-	g_snprintf (_nm_platform_to_string_buffer, sizeof (_nm_platform_to_string_buffer),
+	g_snprintf (buf, len,
 	            "%s/%d"
 	            " via %s"
 	            "%s"
@@ -2664,10 +3621,10 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
 	            route->metric,
 	            route->mss,
 	            source_to_string (route->source));
-	return _nm_platform_to_string_buffer;
+	return buf;
 }
 
-#define _CMP_POINTER(a, b)                                  \
+#define _CMP_SELF(a, b)                                     \
     G_STMT_START {                                          \
         if ((a) == (b))                                     \
             return 0;                                       \
@@ -2677,6 +3634,19 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
             return 1;                                       \
     } G_STMT_END
 
+#define _CMP_DIRECT(a, b)                                   \
+    G_STMT_START {                                          \
+        if ((a) != (b))                                     \
+            return ((a) < (b)) ? -1 : 1;                    \
+    } G_STMT_END
+
+#define _CMP_DIRECT_MEMCMP(a, b, size)                      \
+    G_STMT_START {                                          \
+        int c = memcmp ((a), (b), (size));                  \
+        if (c != 0)                                         \
+            return c < 0 ? -1 : 1;                          \
+    } G_STMT_END
+
 #define _CMP_FIELD(a, b, field)                             \
     G_STMT_START {                                          \
         if (((a)->field) != ((b)->field))                   \
@@ -2732,13 +3702,12 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route)
 int
 nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_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, vlan_id);
 	_CMP_FIELD (a, b, flags);
 	_CMP_FIELD (a, b, connected);
 	_CMP_FIELD (a, b, mtu);
@@ -2757,14 +3726,126 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 }
 
 int
+nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, input_flags);
+	_CMP_FIELD (a, b, output_flags);
+	_CMP_FIELD (a, b, input_key);
+	_CMP_FIELD (a, b, output_key);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	return 0;
+}
+
+int
+nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, p_key);
+	_CMP_FIELD_STR_INTERNED (a, b, mode);
+	return 0;
+}
+
+int
+nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD_MEMCMP (a, b, local);
+	_CMP_FIELD_MEMCMP (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tclass);
+	_CMP_FIELD (a, b, encap_limit);
+	_CMP_FIELD (a, b, flow_label);
+	_CMP_FIELD (a, b, proto);
+	return 0;
+}
+
+int
+nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	return 0;
+}
+
+int
+nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, mode);
+	_CMP_FIELD_BOOL (a, b, no_promisc);
+	return 0;
+}
+
+int
+nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD (a, b, remote);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	_CMP_FIELD (a, b, flags);
+	_CMP_FIELD (a, b, proto);
+	return 0;
+}
+
+int
+nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, id);
+	_CMP_FIELD (a, b, flags);
+	return 0;
+}
+
+int
+nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, parent_ifindex);
+	_CMP_FIELD (a, b, id);
+	_CMP_FIELD (a, b, group);
+	_CMP_FIELD (a, b, local);
+	_CMP_FIELD_MEMCMP (a, b, group6);
+	_CMP_FIELD_MEMCMP (a, b, local6);
+	_CMP_FIELD (a, b, tos);
+	_CMP_FIELD (a, b, ttl);
+	_CMP_FIELD_BOOL (a, b, learning);
+	_CMP_FIELD (a, b, ageing);
+	_CMP_FIELD (a, b, limit);
+	_CMP_FIELD (a, b, dst_port);
+	_CMP_FIELD (a, b, src_port_min);
+	_CMP_FIELD (a, b, src_port_max);
+	_CMP_FIELD_BOOL (a, b, proxy);
+	_CMP_FIELD_BOOL (a, b, rsc);
+	_CMP_FIELD_BOOL (a, b, l2miss);
+	_CMP_FIELD_BOOL (a, b, l3miss);
+	return 0;
+}
+
+int
 nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (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, peer_address);
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
 	_CMP_FIELD (a, b, preferred);
@@ -2775,11 +3856,17 @@ nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4A
 int
 nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
 {
-	_CMP_POINTER (a, b);
+	const struct in6_addr *p_a, *p_b;
+
+	_CMP_SELF (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);
+
+	p_a = nm_platform_ip6_address_get_peer (a);
+	p_b = nm_platform_ip6_address_get_peer (b);
+	_CMP_DIRECT_MEMCMP (p_a, p_b, sizeof (*p_a));
+
 	_CMP_FIELD (a, b, plen);
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
@@ -2791,7 +3878,7 @@ nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6A
 int
 nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, source);
 	_CMP_FIELD (a, b, network);
@@ -2807,7 +3894,7 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 int
 nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b)
 {
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
 	_CMP_FIELD (a, b, source);
 	_CMP_FIELD_MEMCMP (a, b, network);
@@ -2818,9 +3905,6 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 	return 0;
 }
 
-#undef _CMP_FIELD
-#undef _CMP_FIELD_MEMCMP
-
 /**
  * nm_platform_ip_address_cmp_expiry:
  * @a: a NMPlatformIPAddress to compare
@@ -2839,7 +3923,7 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 {
 	gint64 ta = 0, tb = 0;
 
-	_CMP_POINTER (a, b);
+	_CMP_SELF (a, b);
 
 	if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0)
 		ta = G_MAXINT64;
@@ -2872,8 +3956,6 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 	return ta < tb ? -1 : 1;
 }
 
-#undef _CMP_POINTER
-
 const char *
 nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type)
 {
@@ -2893,31 +3975,31 @@ static void
 log_link (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformLink *device, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
 
-	_LOGD ("signal: link %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_link_to_string (device));
+	_LOGD ("signal: link %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_link_to_string (device, NULL, 0));
 }
 
 static void
 log_ip4_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: address 4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_address_to_string (address));
+	_LOGD ("signal: address 4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_address_to_string (address, NULL, 0));
 }
 
 static void
 log_ip6_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: address 6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_address_to_string (address));
+	_LOGD ("signal: address 6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_address_to_string (address, NULL, 0));
 }
 
 static void
 log_ip4_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: route   4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_route_to_string (route));
+	_LOGD ("signal: route   4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_route_to_string (route, NULL, 0));
 }
 
 static void
 log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route));
+	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
 }
 
 /******************************************************************/
@@ -2994,7 +4076,7 @@ const NMPlatformVTableRoute nm_platform_vtable_route_v4 = {
 	.addr_family                    = AF_INET,
 	.sizeof_route                   = sizeof (NMPlatformIP4Route),
 	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b)) nm_platform_ip4_route_cmp,
-	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route)) nm_platform_ip4_route_to_string,
+	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip4_route_to_string,
 	.route_get_all                  = nm_platform_ip4_route_get_all,
 	.route_add                      = _vtr_v4_route_add,
 	.route_delete                   = _vtr_v4_route_delete,
@@ -3007,7 +4089,7 @@ const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
 	.addr_family                    = AF_INET6,
 	.sizeof_route                   = sizeof (NMPlatformIP6Route),
 	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b)) nm_platform_ip6_route_cmp,
-	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route)) nm_platform_ip6_route_to_string,
+	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip6_route_to_string,
 	.route_get_all                  = nm_platform_ip6_route_get_all,
 	.route_add                      = _vtr_v6_route_add,
 	.route_delete                   = _vtr_v6_route_delete,
@@ -3051,14 +4133,6 @@ 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, 5, NM_TYPE_POBJECT_TYPE, 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)
 {
@@ -3069,6 +4143,8 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	object_class->set_property = set_property;
 	object_class->constructed = constructed;
 
+	platform_class->wifi_set_powersave = wifi_set_powersave;
+
 	g_object_class_install_property
 	 (object_class, PROP_REGISTER_SINGLETON,
 	     g_param_spec_boolean (NM_PLATFORM_REGISTER_SINGLETON, "", "",
@@ -3077,10 +4153,21 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	                           G_PARAM_CONSTRUCT_ONLY |
 	                           G_PARAM_STATIC_STRINGS));
 
+#define SIGNAL(signal, signal_id, method) \
+	G_STMT_START { \
+		signals[signal] = \
+			g_signal_new_class_handler (""signal_id"", \
+			                            G_OBJECT_CLASS_TYPE (object_class), \
+			                            G_SIGNAL_RUN_FIRST, \
+			                            G_CALLBACK (method), \
+			                            NULL, NULL, NULL, \
+			                            G_TYPE_NONE, 4, NM_TYPE_POBJECT_TYPE, G_TYPE_INT, G_TYPE_POINTER, NM_TYPE_PLATFORM_SIGNAL_CHANGE_TYPE); \
+	} G_STMT_END
+
 	/* 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)
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_LINK,        NM_PLATFORM_SIGNAL_LINK_CHANGED,        log_link);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, log_ip4_address);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, log_ip6_address);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,   NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,   log_ip4_route);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,   NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,   log_ip6_route);
 }