summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
committerMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
commit7514efc2f38c9ace4557d4e69d68e7d380389030 (patch)
tree7fb00fda86cfcc2ca377f191633a7cfdbfea7ca3 /src/platform
parentd6201f5d8daada3d64a0a3e0038e14eebec683ce (diff)
Imported Upstream version 1.4.0 upstream/1.4.0
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/Makefile.in14
-rw-r--r--src/platform/nm-fake-platform.c27
-rw-r--r--src/platform/nm-linux-platform.c316
-rw-r--r--src/platform/nm-linux-platform.h6
-rw-r--r--src/platform/nm-platform-utils.c134
-rw-r--r--src/platform/nm-platform-utils.h7
-rw-r--r--src/platform/nm-platform.c199
-rw-r--r--src/platform/nm-platform.h49
-rw-r--r--src/platform/nmp-object.c14
-rw-r--r--src/platform/nmp-object.h6
-rw-r--r--src/platform/tests/Makefile.am6
-rw-r--r--src/platform/tests/Makefile.in25
-rw-r--r--src/platform/tests/monitor.c2
-rw-r--r--src/platform/tests/test-common.h4
-rw-r--r--src/platform/tests/test-general.c2
-rw-r--r--src/platform/tests/test-link.c73
-rw-r--r--src/platform/tests/test-nmp-object.c2
-rw-r--r--src/platform/tests/test-route.c16
-rw-r--r--src/platform/wifi/wifi-utils-private.h2
-rw-r--r--src/platform/wifi/wifi-utils.c21
-rw-r--r--src/platform/wifi/wifi-utils.h3
21 files changed, 590 insertions, 338 deletions
diff --git a/src/platform/Makefile.in b/src/platform/Makefile.in
index a528da66..bf094348 100644
--- a/src/platform/Makefile.in
+++ b/src/platform/Makefile.in
@@ -89,8 +89,9 @@ build_triplet = @build@
 host_triplet = @host@
 subdir = src/platform
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -225,6 +226,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -281,13 +283,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -299,6 +302,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -359,6 +364,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -394,6 +401,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 7b545076..8a4f69b6 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -31,9 +31,10 @@
 #include "nm-utils.h"
 
 #include "nm-core-utils.h"
+#include "nm-platform-utils.h"
 #include "nmp-object.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 /*********************************************************************************************/
 
@@ -505,7 +506,7 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 	return TRUE;
 }
 
-static gboolean
+static NMPlatformError
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
@@ -514,7 +515,7 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 	    || len == 0
 	    || len > NM_UTILS_HWADDR_LEN_MAX
 	    || !addr)
-		g_return_val_if_reached (FALSE);
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
 	if (   device->link.addr.len != len
 	    || (   len > 0
@@ -524,7 +525,7 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 		link_changed (platform, link_get (platform, ifindex), TRUE);
 	}
 
-	return TRUE;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -709,12 +710,13 @@ static gboolean
 infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
 	NMFakePlatformLink *device, *parent_device;
-	gs_free char *name = NULL;
+	char name[IFNAMSIZ];
 
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
-	name = g_strdup_printf ("%s.%04x", parent_device->link.name, p_key);
+	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
+
 	if (!link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0, out_link))
 		return FALSE;
 
@@ -726,7 +728,6 @@ infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMP
 	device->lnk->lnk_infiniband.p_key = p_key;
 	device->lnk->lnk_infiniband.mode = "datagram";
 	device->link.parent = parent;
-
 	return TRUE;
 }
 
@@ -739,7 +740,7 @@ infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
-	name = g_strdup_printf ("%s.%04x", parent_device->link.name, p_key);
+	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
 	return link_delete (platform, nm_platform_link_get_ifindex (platform, name));
 }
 
@@ -911,7 +912,7 @@ ip4_address_add (NMPlatform *platform,
 	int i;
 
 	memset (&address, 0, sizeof (address));
-	address.source = NM_IP_CONFIG_SOURCE_KERNEL;
+	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
 	address.ifindex = ifindex;
 	address.address = addr;
 	address.peer_address = peer_addr;
@@ -962,7 +963,7 @@ ip6_address_add (NMPlatform *platform,
 	int i;
 
 	memset (&address, 0, sizeof (address));
-	address.source = NM_IP_CONFIG_SOURCE_KERNEL;
+	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
 	address.ifindex = ifindex;
 	address.address = addr;
 	address.peer_address = (IN6_IS_ADDR_UNSPECIFIED (&peer_addr) || IN6_ARE_ADDR_EQUAL (&addr, &peer_addr)) ? in6addr_any : peer_addr;
@@ -1207,9 +1208,8 @@ ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 	scope = gateway == 0 ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE;
 
 	memset (&route, 0, sizeof (route));
-	route.source = NM_IP_CONFIG_SOURCE_KERNEL;
 	route.ifindex = ifindex;
-	route.source = source;
+	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (source);
 	route.network = nm_utils_ip4_address_clear_host_address (network, plen);
 	route.plen = plen;
 	route.gateway = gateway;
@@ -1273,9 +1273,8 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 	metric = nm_utils_ip6_route_metric_normalize (metric);
 
 	memset (&route, 0, sizeof (route));
-	route.source = NM_IP_CONFIG_SOURCE_KERNEL;
 	route.ifindex = ifindex;
-	route.source = source;
+	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (source);
 	nm_utils_ip6_address_clear_host_address (&route.network, &network, plen);
 	route.plen = plen;
 	route.gateway = gateway;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index f6f7be80..98c4e461 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -176,6 +176,11 @@
  * Forward declarations and enums
  ******************************************************************/
 
+typedef enum {
+	INFINIBAND_ACTION_CREATE_CHILD,
+	INFINIBAND_ACTION_DELETE_CHILD,
+} InfinibandAction;
+
 enum {
 	DELAYED_ACTION_IDX_REFRESH_ALL_LINKS,
 	DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES,
@@ -303,23 +308,6 @@ _support_user_ipv6ll_detect (struct nlattr **tb)
  * Various utilities
  ******************************************************************/
 
-static void
-clear_host_address (int family, const void *network, guint8 plen, void *dst)
-{
-	g_return_if_fail (network);
-
-	switch (family) {
-	case AF_INET:
-		*((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen);
-		break;
-	case AF_INET6:
-		nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-}
-
 static int
 _vlan_qos_mapping_cmp_from (gconstpointer a, gconstpointer b, gpointer user_data)
 {
@@ -372,7 +360,7 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_INFINIBAND,    "infiniband",  NULL,          NULL },
 	{ NM_LINK_TYPE_OLPC_MESH,     "olpc-mesh",   NULL,          NULL },
 	{ NM_LINK_TYPE_WIFI,          "wifi",        NULL,          "wlan" },
-	{ NM_LINK_TYPE_WWAN_ETHERNET, "wwan",        NULL,          "wwan" },
+	{ NM_LINK_TYPE_WWAN_NET,      "wwan",        NULL,          "wwan" },
 	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
 
 	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
@@ -588,12 +576,17 @@ _lookup_cached_link (const NMPCache *cache, int ifindex, gboolean *completed_fro
 #define DEVTYPE_PREFIX "DEVTYPE="
 
 static char *
-_linktype_read_devtype (const char *sysfs_path)
+_linktype_read_devtype (const char *ifname)
 {
-	gs_free char *uevent = g_strdup_printf ("%s/uevent", sysfs_path);
+	char uevent[NM_STRLEN ("/sys/class/net/123456789012345/uevent\0") + 100 /*safety*/];
 	char *contents = NULL;
 	char *cont, *end;
 
+	nm_sprintf_buf (uevent,
+	                "/sys/class/net/%s/uevent",
+	                NM_ASSERT_VALID_PATH_COMPONENT (ifname));
+	nm_assert (strlen (uevent) < sizeof (uevent) - 1);
+
 	if (!g_file_get_contents (uevent, &contents, NULL, NULL))
 		return NULL;
 	for (cont = contents; cont; cont = end) {
@@ -690,9 +683,8 @@ _linktype_get_type (NMPlatform *platform,
 		return NM_LINK_TYPE_IP6TNL;
 
 	if (ifname) {
+		char anycast_mask[NM_STRLEN ("/sys/class/net/123456789012345/anycast_mask\0") + 100 /*safety*/];
 		gs_free char *driver = NULL;
-		gs_free char *sysfs_path = NULL;
-		gs_free char *anycast_mask = NULL;
 		gs_free char *devtype = NULL;
 
 		/* Fallback OVS detection for kernel <= 3.16 */
@@ -709,12 +701,15 @@ _linktype_get_type (NMPlatform *platform,
 			}
 		}
 
-		sysfs_path = g_strdup_printf ("/sys/class/net/%s", ifname);
-		anycast_mask = g_strdup_printf ("%s/anycast_mask", sysfs_path);
+		nm_sprintf_buf (anycast_mask,
+		                "/sys/class/net/%s/anycast_mask",
+		                NM_ASSERT_VALID_PATH_COMPONENT (ifname));
+		nm_assert (strlen (anycast_mask) < sizeof (anycast_mask) - 1);
+
 		if (g_file_test (anycast_mask, G_FILE_TEST_EXISTS))
 			return NM_LINK_TYPE_OLPC_MESH;
 
-		devtype = _linktype_read_devtype (sysfs_path);
+		devtype = _linktype_read_devtype (ifname);
 		for (i = 0; devtype && i < G_N_ELEMENTS (linktypes); i++) {
 			if (g_strcmp0 (devtype, linktypes[i].devtype) == 0) {
 				if (linktypes[i].nm_type == NM_LINK_TYPE_BNEP) {
@@ -729,10 +724,19 @@ _linktype_get_type (NMPlatform *platform,
 		}
 
 		/* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */
-		if (wifi_utils_is_wifi (ifname, sysfs_path))
+		if (wifi_utils_is_wifi (ifname))
 			return NM_LINK_TYPE_WIFI;
 
 		if (arptype == ARPHRD_ETHER) {
+			/* Misc non-upstream WWAN drivers.  rmnet is Qualcomm's proprietary
+			 * modem interface, ccmni is MediaTek's.  FIXME: these drivers should
+			 * really set devtype=WWAN.
+			 */
+			if (g_str_has_prefix (ifname, "rmnet") ||
+			    g_str_has_prefix (ifname, "rev_rmnet") ||
+			    g_str_has_prefix (ifname, "ccmni"))
+				return NM_LINK_TYPE_WWAN_NET;
+
 			/* Standard wired ethernet interfaces don't report an rtnl_link_type, so
 			 * only allow fallback to Ethernet if no type is given.  This should
 			 * prevent future virtual network drivers from being treated as Ethernet
@@ -803,9 +807,10 @@ _nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
 static gboolean
 _parse_af_inet6 (NMPlatform *platform,
                  struct nlattr *attr,
-                 NMUtilsIPv6IfaceId *out_iid,
-                 guint8 *out_iid_is_valid,
-                 guint8 *out_addr_gen_mode_inv)
+                 NMUtilsIPv6IfaceId *out_token,
+                 gboolean *out_token_valid,
+                 guint8 *out_addr_gen_mode_inv,
+                 gboolean *out_addr_gen_mode_valid)
 {
 	static struct nla_policy policy[IFLA_INET6_MAX+1] = {
 		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
@@ -819,7 +824,8 @@ _parse_af_inet6 (NMPlatform *platform,
 	struct nlattr *tb[IFLA_INET6_MAX+1];
 	int err;
 	struct in6_addr i6_token;
-	gboolean iid_is_valid = FALSE;
+	gboolean token_valid = FALSE;
+	gboolean addr_gen_mode_valid = FALSE;
 	guint8 i6_addr_gen_mode_inv = 0;
 	gboolean success = FALSE;
 
@@ -836,8 +842,7 @@ _parse_af_inet6 (NMPlatform *platform,
 
 	if (_check_addr_or_errout (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr))) {
 		nla_memcpy (&i6_token, tb[IFLA_INET6_TOKEN], sizeof (struct in6_addr));
-		if (!IN6_IS_ADDR_UNSPECIFIED (&i6_token))
-			iid_is_valid = TRUE;
+		token_valid = TRUE;
 	}
 
 	/* Hack to detect support addrgenmode of the kernel. We only parse
@@ -852,21 +857,18 @@ _parse_af_inet6 (NMPlatform *platform,
 			 * to signal "unset". */
 			goto errout;
 		}
+		addr_gen_mode_valid = TRUE;
 	}
 
 	success = TRUE;
-	if (iid_is_valid) {
-		out_iid->id_u8[7] = i6_token.s6_addr[15];
-		out_iid->id_u8[6] = i6_token.s6_addr[14];
-		out_iid->id_u8[5] = i6_token.s6_addr[13];
-		out_iid->id_u8[4] = i6_token.s6_addr[12];
-		out_iid->id_u8[3] = i6_token.s6_addr[11];
-		out_iid->id_u8[2] = i6_token.s6_addr[10];
-		out_iid->id_u8[1] = i6_token.s6_addr[9];
-		out_iid->id_u8[0] = i6_token.s6_addr[8];
-		*out_iid_is_valid = TRUE;
-	}
-	*out_addr_gen_mode_inv = i6_addr_gen_mode_inv;
+	if (token_valid) {
+		*out_token_valid = token_valid;
+		nm_utils_ipv6_interface_identifier_get_from_addr (out_token, &i6_token);
+	}
+	if (addr_gen_mode_valid) {
+		*out_addr_gen_mode_valid = addr_gen_mode_valid;
+		*out_addr_gen_mode_inv = i6_addr_gen_mode_inv;
+	}
 errout:
 	return success;
 }
@@ -1441,6 +1443,8 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	NMPObject *lnk_data = NULL;
 	gboolean address_complete_from_cache = TRUE;
 	gboolean lnk_data_complete_from_cache = TRUE;
+	gboolean af_inet6_token_valid = FALSE;
+	gboolean af_inet6_addr_gen_mode_valid = FALSE;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
 		return NULL;
@@ -1472,6 +1476,15 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		nl_info_data = li[IFLA_INFO_DATA];
 	}
 
+	if (tb[IFLA_STATS64]) {
+		struct rtnl_link_stats64 *stats = nla_data (tb[IFLA_STATS64]);
+
+		obj->link.rx_packets = stats->rx_packets;
+		obj->link.rx_bytes = stats->rx_bytes;
+		obj->link.tx_packets = stats->tx_packets;
+		obj->link.tx_bytes = stats->tx_bytes;
+	}
+
 	obj->link.n_ifi_flags = ifi->ifi_flags;
 	obj->link.connected = NM_FLAGS_HAS (obj->link.n_ifi_flags, IFF_LOWER_UP);
 	obj->link.arptype = ifi->ifi_type;
@@ -1517,9 +1530,10 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 			case AF_INET6:
 				_parse_af_inet6 (platform,
 				                 af_attr,
-				                 &obj->link.inet6_token.iid,
-				                 &obj->link.inet6_token.is_valid,
-				                 &obj->link.inet6_addr_gen_mode_inv);
+				                 &obj->link.inet6_token,
+				                 &af_inet6_token_valid,
+				                 &obj->link.inet6_addr_gen_mode_inv,
+				                 &af_inet6_addr_gen_mode_valid);
 				break;
 			}
 		}
@@ -1561,7 +1575,10 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	if (   completed_from_cache
 	    && (   lnk_data_complete_from_cache
-	        || address_complete_from_cache)) {
+	        || address_complete_from_cache
+	        || !af_inet6_token_valid
+	        || !af_inet6_addr_gen_mode_valid
+	        || !tb[IFLA_STATS64])) {
 		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
 		if (link_cached) {
 			if (   lnk_data_complete_from_cache
@@ -1580,6 +1597,16 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 			}
 			if (address_complete_from_cache)
 				obj->link.addr = link_cached->link.addr;
+			if (!af_inet6_token_valid)
+				obj->link.inet6_token = link_cached->link.inet6_token;
+			if (!af_inet6_addr_gen_mode_valid)
+				obj->link.inet6_addr_gen_mode_inv = link_cached->link.inet6_addr_gen_mode_inv;
+			if (!tb[IFLA_STATS64]) {
+				obj->link.rx_packets = link_cached->link.rx_packets;
+				obj->link.rx_bytes = link_cached->link.rx_bytes;
+				obj->link.tx_packets = link_cached->link.tx_packets;
+				obj->link.tx_bytes = link_cached->link.tx_bytes;
+			}
 		}
 	}
 
@@ -1666,7 +1693,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 		}
 	}
 
-	obj->ip_address.source = NM_IP_CONFIG_SOURCE_KERNEL;
+	obj->ip_address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
 
 	obj->ip_address.n_ifa_flags = tb[IFA_FLAGS]
 	                              ? nla_get_u32 (tb[IFA_FLAGS])
@@ -1896,9 +1923,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		 *
 		 * This happens, because this route is not nmp_object_is_alive().
 		 * */
-		obj->ip_route.source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
-	} else
-		obj->ip_route.source = nmp_utils_ip_config_source_from_rtprot (rtm->rtm_protocol);
+		obj->ip_route.rt_cloned = TRUE;
+	}
+
+	obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot (rtm->rtm_protocol);
 
 	obj_result = obj;
 	obj = NULL;
@@ -1951,7 +1979,8 @@ nmp_object_new_from_nl (NMPlatform *platform, const NMPCache *cache, struct nl_m
 
 static gboolean
 _nl_msg_new_link_set_afspec (struct nl_msg *msg,
-                             int addr_gen_mode)
+                             int addr_gen_mode,
+                             NMUtilsIPv6IfaceId *iid)
 {
 	struct nlattr *af_spec;
 	struct nlattr *af_attr;
@@ -1961,11 +1990,19 @@ _nl_msg_new_link_set_afspec (struct nl_msg *msg,
 	if (!(af_spec = nla_nest_start (msg, IFLA_AF_SPEC)))
 		goto nla_put_failure;
 
-	if (addr_gen_mode >= 0) {
+	if (addr_gen_mode >= 0 || iid) {
 		if (!(af_attr = nla_nest_start (msg, AF_INET6)))
 			goto nla_put_failure;
 
-		NLA_PUT_U8 (msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode);
+		if (addr_gen_mode >= 0)
+			NLA_PUT_U8 (msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode);
+
+		if (iid) {
+			struct in6_addr i6_token = { .s6_addr = { 0, } };
+
+			nm_utils_ipv6_addr_set_interface_identifier (&i6_token, *iid);
+			NLA_PUT (msg, IFLA_INET6_TOKEN, sizeof (struct in6_addr), &i6_token);
+		}
 
 		nla_nest_end (msg, af_attr);
 	}
@@ -2258,7 +2295,7 @@ _nl_msg_new_route (int nlmsg_type,
 		.rtm_family = family,
 		.rtm_tos = 0,
 		.rtm_table = RT_TABLE_MAIN, /* omit setting RTA_TABLE attribute */
-		.rtm_protocol = nmp_utils_ip_config_source_to_rtprot (source),
+		.rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot (source),
 		.rtm_scope = scope,
 		.rtm_type = RTN_UNICAST,
 		.rtm_flags = 0,
@@ -2282,7 +2319,7 @@ _nl_msg_new_route (int nlmsg_type,
 
 	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
 
-	clear_host_address (family, network, plen, &network_clean);
+	nm_utils_ipx_address_clear_host_address (family, &network_clean, network, plen);
 	NLA_PUT (msg, RTA_DST, addr_len, &network_clean);
 
 	NLA_PUT_U32 (msg, RTA_PRIORITY, metric);
@@ -2485,6 +2522,7 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value)
 	gsize len;
 	char *actual;
 	gs_free char *actual_free = NULL;
+	int errsv;
 
 	g_return_val_if_fail (path != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
@@ -2495,18 +2533,22 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value)
 	/* Don't write to suspicious locations */
 	g_assert (!strstr (path, "/../"));
 
-	if (!nm_platform_netns_push (platform, &netns))
+	if (!nm_platform_netns_push (platform, &netns)) {
+		errno = ENETDOWN;
 		return FALSE;
+	}
 
 	fd = open (path, O_WRONLY | O_TRUNC);
 	if (fd == -1) {
-		if (errno == ENOENT) {
+		errsv = errno;
+		if (errsv == ENOENT) {
 			_LOGD ("sysctl: failed to open '%s': (%d) %s",
-			       path, errno, strerror (errno));
+			       path, errsv, strerror (errsv));
 		} else {
 			_LOGE ("sysctl: failed to open '%s': (%d) %s",
-			       path, errno, strerror (errno));
+			       path, errsv, strerror (errsv));
 		}
+		errno = errsv;
 		return FALSE;
 	}
 
@@ -2527,26 +2569,43 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value)
 	actual[len] = '\0';
 
 	/* Try to write the entire value three times if a partial write occurs */
+	errsv = 0;
 	for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) {
 		nwrote = write (fd, actual, len);
 		if (nwrote == -1) {
-			if (errno == EINTR) {
+			errsv = errno;
+			if (errsv == EINTR) {
 				_LOGD ("sysctl: interrupted, will try again");
 				continue;
 			}
 			break;
 		}
 	}
-	if (nwrote == -1 && errno != EEXIST) {
+	if (nwrote == -1 && errsv != EEXIST) {
 		_LOGE ("sysctl: failed to set '%s' to '%s': (%d) %s",
-		       path, value, errno, strerror (errno));
+		       path, value, errsv, strerror (errsv));
 	} else if (nwrote < len) {
 		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
 		       path, value);
 	}
 
-	close (fd);
-	return (nwrote == len);
+	if (nwrote != len) {
+		if (close (fd) != 0) {
+			if (errsv != 0)
+				errno = errsv;
+		} else if (errsv != 0)
+			errno = errsv;
+		else
+			errno = EIO;
+		return FALSE;
+	}
+	if (close (fd) != 0) {
+		/* errno is already properly set. */
+		return FALSE;
+	}
+
+	/* success. errno is undefined (no need to set). */
+	return TRUE;
 }
 
 static GSList *sysctl_clear_cache_list;
@@ -3689,6 +3748,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	case RTM_NEWLINK:
 	case RTM_NEWADDR:
 	case RTM_NEWROUTE:
+	case RTM_GETLINK:
 		cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
 
 		cache_post (platform, msghdr, cache_op, obj, obj_cache);
@@ -3724,6 +3784,16 @@ cache_lookup_link (NMPlatform *platform, int ifindex)
 	return obj_cache;
 }
 
+const NMPlatformObject *const*
+nm_linux_platform_lookup (NMPlatform *platform, const NMPCacheId *cache_id, guint *out_len)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), NULL);
+	g_return_val_if_fail (cache_id, NULL);
+
+	return nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                               cache_id, out_len);
+}
+
 static GArray *
 link_get_all (NMPlatform *platform)
 {
@@ -4036,6 +4106,9 @@ retry:
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
 		log_detail = ", firmware not found";
 		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
+		log_level = LOGL_DEBUG;
+		result = NM_PLATFORM_ERROR_NOT_FOUND;
 	} else {
 		log_level = LOGL_ERR;
 		result = NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -4272,7 +4345,7 @@ link_get_udev_device (NMPlatform *platform, int ifindex)
 	return obj_cache ? (GObject *) obj_cache->_link.udev.device : NULL;
 }
 
-static gboolean
+static NMPlatformError
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -4280,7 +4353,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 
 	if (!_support_user_ipv6ll_get ()) {
 		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
-		return FALSE;
+		return NM_PLATFORM_ERROR_OPNOTSUPP;
 	}
 
 	_LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
@@ -4294,8 +4367,22 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	                          0,
 	                          0);
 	if (   !nlmsg
-	    || !_nl_msg_new_link_set_afspec (nlmsg,
-	                                     mode))
+	    || !_nl_msg_new_link_set_afspec (nlmsg, mode, NULL))
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+
+	return do_change_link (platform, ifindex, nlmsg);
+}
+
+static gboolean
+link_set_token (NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+
+	_LOGD ("link: change %d: token: set IPv6 address generation token to %s",
+	       ifindex, nm_utils_inet6_interface_identifier_to_token (iid, NULL));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK, 0, ifindex, NULL, 0, 0);
+	if (!nlmsg || !_nl_msg_new_link_set_afspec (nlmsg, -1, &iid))
 		g_return_val_if_reached (FALSE);
 
 	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
@@ -4338,14 +4425,14 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 	return nmp_utils_ethtool_supports_vlans (obj->link.name);
 }
 
-static gboolean
+static NMPlatformError
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	gs_free char *mac = NULL;
 
 	if (!address || !length)
-		g_return_val_if_reached (FALSE);
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
 	_LOGD ("link: change %d: address: %s (%lu bytes)", ifindex,
 	       (mac = nm_utils_hwaddr_ntoa (address, length)),
@@ -4358,13 +4445,13 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	                          0,
 	                          0);
 	if (!nlmsg)
-		return FALSE;
+		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
 
 	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, ifindex, nlmsg);
 nla_put_failure:
-	g_return_val_if_reached (FALSE);
+	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
 }
 
 static gboolean
@@ -5095,57 +5182,67 @@ link_release (NMPlatform *platform, int master, int slave)
 /******************************************************************/
 
 static gboolean
-_infiniband_partition_action (NMPlatform *platform, int parent, int p_key, const char *action, char **ifname)
+_infiniband_partition_action (NMPlatform *platform,
+                              InfinibandAction action,
+                              int parent,
+                              int p_key,
+                              const NMPlatformLink **out_link)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPObject *obj_parent;
-	gs_free char *path = NULL;
-	gs_free char *id = NULL;
+	const NMPObject *obj;
+	char path[NM_STRLEN ("/sys/class/net/%s/%s") + IFNAMSIZ + 100];
+	char id[20];
+	char name[IFNAMSIZ];
+	gboolean success;
+
+	nm_assert (NM_IN_SET (action, INFINIBAND_ACTION_CREATE_CHILD, INFINIBAND_ACTION_DELETE_CHILD));
+	nm_assert (p_key > 0 && p_key <= 0xffff && p_key != 0x8000);
 
 	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
-	if (!obj_parent || !obj_parent->link.name[0])
-		g_return_val_if_reached (FALSE);
+	if (!obj_parent || !obj_parent->link.name[0]) {
+		errno = ENOENT;
+		return FALSE;
+	}
+
+	nm_sprintf_buf (path,
+	                "/sys/class/net/%s/%s",
+	                NM_ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name),
+	                (action == INFINIBAND_ACTION_CREATE_CHILD
+	                     ? "create_child"
+	                     : "delete_child"));
+	nm_sprintf_buf (id, "0x%04x", p_key);
+	success = nm_platform_sysctl_set (platform, path, id);
+	if (!success) {
+		if (   action == INFINIBAND_ACTION_DELETE_CHILD
+		    && errno == ENODEV)
+			return TRUE;
+		return FALSE;
+	}
 
-	*ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
+	nm_utils_new_infiniband_name (name, obj_parent->link.name, p_key);
+	do_request_link (platform, 0, name);
 
-	path = g_strdup_printf ("/sys/class/net/%s/%s",
-	                        NM_ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name),
-	                        action);
-	id = g_strdup_printf ("0x%04x", p_key);
+	if (action == INFINIBAND_ACTION_DELETE_CHILD)
+		return TRUE;
 
-	return nm_platform_sysctl_set (platform, path, id);
+	obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE,
+	                                  NM_LINK_TYPE_INFINIBAND, NULL, NULL);
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
+	return !!obj;
 }
 
-
 static gboolean
 infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
-	const NMPObject *obj;
-	gs_free char *ifname = NULL;
-
-	if (!_infiniband_partition_action (platform, parent, p_key, "create_child", &ifname))
-		return FALSE;
-
-	do_request_link (platform, 0, ifname);
-
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                                  0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL);
-	if (out_link)
-		*out_link = obj ? &obj->link : NULL;
-	return !!obj;
+	return _infiniband_partition_action (platform, INFINIBAND_ACTION_CREATE_CHILD, parent, p_key, out_link);
 }
 
 static gboolean
 infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
 {
-	gs_free char *ifname = NULL;
-
-	if (!_infiniband_partition_action (platform, parent, p_key, "delete_child", &ifname)) {
-		if (errno != ENODEV)
-			return FALSE;
-	}
-
-	return TRUE;
+	return _infiniband_partition_action (platform, INFINIBAND_ACTION_DELETE_CHILD, parent, p_key, NULL);
 }
 
 /******************************************************************/
@@ -5587,7 +5684,7 @@ ipx_route_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type, NM
 		nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (routes[i])) == klass);
 
 		if (   with_rtprot_kernel
-		    || routes[i]->source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
+		    || routes[i]->rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
 			g_array_append_vals (array, routes[i], 1);
 	}
 	return array;
@@ -6421,6 +6518,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_get_udev_device = link_get_udev_device;
 
 	platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled;
+	platform_class->link_set_token = link_set_token;
 
 	platform_class->link_set_address = link_set_address;
 	platform_class->link_get_permanent_address = link_get_permanent_address;
diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h
index 4ae2fd14..3b2a440e 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/platform/nm-linux-platform.h
@@ -52,4 +52,10 @@ NMPlatform *nm_linux_platform_new (gboolean netns_support);
 
 void nm_linux_platform_setup (void);
 
+struct _NMPCacheId;
+
+const NMPlatformObject *const *nm_linux_platform_lookup (NMPlatform *platform,
+                                                         const struct _NMPCacheId *cache_id,
+                                                         guint *out_len);
+
 #endif /* __NETWORKMANAGER_LINUX_PLATFORM_H__ */
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index b4542adf..068801ee 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -144,8 +144,7 @@ nmp_utils_ethtool_get_permanent_address (const char *ifname,
 		struct ethtool_perm_addr e;
 		guint8 _extra_data[NM_UTILS_HWADDR_LEN_MAX + 1];
 	} edata;
-	static const guint8 zeros[NM_UTILS_HWADDR_LEN_MAX] = { 0 };
-	static guint8 ones[NM_UTILS_HWADDR_LEN_MAX] = { 0 };
+	guint i;
 
 	if (!ifname)
 		return FALSE;
@@ -157,18 +156,23 @@ nmp_utils_ethtool_get_permanent_address (const char *ifname,
 	if (!ethtool_get (ifname, &edata.e))
 		return FALSE;
 
-	g_assert (edata.e.size <= NM_UTILS_HWADDR_LEN_MAX);
-
-	/* Some drivers might return a permanent address of all zeros.
-	 * Reject that (rh#1264024) */
-	if (memcmp (edata.e.data, zeros, edata.e.size) == 0)
+	if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX)
+		return FALSE;
+	if (edata.e.size < 1)
 		return FALSE;
 
-	/* Some drivers return a permanent address of all ones. Reject that too */
-	if (G_UNLIKELY (ones[0] != 0xFF))
-		memset (ones, 0xFF, sizeof (ones));
-	if (memcmp (edata.e.data, ones, edata.e.size) == 0)
+	if (NM_IN_SET (edata.e.data[0], 0, 0xFF)) {
+		/* Some drivers might return a permanent address of all zeros.
+		 * Reject that (rh#1264024)
+		 *
+		 * Some drivers return a permanent address of all ones. Reject that too */
+		for (i = 1; i < edata.e.size; i++) {
+			if (edata.e.data[0] != edata.e.data[i])
+				goto not_all_0or1;
+		}
 		return FALSE;
+	}
+not_all_0or1:
 
 	memcpy (buf, edata.e.data, edata.e.size);
 	*length = edata.e.size;
@@ -433,14 +437,35 @@ nmp_utils_device_exists (const char *name)
 	return g_file_test (sysdir, G_FILE_TEST_EXISTS);
 }
 
-guint
-nmp_utils_ip_config_source_to_rtprot (NMIPConfigSource source)
+NMIPConfigSource
+nmp_utils_ip_config_source_from_rtprot (guint8 rtprot)
 {
-	switch (source) {
-	case NM_IP_CONFIG_SOURCE_UNKNOWN:
+	return ((int) rtprot) + 1;
+}
+
+NMIPConfigSource
+nmp_utils_ip_config_source_round_trip_rtprot (NMIPConfigSource source)
+{
+	/* when adding a route to kernel for a give @source, the resulting route
+	 * will be put into the cache with a source of NM_IP_CONFIG_SOURCE_RTPROT_*.
+	 * This function returns that. */
+	return nmp_utils_ip_config_source_from_rtprot (nmp_utils_ip_config_source_coerce_to_rtprot (source));
+}
+
+guint8
+nmp_utils_ip_config_source_coerce_to_rtprot (NMIPConfigSource source)
+{
+	/* when adding a route to kernel, we coerce the @source field
+	 * to rtm_protocol. This is not lossless as we map different
+	 * source values to the same RTPROT uint8 value. */
+	if (source <= NM_IP_CONFIG_SOURCE_UNKNOWN)
 		return RTPROT_UNSPEC;
+
+	if (source <= _NM_IP_CONFIG_SOURCE_RTPROT_LAST)
+		return source - 1;
+
+	switch (source) {
 	case NM_IP_CONFIG_SOURCE_KERNEL:
-	case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
 		return RTPROT_KERNEL;
 	case NM_IP_CONFIG_SOURCE_DHCP:
 		return RTPROT_DHCP;
@@ -453,18 +478,32 @@ nmp_utils_ip_config_source_to_rtprot (NMIPConfigSource source)
 }
 
 NMIPConfigSource
-nmp_utils_ip_config_source_from_rtprot (guint rtprot)
+nmp_utils_ip_config_source_coerce_from_rtprot (NMIPConfigSource source)
 {
-	switch (rtprot) {
-	case RTPROT_UNSPEC:
+	/* When we receive a route from kernel and put it into the platform cache,
+	 * we preserve the protocol field by converting it to a NMIPConfigSource
+	 * via nmp_utils_ip_config_source_from_rtprot().
+	 *
+	 * However, that is not the inverse of nmp_utils_ip_config_source_coerce_to_rtprot().
+	 * Instead, to go back to the original value, you need another step:
+	 *   nmp_utils_ip_config_source_coerce_from_rtprot (nmp_utils_ip_config_source_from_rtprot (rtprot)).
+	 *
+	 * This might partly restore the original source value, but of course that
+	 * is not really possible because nmp_utils_ip_config_source_coerce_to_rtprot()
+	 * is not injective.
+	 * */
+	switch (source) {
+	case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC:
 		return NM_IP_CONFIG_SOURCE_UNKNOWN;
-	case RTPROT_KERNEL:
-		return NM_IP_CONFIG_SOURCE_RTPROT_KERNEL;
-	case RTPROT_REDIRECT:
+
+	case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
+	case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT:
 		return NM_IP_CONFIG_SOURCE_KERNEL;
-	case RTPROT_RA:
+
+	case NM_IP_CONFIG_SOURCE_RTPROT_RA:
 		return NM_IP_CONFIG_SOURCE_RDISC;
-	case RTPROT_DHCP:
+
+	case NM_IP_CONFIG_SOURCE_RTPROT_DHCP:
 		return NM_IP_CONFIG_SOURCE_DHCP;
 
 	default:
@@ -472,3 +511,50 @@ nmp_utils_ip_config_source_from_rtprot (guint rtprot)
 	}
 }
 
+const char *
+nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize len)
+{
+	const char *s = NULL;
+	nm_utils_to_string_buffer_init (&buf, &len); \
+
+	if (!len)
+		return buf;
+
+	switch (source) {
+	case NM_IP_CONFIG_SOURCE_UNKNOWN:         s = "unknown"; break;
+
+	case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC:   s = "rt-unspec"; break;
+	case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT: s = "rt-redirect"; break;
+	case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:   s = "rt-kernel"; break;
+	case NM_IP_CONFIG_SOURCE_RTPROT_BOOT:     s = "rt-boot"; break;
+	case NM_IP_CONFIG_SOURCE_RTPROT_STATIC:   s = "rt-static"; break;
+	case NM_IP_CONFIG_SOURCE_RTPROT_DHCP:     s = "rt-dhcp"; break;
+	case NM_IP_CONFIG_SOURCE_RTPROT_RA:       s = "rt-ra"; break;
+
+	case NM_IP_CONFIG_SOURCE_KERNEL:          s = "kernel"; break;
+	case NM_IP_CONFIG_SOURCE_SHARED:          s = "shared"; break;
+	case NM_IP_CONFIG_SOURCE_IP4LL:           s = "ipv4ll"; break;
+	case NM_IP_CONFIG_SOURCE_PPP:             s = "ppp"; break;
+	case NM_IP_CONFIG_SOURCE_WWAN:            s = "wwan"; break;
+	case NM_IP_CONFIG_SOURCE_VPN:             s = "vpn"; break;
+	case NM_IP_CONFIG_SOURCE_DHCP:            s = "dhcp"; break;
+	case NM_IP_CONFIG_SOURCE_RDISC:           s = "rdisc"; break;
+	case NM_IP_CONFIG_SOURCE_USER:            s = "user"; break;
+	default:
+		break;
+	}
+
+	if (source >= 1 && source <= 0x100) {
+		if (s)
+			g_snprintf (buf, len, "%s", s);
+		else
+			g_snprintf (buf, len, "rt-%d", ((int) source) - 1);
+	} else {
+		if (s)
+			g_strlcpy (buf, s, len);
+		else
+			g_snprintf (buf, len, "(%d)", source);
+	}
+	return buf;
+}
+
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index f2594747..456c0865 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -54,7 +54,10 @@ const char *nmp_utils_udev_get_driver (GUdevDevice *device);
 
 gboolean nmp_utils_device_exists (const char *name);
 
-guint            nmp_utils_ip_config_source_to_rtprot   (NMIPConfigSource source);
-NMIPConfigSource nmp_utils_ip_config_source_from_rtprot (guint rtprot);
+NMIPConfigSource nmp_utils_ip_config_source_from_rtprot (guint8 rtprot) _nm_const;
+guint8           nmp_utils_ip_config_source_coerce_to_rtprot   (NMIPConfigSource source) _nm_const;
+NMIPConfigSource nmp_utils_ip_config_source_coerce_from_rtprot (NMIPConfigSource source) _nm_const;
+NMIPConfigSource nmp_utils_ip_config_source_round_trip_rtprot  (NMIPConfigSource source) _nm_const;
+const char *     nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize len);
 
 #endif /* __NM_PLATFORM_UTILS_H__ */
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 26ac766d..9dace699 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -205,6 +205,7 @@ NM_UTILS_LOOKUP_STR_DEFINE (_nm_platform_error_to_string, NMPlatformError,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_WRONG_TYPE,  "wrong-type"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_SLAVE,   "not-slave"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NO_FIRMWARE, "no-firmware"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_OPNOTSUPP,   "not-supported"),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_PLATFORM_ERROR_MININT),
 );
 
@@ -387,6 +388,20 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *path, guint ba
 
 /******************************************************************/
 
+static int
+_link_get_all_presort (gconstpointer  p_a,
+                       gconstpointer  p_b)
+{
+	const NMPlatformLink *a = p_a;
+	const NMPlatformLink *b = p_b;
+
+	if (a->ifindex < b->ifindex)
+		return -1;
+	if (a->ifindex > b->ifindex)
+		return 1;
+	return 0;
+}
+
 /**
  * nm_platform_link_get_all:
  * self: platform instance
@@ -409,15 +424,17 @@ nm_platform_link_get_all (NMPlatform *self)
 	if (!links || links->len == 0)
 		return links;
 
+	/* first sort the links by their ifindex. Below we will sort further by moving
+	 * children/slaves to the end. */
+	g_array_sort (links, _link_get_all_presort);
+
 	unseen = g_hash_table_new (g_direct_hash, g_direct_equal);
 	for (i = 0; i < links->len; i++) {
 		item = &g_array_index (links, NMPlatformLink, i);
 
-		_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)));
-
-		g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL);
+		nm_assert (item->ifindex > 0);
+		if (!nm_g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
+			nm_assert_not_reached ();
 	}
 
 #ifndef G_DISABLE_ASSERT
@@ -468,8 +485,6 @@ 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, NULL, 0));
-
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
 			item->ifindex = 0;
@@ -481,8 +496,6 @@ 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, NULL, 0));
-
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
 			item->ifindex = 0;
@@ -888,33 +901,25 @@ nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
 }
 
 /**
- * nm_platform_link_get_ipv6_token:
+ * nm_platform_link_set_ipv6_token:
  * @self: platform instance
  * @ifindex: Interface index
  * @iid: Tokenized interface identifier
  *
- * Returns IPv6 tokenized interface identifier. If the platform or OS doesn't
- * support IPv6 tokenized interface identifiers, or the token is not set
- * this call will fail and return %FALSE.
+ * Sets then IPv6 tokenized interface identifier.
  *
  * Returns: %TRUE a tokenized identifier was available
  */
 gboolean
-nm_platform_link_get_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId *iid)
+nm_platform_link_set_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);
+	g_return_val_if_fail (iid.id, FALSE);
 
-
-	pllink = nm_platform_link_get (self, ifindex);
-	if (pllink && pllink->inet6_token.is_valid) {
-		*iid = pllink->inet6_token.iid;
-		return TRUE;
-	}
+	if (klass->link_set_token)
+		return klass->link_set_token (self, ifindex, iid);
 	return FALSE;
 }
 
@@ -977,18 +982,16 @@ nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
  * platform or OS doesn't support changing the IPv6LL address mode, this call
  * will fail and return %FALSE.
  *
- * Returns: %TRUE if the operation was successful, %FALSE if it failed.
+ * Returns: %NM_PLATFORM_ERROR_SUCCESS if the operation was successful or an error code otherwise.
  */
-gboolean
+NMPlatformError
 nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
 
-	if (klass->link_set_user_ipv6ll_enabled)
-		return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
-	return FALSE;
+	return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
 }
 
 /**
@@ -999,16 +1002,18 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
  *
  * Set interface MAC address.
  */
-gboolean
+NMPlatformError
 nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
-	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 (ifindex > 0, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (length > 0, NM_PLATFORM_ERROR_BUG);
 
-	_LOGD ("link: setting '%s' (%d) hardware address", nm_platform_link_get_name (self, ifindex), ifindex);
+	_LOGD ("link: setting %s (%d) hardware address",
+	       nm_strquote_a (20, nm_platform_link_get_name (self, ifindex)),
+	       ifindex);
 	return klass->link_set_address (self, ifindex, address, length);
 }
 
@@ -1895,21 +1900,27 @@ _infiniband_add_add_or_delete (NMPlatform *self,
                                gboolean add,
                                const NMPlatformLink **out_link)
 {
-	gs_free char *parent_name = NULL;
-	gs_free char *name = NULL;
+	char name[IFNAMSIZ];
+	const NMPlatformLink *parent_link;
 	NMPlatformError plerr;
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	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 (p_key >= 0 && p_key <= 0xffff, NM_PLATFORM_ERROR_BUG);
+
+	/* the special keys 0x0000 and 0x8000 are not allowed. */
+	if (NM_IN_SET (p_key, 0, 0x8000))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
 
-	parent_name = g_strdup (nm_platform_link_get_name (self, parent));
-	if (   !parent_name
-	    || nm_platform_link_get_type (self, parent) != NM_LINK_TYPE_INFINIBAND)
+	parent_link = nm_platform_link_get (self, parent);
+	if (!parent_link)
+		return NM_PLATFORM_ERROR_NOT_FOUND;
+
+	if (parent_link->type != NM_LINK_TYPE_INFINIBAND)
 		return NM_PLATFORM_ERROR_WRONG_TYPE;
 
-	name = g_strdup_printf ("%s.%04x", parent_name, p_key);
+	nm_utils_new_infiniband_name (name, parent_link->name, p_key);
 
 	if (add) {
 		plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_INFINIBAND, out_link);
@@ -1917,7 +1928,7 @@ _infiniband_add_add_or_delete (NMPlatform *self,
 			return plerr;
 
 		_LOGD ("link: adding infiniband partition %s for parent '%s' (%d), key %d",
-		       name, parent_name, parent, p_key);
+		       name, parent_link->name, parent, p_key);
 		if (!klass->infiniband_partition_add (self, parent, p_key, out_link))
 			return NM_PLATFORM_ERROR_UNSPECIFIED;
 	} else {
@@ -2894,7 +2905,7 @@ nm_platform_ip4_route_add (NMPlatform *self,
 		NMPlatformIP4Route route = { 0 };
 
 		route.ifindex = ifindex;
-		route.source = source;
+		route.rt_source = source;
 		route.network = network;
 		route.plen = plen;
 		route.gateway = gateway;
@@ -2921,7 +2932,7 @@ nm_platform_ip6_route_add (NMPlatform *self,
 		NMPlatformIP6Route route = { 0 };
 
 		route.ifindex = ifindex;
-		route.source = source;
+		route.rt_source = source;
 		route.network = network;
 		route.plen = plen;
 		route.gateway = gateway;
@@ -3012,38 +3023,6 @@ nm_platform_vlan_qos_mapping_to_string (const char *name,
 }
 
 static const char *
-source_to_string (NMIPConfigSource source)
-{
-	switch (source) {
-	case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
-		return "rtprot-kernel";
-	case _NM_IP_CONFIG_SOURCE_RTM_F_CLONED:
-		return "rtm-f-cloned";
-	case NM_IP_CONFIG_SOURCE_KERNEL:
-		return "kernel";
-	case NM_IP_CONFIG_SOURCE_SHARED:
-		return "shared";
-	case NM_IP_CONFIG_SOURCE_IP4LL:
-		return "ipv4ll";
-	case NM_IP_CONFIG_SOURCE_PPP:
-		return "ppp";
-	case NM_IP_CONFIG_SOURCE_WWAN:
-		return "wwan";
-	case NM_IP_CONFIG_SOURCE_VPN:
-		return "vpn";
-	case NM_IP_CONFIG_SOURCE_DHCP:
-		return "dhcp";
-	case NM_IP_CONFIG_SOURCE_RDISC:
-		return "rdisc";
-	case NM_IP_CONFIG_SOURCE_USER:
-		return "user";
-	default:
-		break;
-	}
-	return "unknown";
-}
-
-static const char *
 _lifetime_to_string (guint32 timestamp, guint32 lifetime, gint32 now, char *buf, size_t buf_size)
 {
 	if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT)
@@ -3081,7 +3060,7 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	GString *str_flags;
 	char str_addrmode[30];
 	gs_free char *str_addr = NULL;
-	gs_free char *str_inet6_token = NULL;
+	char str_inet6_token[NM_UTILS_INET_ADDRSTRLEN];
 	const char *str_link_type;
 
 	if (!nm_utils_to_string_buffer_init_null (link, &buf, &len))
@@ -3118,8 +3097,6 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 
 	if (link->addr.len)
 		str_addr = nm_utils_hwaddr_ntoa (link->addr.data, MIN (link->addr.len, sizeof (link->addr.data)));
-	if (link->inet6_token.is_valid)
-		str_inet6_token = nm_utils_hwaddr_ntoa (&link->inet6_token.iid, sizeof (link->inet6_token.iid));
 
 	str_link_type = nm_link_type_to_string (link->type);
 
@@ -3138,6 +3115,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            "%s%s" /* addr */
 	            "%s%s" /* inet6_token */
 	            "%s%s" /* driver */
+	            " rx:%"G_GUINT64_FORMAT",%"G_GUINT64_FORMAT
+	            " tx:%"G_GUINT64_FORMAT",%"G_GUINT64_FORMAT
 	            ,
 	            link->ifindex,
 	            link->name,
@@ -3153,10 +3132,12 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            link->inet6_addr_gen_mode_inv ? nm_platform_link_inet6_addrgenmode2str (_nm_platform_uint8_inv (link->inet6_addr_gen_mode_inv), str_addrmode, sizeof (str_addrmode)) : "",
 	            str_addr ? " addr " : "",
 	            str_addr ? str_addr : "",
-	            str_inet6_token ? " inet6token " : "",
-	            str_inet6_token ? str_inet6_token : "",
+	            link->inet6_token.id ? " inet6token " : "",
+	            link->inet6_token.id ? nm_utils_inet6_interface_identifier_to_token (link->inet6_token, str_inet6_token) : "",
 	            link->driver ? " driver " : "",
-	            link->driver ? link->driver : "");
+	            link->driver ? link->driver : "",
+	            link->rx_packets, link->rx_bytes,
+	            link->tx_packets, link->tx_bytes);
 	g_string_free (str_flags, TRUE);
 	return buf;
 }
@@ -3471,7 +3452,7 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	char s_peer[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 	char str_label[32];
-	char str_lft[30], str_pref[30], str_time[50];
+	char str_lft[30], str_pref[30], str_time[50], s_source[50];
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
@@ -3510,7 +3491,7 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	            str_dev,
 	            _to_string_ifa_flags (address->n_ifa_flags, s_flags, sizeof (s_flags)),
 	            str_label,
-	            source_to_string (address->source));
+	            nmp_utils_ip_config_source_to_string (address->addr_source, s_source, sizeof (s_source)));
 	g_free (str_peer);
 	return buf;
 }
@@ -3582,7 +3563,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
 	char s_address[INET6_ADDRSTRLEN];
 	char s_peer[INET6_ADDRSTRLEN];
-	char str_lft[30], str_pref[30], str_time[50];
+	char str_lft[30], str_pref[30], str_time[50], s_source[50];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
@@ -3616,7 +3597,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	            str_peer ? str_peer : "",
 	            str_dev,
 	            _to_string_ifa_flags (address->n_ifa_flags, s_flags, sizeof (s_flags)),
-	            source_to_string (address->source));
+	            nmp_utils_ip_config_source_to_string (address->addr_source, s_source, sizeof (s_source)));
 	g_free (str_peer);
 	return buf;
 }
@@ -3639,7 +3620,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	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];
+	char str_scope[30], s_source[50];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
 		return buf;
@@ -3656,6 +3637,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            " metric %"G_GUINT32_FORMAT
 	            " mss %"G_GUINT32_FORMAT
 	            " src %s" /* source */
+	            "%s" /* cloned */
 	            "%s%s" /* scope */
 	            "%s%s" /* pref-src */
 	            "",
@@ -3665,7 +3647,8 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            str_dev,
 	            route->metric,
 	            route->mss,
-	            source_to_string (route->source),
+	            nmp_utils_ip_config_source_to_string (route->rt_source, s_source, sizeof (s_source)),
+	            route->rt_cloned ? " cloned" : "",
 	            route->scope_inv ? " 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 " : "",
@@ -3689,7 +3672,7 @@ const char *
 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];
+	char str_dev[TO_STRING_DEV_BUF_SIZE], s_source[50];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
 		return buf;
@@ -3706,6 +3689,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            " metric %"G_GUINT32_FORMAT
 	            " mss %"G_GUINT32_FORMAT
 	            " src %s" /* source */
+	            "%s" /* cloned */
 	            "",
 	            s_network,
 	            route->plen,
@@ -3713,7 +3697,8 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            str_dev,
 	            route->metric,
 	            route->mss,
-	            source_to_string (route->source));
+	            nmp_utils_ip_config_source_to_string (route->rt_source, s_source, sizeof (s_source)),
+	            route->rt_cloned ? " cloned" : "");
 	return buf;
 }
 
@@ -3808,13 +3793,15 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 	_CMP_FIELD (a, b, arptype);
 	_CMP_FIELD (a, b, addr.len);
 	_CMP_FIELD (a, b, inet6_addr_gen_mode_inv);
-	_CMP_FIELD (a, b, inet6_token.is_valid);
 	_CMP_FIELD_STR_INTERNED (a, b, kind);
 	_CMP_FIELD_STR_INTERNED (a, b, driver);
 	if (a->addr.len)
 		_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
-	if (a->inet6_token.is_valid)
-		_CMP_FIELD_MEMCMP (a, b, inet6_token.iid);
+	_CMP_FIELD_MEMCMP (a, b, inet6_token);
+	_CMP_FIELD (a, b, rx_packets);
+	_CMP_FIELD (a, b, rx_bytes);
+	_CMP_FIELD (a, b, tx_packets);
+	_CMP_FIELD (a, b, tx_bytes);
 	return 0;
 }
 
@@ -3935,10 +3922,10 @@ nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4A
 {
 	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD (a, b, source);
 	_CMP_FIELD (a, b, address);
 	_CMP_FIELD (a, b, plen);
 	_CMP_FIELD (a, b, peer_address);
+	_CMP_FIELD (a, b, addr_source);
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
 	_CMP_FIELD (a, b, preferred);
@@ -3954,14 +3941,12 @@ nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6A
 
 	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD (a, b, source);
 	_CMP_FIELD_MEMCMP (a, b, address);
-
+	_CMP_FIELD (a, b, plen);
 	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, addr_source);
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
 	_CMP_FIELD (a, b, preferred);
@@ -3974,14 +3959,15 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 {
 	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD (a, b, source);
 	_CMP_FIELD (a, b, network);
 	_CMP_FIELD (a, b, plen);
-	_CMP_FIELD (a, b, gateway);
 	_CMP_FIELD (a, b, metric);
+	_CMP_FIELD (a, b, gateway);
+	_CMP_FIELD (a, b, rt_source);
 	_CMP_FIELD (a, b, mss);
 	_CMP_FIELD (a, b, scope_inv);
 	_CMP_FIELD (a, b, pref_src);
+	_CMP_FIELD (a, b, rt_cloned);
 	return 0;
 }
 
@@ -3990,12 +3976,13 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 {
 	_CMP_SELF (a, b);
 	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD (a, b, source);
 	_CMP_FIELD_MEMCMP (a, b, network);
 	_CMP_FIELD (a, b, plen);
-	_CMP_FIELD_MEMCMP (a, b, gateway);
 	_CMP_FIELD (a, b, metric);
+	_CMP_FIELD_MEMCMP (a, b, gateway);
+	_CMP_FIELD (a, b, rt_source);
 	_CMP_FIELD (a, b, mss);
+	_CMP_FIELD (a, b, rt_cloned);
 	return 0;
 }
 
@@ -4128,7 +4115,7 @@ _vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *rout
 {
 	return nm_platform_ip4_route_add (self,
 	                                  ifindex > 0 ? ifindex : route->rx.ifindex,
-	                                  route->rx.source,
+	                                  route->rx.rt_source,
 	                                  route->r4.network,
 	                                  route->rx.plen,
 	                                  route->r4.gateway,
@@ -4142,7 +4129,7 @@ _vtr_v6_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *rout
 {
 	return nm_platform_ip6_route_add (self,
 	                                  ifindex > 0 ? ifindex : route->rx.ifindex,
-	                                  route->rx.source,
+	                                  route->rx.rt_source,
 	                                  route->r6.network,
 	                                  route->rx.plen,
 	                                  route->r6.gateway,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 19724f1c..fc6965e9 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -49,8 +49,6 @@
 
 /******************************************************************/
 
-typedef struct _NMPlatform NMPlatform;
-
 /* workaround for older libnl version, that does not define these flags. */
 #ifndef IFA_F_MANAGETEMPADDR
 #define IFA_F_MANAGETEMPADDR 0x100
@@ -90,6 +88,7 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_ERROR_WRONG_TYPE,
 	NM_PLATFORM_ERROR_NOT_SLAVE,
 	NM_PLATFORM_ERROR_NO_FIRMWARE,
+	NM_PLATFORM_ERROR_OPNOTSUPP,
 } NMPlatformError;
 
 
@@ -98,6 +97,11 @@ typedef struct {
 		guint8 addr_ptr[1];
 		in_addr_t addr4;
 		struct in6_addr addr6;
+
+		/* NMIPAddr is really a union for IP addresses.
+		 * However, as ethernet addresses fit in here nicely, ruse
+		 * it also for an ethernet MAC address. */
+		guint8 addr_eth[6 /*ETH_ALEN*/];
 	};
 } NMIPAddr;
 
@@ -144,17 +148,20 @@ struct _NMPlatformLink {
 		guint8 len;
 	} addr;
 
-	/* rtnl_link_inet6_get_token() */
-	struct {
-		NMUtilsIPv6IfaceId iid;
-		guint8 is_valid;
-	} inet6_token;
+	/* rtnl_link_inet6_get_token(), IFLA_INET6_TOKEN */
+	NMUtilsIPv6IfaceId inet6_token;
 
 	/* The bitwise inverse of rtnl_link_inet6_get_addr_gen_mode(). It is inverse
 	 * to have a default of 0 -- meaning: unspecified. That way, a struct
 	 * initialized with memset(0) has and unset value.*/
 	guint8 inet6_addr_gen_mode_inv;
 
+	/* Statistics */
+	guint64 rx_packets;
+	guint64 rx_bytes;
+	guint64 tx_packets;
+	guint64 tx_bytes;
+
 	/* @connected is mostly identical to (@n_ifi_flags & IFF_UP). Except for bridge/bond masters,
 	 * where we coerce the link as disconnect if it has no slaves. */
 	bool connected:1;
@@ -200,7 +207,7 @@ typedef struct {
 
 #define __NMPlatformIPAddress_COMMON \
 	__NMPlatformObject_COMMON; \
-	NMIPConfigSource source; \
+	NMIPConfigSource addr_source; \
 	\
 	/* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*().
 	 *
@@ -303,8 +310,21 @@ typedef union {
 
 #define __NMPlatformIPRoute_COMMON \
 	__NMPlatformObject_COMMON; \
-	NMIPConfigSource source; \
+	\
+	/* The NMIPConfigSource. For routes that we receive from cache this corresponds
+	 * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values).
+	 * When adding a route, the source will be coerced to the protocol using
+	 * nmp_utils_ip_config_source_coerce_to_rtprot(). */ \
+	NMIPConfigSource rt_source; \
+	\
 	guint8 plen; \
+	\
+	/* the route has rtm_flags set to RTM_F_CLONED. Such a route
+	 * is hidden by platform and does not exist from the point-of-view
+	 * of platform users. This flag is internal to track those hidden
+	 * routes. Such a route is not alive, according to nmp_object_is_alive(). */ \
+	bool rt_cloned:1; \
+	\
 	guint32 metric; \
 	guint32 mss; \
 	;
@@ -506,13 +526,14 @@ typedef struct {
 	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
 	GObject *(*link_get_udev_device) (NMPlatform *self, int ifindex);
 
-	gboolean (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
+	NMPlatformError (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
+	gboolean (*link_set_token) (NMPlatform *, int ifindex, NMUtilsIPv6IfaceId iid);
 
 	gboolean (*link_get_permanent_address) (NMPlatform *,
 	                                        int ifindex,
 	                                        guint8 *buf,
 	                                        size_t *length);
-	gboolean (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
+	NMPlatformError (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
 	gboolean (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
 
 	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
@@ -717,7 +738,6 @@ gboolean nm_platform_link_is_up (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex);
 guint32 nm_platform_link_get_mtu (NMPlatform *self, int ifindex);
-gboolean nm_platform_link_get_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId *iid);
 gboolean nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex);
 gconstpointer nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length);
 int nm_platform_link_get_master (NMPlatform *self, int slave);
@@ -740,10 +760,11 @@ const char *nm_platform_link_get_udi (NMPlatform *self, int ifindex);
 
 GObject *nm_platform_link_get_udev_device (NMPlatform *self, int ifindex);
 
-gboolean nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
+NMPlatformError nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
+gboolean nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
 
 gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length);
-gboolean nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
+NMPlatformError nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
 gboolean nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
 
 char    *nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex);
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index eb7e1ca4..30a52c25 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -47,6 +47,10 @@
         } \
     } G_STMT_END
 
+/* logging to trace object lifetime and references.
+ * Disabled by default. */
+#define _LOGr(...) G_STMT_START { if (FALSE) { _LOGt (__VA_ARGS__); } } G_STMT_END
+
 /*********************************************************************************************/
 
 struct _NMPCache {
@@ -211,7 +215,7 @@ nmp_object_ref (NMPObject *obj)
 	g_return_val_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT, NULL);
 	obj->_ref_count++;
 
-	_LOGt (obj, "ref: %d", obj->_ref_count);
+	_LOGr (obj, "ref: %d", obj->_ref_count);
 
 	return obj;
 }
@@ -222,7 +226,7 @@ nmp_object_unref (NMPObject *obj)
 	if (obj) {
 		g_return_if_fail (obj->_ref_count > 0);
 		g_return_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT);
-		_LOGt (obj, "%s: %d",
+		_LOGr (obj, "%s: %d",
 		       obj->_ref_count <= 1 ? "destroy" : "unref",
 		       obj->_ref_count - 1);
 		if (--obj->_ref_count <= 0) {
@@ -262,7 +266,7 @@ _nmp_object_new_from_class (const NMPClass *klass)
 	obj = g_slice_alloc0 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object));
 	obj->_class = klass;
 	obj->_ref_count = 1;
-	_LOGt (obj, "new");
+	_LOGr (obj, "new");
 	return obj;
 }
 
@@ -915,12 +919,12 @@ _vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
 	 *
 	 * If nmp_object_from_nl() would just return NULL, we couldn't look
 	 * into the cache to see if it contains a route that now disappears
-	 * (because it is cloned).
+	 * (because it changed to be cloned).
 	 *
 	 * Instead we create a dead object, and nmp_cache_update_netlink()
 	 * will remove the old version of the update.
 	 **/
-	return obj->object.ifindex > 0 && (obj->ip_route.source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED);
+	return obj->object.ifindex > 0 && !obj->ip_route.rt_cloned;
 }
 
 gboolean
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index c5241037..d295f7a0 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -94,9 +94,9 @@ typedef enum { /*< skip >*/
 	NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1,
 } NMPCacheIdType;
 
-typedef struct _NMPObject NMPObject;
+typedef struct _NMPCacheId NMPCacheId;
 
-typedef struct {
+struct _NMPCacheId {
 	union {
 		NMMultiIndexId base;
 		guint8 _id_type; /* NMPCacheIdType as guint8 */
@@ -136,7 +136,7 @@ typedef struct {
 			struct in6_addr _misaligned_network;
 		} routes_by_destination_ip6;
 	};
-} NMPCacheId;
+};
 
 extern NMPCacheId _nmp_cache_id_static;
 #define NMP_CACHE_ID_STATIC  (&_nmp_cache_id_static)
diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am
index 5af4ce91..0633dd28 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -28,7 +28,7 @@ TEST_SOURCES = \
 AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
 AM_LDFLAGS = $(GLIB_LIBS) $(GUDEV_LIBS) $(LIBNL_LIBS) $(CODE_COVERAGE_LDFLAGS)
 PLATFORM_LDADD = \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 
 @GNOME_CODE_COVERAGE_RULES@
 
@@ -109,12 +109,12 @@ test_cleanup_linux_LDADD = $(PLATFORM_LDADD)
 test_nmp_object_SOURCES = \
 	test-nmp-object.c
 test_nmp_object_LDADD = \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 
 test_general_SOURCES = \
 	test-general.c
 test_general_LDADD = \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 
 
 @VALGRIND_RULES@
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index 49f2fa09..4922a3dc 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -102,8 +102,9 @@ TESTS = test-address-fake$(EXEEXT) test-address-linux$(EXEEXT) \
 	test-route-fake$(EXEEXT) test-route-linux$(EXEEXT)
 subdir = src/platform/tests
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -170,7 +171,8 @@ test_cleanup_linux_OBJECTS = $(am_test_cleanup_linux_OBJECTS)
 test_cleanup_linux_DEPENDENCIES = $(PLATFORM_LDADD)
 am_test_general_OBJECTS = test-general.$(OBJEXT)
 test_general_OBJECTS = $(am_test_general_OBJECTS)
-test_general_DEPENDENCIES = $(top_builddir)/src/libNetworkManager.la
+test_general_DEPENDENCIES =  \
+	$(top_builddir)/src/libNetworkManagerTest.la
 am__objects_10 = test_link_fake-nm-platform.$(OBJEXT) \
 	test_link_fake-nm-fake-platform.$(OBJEXT) \
 	test_link_fake-nm-linux-platform.$(OBJEXT)
@@ -192,7 +194,7 @@ test_link_linux_DEPENDENCIES = $(PLATFORM_LDADD)
 am_test_nmp_object_OBJECTS = test-nmp-object.$(OBJEXT)
 test_nmp_object_OBJECTS = $(am_test_nmp_object_OBJECTS)
 test_nmp_object_DEPENDENCIES =  \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 am__objects_14 = test_route_fake-nm-platform.$(OBJEXT) \
 	test_route_fake-nm-fake-platform.$(OBJEXT) \
 	test_route_fake-nm-linux-platform.$(OBJEXT)
@@ -522,6 +524,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -578,13 +581,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -596,6 +600,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -656,6 +662,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -691,6 +699,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -772,7 +781,7 @@ TEST_SOURCES = \
 AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
 AM_LDFLAGS = $(GLIB_LIBS) $(GUDEV_LIBS) $(LIBNL_LIBS) $(CODE_COVERAGE_LDFLAGS)
 PLATFORM_LDADD = \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 
 EXTRA_DIST = test-common.h
 monitor_SOURCES = monitor.c $(PLATFORM_SOURCES)
@@ -837,13 +846,13 @@ test_nmp_object_SOURCES = \
 	test-nmp-object.c
 
 test_nmp_object_LDADD = \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 
 test_general_SOURCES = \
 	test-general.c
 
 test_general_LDADD = \
-	$(top_builddir)/src/libNetworkManager.la
+	$(top_builddir)/src/libNetworkManagerTest.la
 
 all: all-am
 
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index 3af34be4..937eea11 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -25,7 +25,7 @@
 
 #include "nm-linux-platform.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 NMTST_DEFINE ();
 
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 0e3cf10b..87fccee2 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -8,7 +8,7 @@
 #include "nm-fake-platform.h"
 #include "nm-linux-platform.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 #define DEVICE_NAME "nm-test-device"
 
@@ -84,7 +84,7 @@ void link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, N
 
 /*****************************************************************************/
 
-int nmtstp_run_command (const char *format, ...) __attribute__((__format__ (__printf__, 1, 2)));
+int nmtstp_run_command (const char *format, ...) _nm_printf (1, 2);
 #define nmtstp_run_command_check(...) do { g_assert_cmpint (nmtstp_run_command (__VA_ARGS__), ==, 0); } while (0)
 
 /*****************************************************************************/
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index d8d92561..f67f0089 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -25,7 +25,7 @@
 #include "nm-platform-utils.h"
 #include "nm-linux-platform.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 
 /******************************************************************/
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 9d548aab..99873620 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -30,7 +30,7 @@
 #include "nm-platform-utils.h"
 
 #include "test-common.h"
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 #define LO_INDEX 1
 #define LO_NAME "lo"
@@ -60,16 +60,12 @@ test_bogus(void)
 	g_assert (!nm_platform_link_get_type (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_get_type_name (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_up (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_down (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_noarp (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -80,7 +76,6 @@ test_bogus(void)
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
 
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -226,7 +221,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	g_assert_cmpint (nm_platform_link_get_master (NM_PLATFORM_GET, ifindex), ==, master);
 
 	accept_signals (link_changed, 1, 3);
-	accept_signals (master_changed, 0, 1);
+	accept_signals (master_changed, 0, 2);
 
 	/* enslaveing brings put the slave */
 	if (NM_IN_SET (link_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM))
@@ -515,13 +510,13 @@ test_bridge_addr (void)
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
-		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE));
+		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE) == NM_PLATFORM_ERROR_SUCCESS);
 		g_assert (nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 		g_assert (plink);
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_NONE);
 
-		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE));
+		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE) == NM_PLATFORM_ERROR_SUCCESS);
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 		g_assert (plink);
@@ -572,7 +567,7 @@ test_internal (void)
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL));
 	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
-	accept_signal (link_changed);
+	accept_signals (link_changed, 1, 2);
 	g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
@@ -592,7 +587,7 @@ test_internal (void)
 	g_assert (nm_platform_link_supports_vlans (NM_PLATFORM_GET, ifindex));
 
 	/* Set MAC address */
-	g_assert (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac)));
+	g_assert (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac)) == NM_PLATFORM_ERROR_SUCCESS);
 	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &addrlen);
 	g_assert (addrlen == sizeof(mac));
 	g_assert (!memcmp (address, mac, addrlen));
@@ -778,11 +773,32 @@ test_software_detect (gconstpointer user_data)
 	}
 	case NM_LINK_TYPE_MACVLAN: {
 		NMPlatformLnkMacvlan lnk_macvlan = { };
+		const NMPlatformLink *dummy;
+		char buf[256];
+		int i;
 
 		lnk_macvlan.mode = MACVLAN_MODE_BRIDGE;
 		lnk_macvlan.no_promisc = FALSE;
 		lnk_macvlan.tap = FALSE;
 
+		/* Since in old kernel versions sysfs files for macvtaps are not
+		 * namespaced, the creation can fail if a macvtap in another namespace
+		 * has the same index. Try to detect this situation and skip already
+		 * used indexes.
+		 * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=17af2bce88d31e65ed73d638bb752d2e13c66ced
+		 */
+		for (i = ifindex_parent + 1; i < ifindex_parent + 100; i++) {
+			snprintf (buf, sizeof (buf), "/sys/class/macvtap/tap%d", i);
+			if (!g_file_test (buf, G_FILE_TEST_IS_SYMLINK))
+				break;
+
+			_LOGD ("skipping ifindex %d as already used by a macvtap", i);
+
+			dummy = nmtstp_link_dummy_add (NM_PLATFORM_GET, FALSE, "dummy-tmp");
+			g_assert_cmpint (dummy->ifindex, ==, i);
+			nmtstp_link_del (NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL);
+		}
+
 		if (!nmtstp_link_macvlan_add (NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan))
 			g_error ("Failed adding MACVLAN interface");
 		break;
@@ -1861,7 +1877,7 @@ _test_netns_setup (gpointer fixture, gconstpointer test_data)
 {
 	/* the singleton platform instance has netns support disabled.
 	 * Destroy the instance before the test and re-create it afterwards. */
-	g_object_unref (nm_platform_get ());
+	g_object_unref (NM_PLATFORM_GET);
 }
 
 static void
@@ -1919,6 +1935,14 @@ _test_netns_check_skip (void)
 
 /******************************************************************/
 
+#define _sysctl_assert_eq(plat, path, value) \
+	G_STMT_START { \
+		gs_free char *_val = NULL; \
+		\
+		_val = nm_platform_sysctl_get (plat, path); \
+		g_assert_cmpstr (_val, ==, value); \
+	} G_STMT_END
+
 static void
 test_netns_general (gpointer fixture, gconstpointer test_data)
 {
@@ -1954,13 +1978,19 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i));
 	}
 
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex));
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, NULL);
+	_sysctl_assert_eq (platform_1,"/sys/devices/virtual/net/dummy1_/ifindex",
+	                   nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
+	_sysctl_assert_eq (platform_1, "/sys/devices/virtual/net/dummy2a/ifindex",
+	                   nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex));
+	_sysctl_assert_eq (platform_1, "/sys/devices/virtual/net/dummy2b/ifindex",
+	                   NULL);
 
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, NULL);
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex));
+	_sysctl_assert_eq (platform_2, "/sys/devices/virtual/net/dummy1_/ifindex",
+	                   nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
+	_sysctl_assert_eq (platform_2, "/sys/devices/virtual/net/dummy2a/ifindex",
+	                   NULL);
+	_sysctl_assert_eq (platform_2, "/sys/devices/virtual/net/dummy2b/ifindex",
+	                   nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex));
 
 	for (i = 0; i < 10; i++) {
 		NMPlatform *pl;
@@ -1982,10 +2012,11 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 				path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6";
 		}
 		g_assert (nm_platform_sysctl_set (pl, path, nm_sprintf_buf (sbuf, "%d", j)));
-		g_assert_cmpstr (nm_platform_sysctl_get (pl, path), ==, nm_sprintf_buf (sbuf, "%d", j));
+		_sysctl_assert_eq (pl, path, nm_sprintf_buf (sbuf, "%d", j));
 	}
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"), ==, NULL);
-	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"), ==, NULL);
+
+	_sysctl_assert_eq (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6", NULL);
+	_sysctl_assert_eq (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6", NULL);
 
 	/* older kernels (Ubuntu 12.04) don't support ethtool -i for dummy devices. Work around that and
 	 * skip asserts that are known to fail. */
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index d77170b3..9d4f69de 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -22,7 +22,7 @@
 
 #include "nmp-object.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 struct {
 	GList *udev_devices;
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index a50392a6..360404e9 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -23,9 +23,9 @@
 #include <linux/rtnetlink.h>
 
 #include "nm-core-utils.h"
-#include "test-common.h"
+#include "nm-platform-utils.h"
 
-#include "nm-test-utils.h"
+#include "test-common.h"
 
 #define DEVICE_NAME "nm-test-device"
 
@@ -178,7 +178,7 @@ test_ip4_route (void)
 	/* Test route listing */
 	routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 	memset (rts, 0, sizeof (rts));
-	rts[0].source = NM_IP_CONFIG_SOURCE_USER;
+	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].network = gateway;
 	rts[0].plen = 32;
 	rts[0].ifindex = ifindex;
@@ -186,7 +186,7 @@ test_ip4_route (void)
 	rts[0].metric = metric;
 	rts[0].mss = mss;
 	rts[0].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK);
-	rts[1].source = NM_IP_CONFIG_SOURCE_USER;
+	rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[1].network = network;
 	rts[1].plen = plen;
 	rts[1].ifindex = ifindex;
@@ -194,7 +194,7 @@ test_ip4_route (void)
 	rts[1].metric = metric;
 	rts[1].mss = mss;
 	rts[1].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE);
-	rts[2].source = NM_IP_CONFIG_SOURCE_USER;
+	rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[2].network = 0;
 	rts[2].plen = 0;
 	rts[2].ifindex = ifindex;
@@ -265,21 +265,21 @@ test_ip6_route (void)
 	/* Test route listing */
 	routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
 	memset (rts, 0, sizeof (rts));
-	rts[0].source = NM_IP_CONFIG_SOURCE_USER;
+	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].network = gateway;
 	rts[0].plen = 128;
 	rts[0].ifindex = ifindex;
 	rts[0].gateway = in6addr_any;
 	rts[0].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[0].mss = mss;
-	rts[1].source = NM_IP_CONFIG_SOURCE_USER;
+	rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[1].network = network;
 	rts[1].plen = plen;
 	rts[1].ifindex = ifindex;
 	rts[1].gateway = gateway;
 	rts[1].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[1].mss = mss;
-	rts[2].source = NM_IP_CONFIG_SOURCE_USER;
+	rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[2].network = in6addr_any;
 	rts[2].plen = 0;
 	rts[2].ifindex = ifindex;
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/wifi-utils-private.h
index 21a4c57b..ebe76f1b 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/wifi-utils-private.h
@@ -21,8 +21,6 @@
 #ifndef __WIFI_UTILS_PRIVATE_H__
 #define __WIFI_UTILS_PRIVATE_H__
 
-
-#include "nm-default.h"
 #include "nm-dbus-interface.h"
 #include "wifi-utils.h"
 
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
index 4f04041a..b7fe86bb 100644
--- a/src/platform/wifi/wifi-utils.c
+++ b/src/platform/wifi/wifi-utils.c
@@ -21,16 +21,18 @@
 
 #include "nm-default.h"
 
+#include "wifi-utils.h"
+
 #include <sys/stat.h>
 #include <stdio.h>
 #include <string.h>
 
-#include "wifi-utils.h"
 #include "wifi-utils-private.h"
 #include "wifi-utils-nl80211.h"
 #if HAVE_WEXT
 #include "wifi-utils-wext.h"
 #endif
+#include "nm-core-utils.h"
 
 gpointer
 wifi_data_new (const char *iface, int ifindex, gsize len)
@@ -178,19 +180,20 @@ wifi_utils_deinit (WifiData *data)
 }
 
 gboolean
-wifi_utils_is_wifi (const char *iface, const char *sysfs_path)
+wifi_utils_is_wifi (const char *iface)
 {
-	char phy80211_path[255];
+	char phy80211_path[NM_STRLEN ("/sys/class/net/123456789012345/phy80211\0") + 100 /*safety*/];
 	struct stat s;
 
 	g_return_val_if_fail (iface != NULL, FALSE);
 
-	if (sysfs_path) {
-		/* Check for nl80211 sysfs paths */
-		g_snprintf (phy80211_path, sizeof (phy80211_path), "%s/phy80211", sysfs_path);
-		if ((stat (phy80211_path, &s) == 0 && (s.st_mode & S_IFDIR)))
-			return TRUE;
-	}
+	nm_sprintf_buf (phy80211_path,
+	                "/sys/class/net/%s/phy80211",
+	                NM_ASSERT_VALID_PATH_COMPONENT (iface));
+	nm_assert (strlen (phy80211_path) < sizeof (phy80211_path) - 1);
+
+	if ((stat (phy80211_path, &s) == 0 && (s.st_mode & S_IFDIR)))
+		return TRUE;
 
 #if HAVE_WEXT
 	if (wifi_wext_is_wifi (iface))
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index 9555bbee..8e2b93f1 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -24,12 +24,11 @@
 
 #include <net/ethernet.h>
 
-#include "nm-default.h"
 #include "nm-dbus-interface.h"
 
 typedef struct WifiData WifiData;
 
-gboolean wifi_utils_is_wifi (const char *iface, const char *sysfs_path);
+gboolean wifi_utils_is_wifi (const char *iface);
 
 WifiData *wifi_utils_init (const char *iface, int ifindex, gboolean check_scan);