about summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-05-05 17:48:57 +0200
committerMichael Biebl <biebl@debian.org>2015-05-05 17:48:57 +0200
commitf408e27bccfacf347605a8d98649975a68f38a17 (patch)
tree654fd6695c31511baf919b1c0870d119a352ed75 /src/platform
parent2c032d8f1c6292c1338a615e6ec40252889ba85c (diff)
Imported Upstream version 1.0.2 upstream/1.0.2
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/Makefile.in24
-rw-r--r--src/platform/nm-fake-platform.c18
-rw-r--r--src/platform/nm-linux-platform.c150
-rw-r--r--src/platform/nm-platform.c166
-rw-r--r--src/platform/nm-platform.h30
-rw-r--r--src/platform/tests/Makefile.am2
-rw-r--r--src/platform/tests/Makefile.in32
-rw-r--r--src/platform/tests/test-address.c6
-rw-r--r--src/platform/tests/test-cleanup.c6
-rw-r--r--src/platform/tests/test-common.c115
-rw-r--r--src/platform/tests/test-common.h8
-rw-r--r--src/platform/tests/test-link.c8
-rw-r--r--src/platform/tests/test-route.c97
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c11
-rw-r--r--src/platform/wifi/wifi-utils-wext.c15
-rw-r--r--src/platform/wifi/wifi-utils.h3
16 files changed, 632 insertions, 59 deletions
diff --git a/src/platform/Makefile.in b/src/platform/Makefile.in
index d4c8ade7..a41de1ae 100644
--- a/src/platform/Makefile.in
+++ b/src/platform/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.13.4 from Makefile.am.
+# Makefile.in generated by automake 1.15 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -14,7 +14,17 @@
 
 @SET_MAKE@
 VPATH = @srcdir@
-am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
 am__make_running_with_option = \
   case $${target_option-} in \
       ?) ;; \
@@ -78,7 +88,6 @@ POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
 subdir = src/platform
-DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
 	$(top_srcdir)/m4/compiler_warnings.m4 \
@@ -95,6 +104,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
 	$(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
 mkinstalldirs = $(install_sh) -d
 CONFIG_HEADER = $(top_builddir)/config.h
 CONFIG_CLEAN_FILES =
@@ -154,6 +164,7 @@ am__define_uniq_tagged_files = \
 ETAGS = etags
 CTAGS = ctags
 DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 am__relativize = \
   dir0=`pwd`; \
@@ -205,7 +216,6 @@ CXXCPP = @CXXCPP@
 CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
-DATADIRNAME = @DATADIRNAME@
 DBUS_CFLAGS = @DBUS_CFLAGS@
 DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
 DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
@@ -240,6 +250,7 @@ GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
 GNUTLS_LIBS = @GNUTLS_LIBS@
 GREP = @GREP@
 GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
 GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
 GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
 GTKDOC_MKPDF = @GTKDOC_MKPDF@
@@ -456,7 +467,6 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__confi
 	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/platform/Makefile'; \
 	$(am__cd) $(top_srcdir) && \
 	  $(AUTOMAKE) --gnu src/platform/Makefile
-.PRECIOUS: Makefile
 Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
 	@case '$?' in \
 	  *config.status*) \
@@ -751,6 +761,8 @@ uninstall-am:
 	mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
 	ps ps-am tags tags-am uninstall uninstall-am
 
+.PRECIOUS: Makefile
+
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index a5eb7d4e..7b0d2d64 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -456,6 +456,15 @@ link_get_physical_port_id (NMPlatform *platform, int ifindex)
 	return NULL;
 }
 
+static guint
+link_get_dev_id (NMPlatform *platform, int ifindex)
+{
+	/* We call link_get just to cause an error to be set if @ifindex is bad. */
+	link_get (platform, ifindex);
+
+	return 0;
+}
+
 static gboolean
 link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 {
@@ -1076,6 +1085,8 @@ ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 			continue;
 		if (item->plen != route.plen)
 			continue;
+		if (item->metric != metric)
+			continue;
 
 		memcpy (item, &route, sizeof (route));
 		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
@@ -1097,6 +1108,8 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 	NMPlatformIP6Route route;
 	guint i;
 
+	metric = nm_utils_ip6_route_metric_normalize (metric);
+
 	memset (&route, 0, sizeof (route));
 	route.source = NM_IP_CONFIG_SOURCE_KERNEL;
 	route.ifindex = ifindex;
@@ -1116,6 +1129,8 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
 			continue;
 		if (item->plen != route.plen)
 			continue;
+		if (item->metric != metric)
+			continue;
 
 		memcpy (item, &route, sizeof (route));
 		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
@@ -1153,6 +1168,8 @@ ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int p
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	int i;
 
+	metric = nm_utils_ip6_route_metric_normalize (metric);
+
 	for (i = 0; i < priv->ip6_routes->len; i++) {
 		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
 
@@ -1301,6 +1318,7 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->link_set_mtu = link_set_mtu;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
+	platform_class->link_get_dev_id = link_get_dev_id;
 	platform_class->link_get_wake_on_lan = link_get_wake_on_lan;
 
 	platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 5d1522ce..c337ee9c 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -127,7 +127,7 @@ _nl_get_vtable (void)
 	if (G_UNLIKELY (!vtable.f_nl_has_capability)) {
 		void *handle;
 
-		handle = dlopen ("libnl-3.so", RTLD_LAZY | RTLD_NOLOAD);
+		handle = dlopen ("libnl-3.so.200", RTLD_LAZY | RTLD_NOLOAD);
 		if (handle) {
 			vtable.handle = handle;
 			vtable.f_nl_has_capability = dlsym (handle, "nl_has_capability");
@@ -872,16 +872,15 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, const char
 		if (!ifname)
 			return_type (NM_LINK_TYPE_UNKNOWN, type);
 
+		driver = ethtool_get_driver (ifname);
 		if (arptype == 256) {
 			/* Some s390 CTC-type devices report 256 for the encapsulation type
-			 * for some reason, but we need to call them Ethernet. FIXME: use
-			 * something other than interface name to detect CTC here.
+			 * for some reason, but we need to call them Ethernet.
 			 */
-			if (g_str_has_prefix (ifname, "ctc"))
+			if (!g_strcmp0 (driver, "ctcm"))
 				return_type (NM_LINK_TYPE_ETHERNET, "ethernet");
 		}
 
-		driver = ethtool_get_driver (ifname);
 		if (!g_strcmp0 (driver, "openvswitch"))
 			return_type (NM_LINK_TYPE_OPENVSWITCH, "openvswitch");
 
@@ -1008,7 +1007,7 @@ init_link (NMPlatform *platform, NMPlatformLink *info, struct rtnl_link *rtnllin
 	if (udev_device) {
 		info->driver = udev_get_driver (platform, udev_device, info->ifindex);
 		if (!info->driver)
-			info->driver = rtnl_link_get_type (rtnllink);
+			info->driver = g_intern_string (rtnl_link_get_type (rtnllink));
 		if (!info->driver)
 			info->driver = ethtool_get_driver (info->name);
 		if (!info->driver)
@@ -1392,7 +1391,6 @@ static char to_string_buffer[255];
 #define SET_AND_RETURN_STRING_BUFFER(...) \
 	G_STMT_START { \
 		g_snprintf (to_string_buffer, sizeof (to_string_buffer), ## __VA_ARGS__); \
-		g_return_val_if_reached (to_string_buffer); \
 		return to_string_buffer; \
 	} G_STMT_END
 
@@ -1668,6 +1666,9 @@ announce_object (NMPlatform *platform, const struct nl_object *object, NMPlatfor
 		{
 			NMPlatformIP4Route route;
 
+			if (reason == _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)
+				return;
+
 			if (!_route_match ((struct rtnl_route *) object, AF_INET, 0, FALSE)) {
 				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP4 route %s", to_string_ip4_route ((struct rtnl_route *) object));
 				return;
@@ -1680,6 +1681,9 @@ announce_object (NMPlatform *platform, const struct nl_object *object, NMPlatfor
 		{
 			NMPlatformIP6Route route;
 
+			if (reason == _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)
+				return;
+
 			if (!_route_match ((struct rtnl_route *) object, AF_INET6, 0, FALSE)) {
 				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP6 route %s", to_string_ip6_route ((struct rtnl_route *) object));
 				return;
@@ -1719,9 +1723,15 @@ refresh_object (NMPlatform *platform, struct nl_object *object, gboolean removed
 			announce_object (platform, cached_object, NM_PLATFORM_SIGNAL_REMOVED, reason);
 		}
 	} else {
+		ObjectType type;
+
 		if (!kernel_object)
 			return FALSE;
 
+		/* Unsupported object types should never have reached the caches */
+		type = object_type_from_nl_object (kernel_object);
+		g_assert (type != OBJECT_TYPE_UNKNOWN);
+
 		hack_empty_master_iff_lower_up (platform, kernel_object);
 
 		if (cached_object)
@@ -1735,7 +1745,7 @@ refresh_object (NMPlatform *platform, struct nl_object *object, gboolean removed
 		announce_object (platform, kernel_object, cached_object ? NM_PLATFORM_SIGNAL_CHANGED : NM_PLATFORM_SIGNAL_ADDED, reason);
 
 		/* Refresh the master device (even on enslave/release) */
-		if (object_type_from_nl_object (kernel_object) == OBJECT_TYPE_LINK) {
+		if (type == OBJECT_TYPE_LINK) {
 			int kernel_master = rtnl_link_get_master ((struct rtnl_link *) kernel_object);
 			int cached_master = cached_object ? rtnl_link_get_master ((struct rtnl_link *) cached_object) : 0;
 			struct nl_object *master_object;
@@ -1763,10 +1773,6 @@ add_object (NMPlatform *platform, struct nl_object *obj)
 	auto_nl_object struct nl_object *object = obj;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int nle;
-	struct nl_dump_params dp = {
-		.dp_type = NL_DUMP_DETAILS,
-		.dp_fd = stderr,
-	};
 
 	g_return_val_if_fail (object, FALSE);
 
@@ -1782,7 +1788,18 @@ add_object (NMPlatform *platform, struct nl_object *obj)
 		break;
 	default:
 		error ("Netlink error adding %s: %s", to_string_object (platform, object),  nl_geterror (nle));
-		nl_object_dump (object, &dp);
+		if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+			char buf[256];
+			struct nl_dump_params dp = {
+				.dp_type = NL_DUMP_DETAILS,
+				.dp_buf = buf,
+				.dp_buflen = sizeof (buf),
+			};
+
+			nl_object_dump (object, &dp);
+			buf[sizeof (buf) - 1] = '\0';
+			debug ("netlink object:\n%s", buf);
+		}
 		return FALSE;
 	}
 
@@ -1997,6 +2014,11 @@ event_notification (struct nl_msg *msg, gpointer user_data)
 		 */
 		if (!kernel_object)
 			return NL_OK;
+
+		/* Ignore unsupported object types (e.g. AF_PHONET family addresses) */
+		if (type == OBJECT_TYPE_UNKNOWN)
+			return NL_OK;
+
 		/* Handle external addition */
 		if (!cached_object) {
 			nle = nl_cache_add (cache, kernel_object);
@@ -2690,6 +2712,30 @@ link_get_physical_port_id (NMPlatform *platform, int ifindex)
 	return id;
 }
 
+static guint
+link_get_dev_id (NMPlatform *platform, int ifindex)
+{
+	const char *ifname;
+	gs_free char *path = NULL, *id = NULL;
+	gint64 int_val;
+
+	ifname = nm_platform_link_get_name (ifindex);
+	if (!ifname)
+		return 0;
+
+	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+
+	path = g_strdup_printf ("/sys/class/net/%s/dev_id", ifname);
+	id = sysctl_get (platform, path);
+	if (!id || !*id)
+		return 0;
+
+	/* Value is reported as hex */
+	int_val = nm_utils_ascii_str_to_int64 (id, 16, 0, G_MAXUINT16, 0);
+
+	return errno ? 0 : (int) int_val;
+}
+
 static int
 vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags)
 {
@@ -3507,7 +3553,7 @@ build_rtnl_addr (int family,
                  guint flags,
                  const char *label)
 {
-	auto_nl_addr struct rtnl_addr *rtnladdr = _nm_rtnl_addr_alloc (ifindex);
+	auto_nl_object struct rtnl_addr *rtnladdr = _nm_rtnl_addr_alloc (ifindex);
 	struct rtnl_addr *rtnladdr_copy;
 	int addrlen = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
 	auto_nl_addr struct nl_addr *nladdr = _nm_nl_addr_build (family, addr, addrlen);
@@ -3698,6 +3744,7 @@ _route_match (struct rtnl_route *rtnlroute, int family, int ifindex, gboolean in
 
 	if (rtnl_route_get_type (rtnlroute) != RTN_UNICAST ||
 	    rtnl_route_get_table (rtnlroute) != RT_TABLE_MAIN ||
+	    rtnl_route_get_tos (rtnlroute) != 0 ||
 	    (!include_proto_kernel && rtnl_route_get_protocol (rtnlroute) == RTPROT_KERNEL) ||
 	    rtnl_route_get_family (rtnlroute) != family ||
 	    rtnl_route_get_nnexthops (rtnlroute) != 1 ||
@@ -3845,6 +3892,8 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
                struct in6_addr network, int plen, struct in6_addr gateway,
                guint32 metric, guint32 mss)
 {
+	metric = nm_utils_ip6_route_metric_normalize (metric);
+
 	return add_object (platform, build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss));
 }
 
@@ -3863,7 +3912,7 @@ route_search_cache (struct nl_cache *cache, int family, int ifindex, const void
 		if (!_route_match (rtnlroute, family, ifindex, FALSE))
 			continue;
 
-		if (metric && metric != rtnl_route_get_priority (rtnlroute))
+		if (metric != rtnl_route_get_priority (rtnlroute))
 			continue;
 
 		dst = rtnl_route_get_dst (rtnlroute);
@@ -3872,7 +3921,14 @@ route_search_cache (struct nl_cache *cache, int family, int ifindex, const void
 		    || nl_addr_get_prefixlen (dst) != plen)
 			continue;
 
-		clear_host_address (family, nl_addr_get_binary_addr (dst), plen, dst_clean);
+		/* plen = 0 means all host bits, so all bits should be cleared.
+		 * Likewise if the binary address is not present or all zeros.
+		 */
+		if (plen == 0 || nl_addr_iszero (dst))
+			memset (dst_clean, 0, sizeof (dst_clean));
+		else
+			clear_host_address (family, nl_addr_get_binary_addr (dst), plen, dst_clean);
+
 		if (memcmp (dst_clean, network_clean,
 		            family == AF_INET ? sizeof (guint32) : sizeof (struct in6_addr)) != 0)
 			continue;
@@ -3884,7 +3940,7 @@ route_search_cache (struct nl_cache *cache, int family, int ifindex, const void
 }
 
 static gboolean
-refresh_route (NMPlatform *platform, int family, int ifindex, const void *network, int plen, int metric)
+refresh_route (NMPlatform *platform, int family, int ifindex, const void *network, int plen, guint32 metric)
 {
 	struct nl_cache *cache;
 	auto_nl_object struct rtnl_route *cached_object = NULL;
@@ -3910,6 +3966,19 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 
 	cache = choose_cache_by_type (platform, OBJECT_TYPE_IP4_ROUTE);
 
+	if (metric == 0) {
+		/* Deleting an IPv4 route with metric 0 does not only delete an exectly matching route.
+		 * If no route with metric 0 exists, it might delete another route to the same destination.
+		 * For nm_platform_ip4_route_delete() we don't want this semantic.
+		 *
+		 * Instead, re-fetch the route from kernel, and if that fails, there is nothing to do.
+		 * On success, there is still a race that we might end up deleting the wrong route. */
+		if (!refresh_object (platform, (struct nl_object *) route, FALSE, _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)) {
+			rtnl_route_put ((struct rtnl_route *) route);
+			return TRUE;
+		}
+	}
+
 	/* when deleting an IPv4 route, several fields of the provided route must match.
 	 * Lookup in the cache so that we hopefully get the right values. */
 	cached_object = (struct rtnl_route *) nl_cache_search (cache, route);
@@ -3943,8 +4012,8 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 	}
 	rtnl_route_set_scope ((struct rtnl_route *) route, scope);
 
-	if (cached_object)
-		rtnl_route_set_tos ((struct rtnl_route *) route, rtnl_route_get_tos (cached_object));
+	/* we only support routes with TOS zero. As such, delete_route() is also only able to delete
+	 * routes with tos==0. build_rtnl_route() already initializes tos properly. */
 
 	/* The following fields are also relevant when comparing the route, but the default values
 	 * are already as we want them:
@@ -3962,6 +4031,8 @@ ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, in
 {
 	struct in6_addr gateway = IN6ADDR_ANY_INIT;
 
+	metric = nm_utils_ip6_route_metric_normalize (metric);
+
 	return delete_object (platform, build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0), FALSE) &&
 	    refresh_route (platform, AF_INET6, ifindex, &network, plen, metric);
 }
@@ -3989,6 +4060,8 @@ ip4_route_exists (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 static gboolean
 ip6_route_exists (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
+	metric = nm_utils_ip6_route_metric_normalize (metric);
+
 	return ip_route_exists (platform, AF_INET6, ifindex, &network, plen, metric);
 }
 
@@ -4061,6 +4134,33 @@ cache_announce_changes (NMPlatform *platform, struct nl_cache *new, struct nl_ca
 	nl_cache_free (old);
 }
 
+/* The cache should always avoid containing objects not handled by NM, like
+ * e.g. addresses of the AF_PHONET family. */
+static void
+cache_remove_unknown (struct nl_cache *cache)
+{
+	GPtrArray *objects_to_remove = NULL;
+	struct nl_object *object;
+
+	for (object = nl_cache_get_first (cache); object; object = nl_cache_get_next (object)) {
+		if (object_type_from_nl_object (object) == OBJECT_TYPE_UNKNOWN) {
+			if (!objects_to_remove)
+				objects_to_remove = g_ptr_array_new_with_free_func ((GDestroyNotify) nl_object_put);
+			nl_object_get (object);
+			g_ptr_array_add (objects_to_remove, object);
+		}
+	}
+
+	if (objects_to_remove) {
+		guint i;
+
+		for (i = 0; i < objects_to_remove->len; i++)
+			nl_cache_remove (g_ptr_array_index (objects_to_remove, i));
+
+		g_ptr_array_free (objects_to_remove, TRUE);
+	}
+}
+
 /* Creates and populates the netlink object caches. Called upon platform init and
  * when we run out of sync (out of buffer space, netlink congestion control). In case
  * the caches already exist, it finds changed, added and removed objects, announces
@@ -4082,6 +4182,11 @@ cache_repopulate_all (NMPlatform *platform)
 	rtnl_route_alloc_cache (priv->nlh, AF_UNSPEC, 0, &priv->route_cache);
 	g_assert (priv->link_cache && priv->address_cache && priv->route_cache);
 
+	/* Remove all unknown objects from the caches */
+	cache_remove_unknown (priv->link_cache);
+	cache_remove_unknown (priv->address_cache);
+	cache_remove_unknown (priv->route_cache);
+
 	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
 		_rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object);
 	}
@@ -4139,7 +4244,13 @@ event_handler (GIOChannel *channel,
 			 * like to free up some space. We'll read in the status synchronously. */
 			nl_socket_modify_cb (priv->nlh_event, NL_CB_VALID, NL_CB_DEFAULT, NULL, NULL);
 			do {
+				errno = 0;
+
 				nle = nl_recvmsgs_default (priv->nlh_event);
+
+				/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
+				if (nle == 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+					nle = -NLE_AGAIN;
 			} while (nle != -NLE_AGAIN);
 			nl_socket_modify_cb (priv->nlh_event, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data);
 			cache_repopulate_all (platform);
@@ -4473,6 +4584,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_mtu = link_set_mtu;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
+	platform_class->link_get_dev_id = link_get_dev_id;
 	platform_class->link_get_wake_on_lan = link_get_wake_on_lan;
 
 	platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 14fc44c5..ba7708d6 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -258,6 +258,38 @@ nm_platform_sysctl_set (const char *path, const char *value)
 	return klass->sysctl_set (platform, path, value);
 }
 
+gboolean
+nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value)
+{
+	const char *path;
+	gint64 cur;
+
+	/* the hop-limit provided via RA is uint8. */
+	if (value > 0xFF)
+		return FALSE;
+
+	/* don't allow unreasonable small values */
+	if (value < 10)
+		return FALSE;
+
+	path = nm_utils_ip6_property_path (iface, "hop_limit");
+	cur = nm_platform_sysctl_get_int_checked (path, 10, 1, G_MAXINT32, -1);
+
+	/* only allow increasing the hop-limit to avoid DOS by an attacker
+	 * setting a low hop-limit (CVE-2015-2924, rh#1209902) */
+
+	if (value < cur)
+		return FALSE;
+	if (value != cur) {
+		char svalue[20];
+
+		sprintf (svalue, "%d", value);
+		nm_platform_sysctl_set (path, svalue);
+	}
+
+	return TRUE;
+}
+
 /**
  * nm_platform_sysctl_get:
  * @path: Absolute path to sysctl
@@ -963,9 +995,15 @@ nm_platform_link_get_mtu (int ifindex)
 }
 
 /**
- * nm_platform_link_get_mtu:
+ * nm_platform_link_get_physical_port_id:
  * @ifindex: Interface index
  *
+ * The physical port ID, if present, indicates some unique identifier of
+ * the parent interface (eg, the physical port of which this link is a child).
+ * Two links that report the same physical port ID can be assumed to be
+ * children of the same physical port and may share resources that limit
+ * their abilities.
+ *
  * Returns: physical port ID for the interface, or %NULL on error
  * or if the interface has no physical port ID.
  */
@@ -981,6 +1019,28 @@ nm_platform_link_get_physical_port_id (int ifindex)
 }
 
 /**
+ * nm_platform_link_get_dev_id:
+ * @ifindex: Interface index
+ *
+ * In contrast to the physical device ID (which indicates which parent a
+ * child has) the device ID differentiates sibling devices that may share
+ * the same MAC address.
+ *
+ * Returns: device ID for the interface, or 0 on error or if the
+ * interface has no device ID.
+ */
+guint
+nm_platform_link_get_dev_id (int ifindex)
+{
+	reset_error ();
+
+	g_return_val_if_fail (ifindex >= 0, 0);
+	g_return_val_if_fail (klass->link_get_dev_id, 0);
+
+	return klass->link_get_dev_id (platform, ifindex);
+}
+
+/**
  * nm_platform_link_get_wake_onlan:
  * @ifindex: Interface index
  *
@@ -1826,6 +1886,7 @@ nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint3
  * nm_platform_ip6_address_sync:
  * @ifindex: Interface index
  * @known_addresses: List of addresses
+ * @keep_link_local: Don't remove link-local address
  *
  * A convenience function to synchronize addresses for a specific interface
  * with the least possible disturbance. It simply removes addresses that are
@@ -1834,7 +1895,7 @@ nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint3
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses)
+nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboolean keep_link_local)
 {
 	GArray *addresses;
 	NMPlatformIP6Address *address;
@@ -1847,7 +1908,7 @@ nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses)
 		address = &g_array_index (addresses, NMPlatformIP6Address, i);
 
 		/* Leave link local address management to the kernel */
-		if (IN6_IS_ADDR_LINKLOCAL (&address->address))
+		if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
 			continue;
 
 		if (!array_contains_ip6_address (known_addresses, address))
@@ -1880,7 +1941,7 @@ gboolean
 nm_platform_address_flush (int ifindex)
 {
 	return nm_platform_ip4_address_sync (ifindex, NULL, 0)
-			&& nm_platform_ip6_address_sync (ifindex, NULL);
+			&& nm_platform_ip6_address_sync (ifindex, NULL, FALSE);
 }
 
 /******************************************************************/
@@ -2100,7 +2161,8 @@ nm_platform_ip4_route_sync (int ifindex, const GArray *known_routes)
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (known_route))
 				continue;
 
-			if ((known_route->gateway == 0) ^ (i_type != 0)) {
+			if (   (i_type == 0 && known_route->gateway != 0)
+			    || (i_type == 1 && known_route->gateway == 0)) {
 				/* Make two runs over the list of routes. On the first, only add
 				 * device routes, on the second the others (gateway routes). */
 				continue;
@@ -2174,7 +2236,8 @@ nm_platform_ip6_route_sync (int ifindex, const GArray *known_routes)
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (known_route))
 				continue;
 
-			if (IN6_IS_ADDR_UNSPECIFIED (&known_route->gateway) ^ (i_type != 0)) {
+			if (   (i_type == 0 && !IN6_IS_ADDR_UNSPECIFIED (&known_route->gateway))
+			    || (i_type == 1 && IN6_IS_ADDR_UNSPECIFIED (&known_route->gateway))) {
 				/* Make two runs over the list of routes. On the first, only add
 				 * device routes, on the second the others (gateway routes). */
 				continue;
@@ -2738,6 +2801,97 @@ log_ip6_route (NMPlatform *p, int ifindex, NMPlatformIP6Route *route, NMPlatform
 
 /******************************************************************/
 
+static gboolean
+_vtr_v4_route_add (int ifindex, const NMPlatformIPXRoute *route, guint32 v4_pref_src)
+{
+	return nm_platform_ip4_route_add (ifindex > 0 ? ifindex : route->rx.ifindex,
+	                                  route->rx.source,
+	                                  route->r4.network,
+	                                  route->rx.plen,
+	                                  route->r4.gateway,
+	                                  v4_pref_src,
+	                                  route->rx.metric,
+	                                  route->rx.mss);
+}
+
+static gboolean
+_vtr_v6_route_add (int ifindex, const NMPlatformIPXRoute *route, guint32 v4_pref_src)
+{
+	return nm_platform_ip6_route_add (ifindex > 0 ? ifindex : route->rx.ifindex,
+	                                  route->rx.source,
+	                                  route->r6.network,
+	                                  route->rx.plen,
+	                                  route->r6.gateway,
+	                                  route->rx.metric,
+	                                  route->rx.mss);
+}
+
+static gboolean
+_vtr_v4_route_delete (int ifindex, const NMPlatformIPXRoute *route)
+{
+	return nm_platform_ip4_route_delete (ifindex > 0 ? ifindex : route->rx.ifindex,
+	                                     route->r4.network,
+	                                     route->rx.plen,
+	                                     route->rx.metric);
+}
+
+static gboolean
+_vtr_v6_route_delete (int ifindex, const NMPlatformIPXRoute *route)
+{
+	return nm_platform_ip6_route_delete (ifindex > 0 ? ifindex : route->rx.ifindex,
+	                                     route->r6.network,
+	                                     route->rx.plen,
+	                                     route->rx.metric);
+}
+
+static guint32
+_vtr_v4_metric_normalize (guint32 metric)
+{
+	return metric;
+}
+
+static gboolean
+_vtr_v4_route_delete_default (int ifindex, guint32 metric)
+{
+	return nm_platform_ip4_route_delete (ifindex, 0, 0, metric);
+}
+
+static gboolean
+_vtr_v6_route_delete_default (int ifindex, guint32 metric)
+{
+	return nm_platform_ip6_route_delete (ifindex, in6addr_any, 0, metric);
+}
+
+/******************************************************************/
+
+const NMPlatformVTableRoute nm_platform_vtable_route_v4 = {
+	.is_ip4                         = TRUE,
+	.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_get_all                  = nm_platform_ip4_route_get_all,
+	.route_add                      = _vtr_v4_route_add,
+	.route_delete                   = _vtr_v4_route_delete,
+	.route_delete_default           = _vtr_v4_route_delete_default,
+	.metric_normalize               = _vtr_v4_metric_normalize,
+};
+
+const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
+	.is_ip4                         = FALSE,
+	.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_get_all                  = nm_platform_ip6_route_get_all,
+	.route_add                      = _vtr_v6_route_add,
+	.route_delete                   = _vtr_v6_route_delete,
+	.route_delete_default           = _vtr_v6_route_delete_default,
+	.metric_normalize               = nm_utils_ip6_route_metric_normalize,
+};
+
+/******************************************************************/
+
 static void
 nm_platform_init (NMPlatform *object)
 {
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 848f78e4..a49e0f11 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -69,7 +69,10 @@ typedef enum {
 	/* Event came from the kernel. */
 	NM_PLATFORM_REASON_EXTERNAL,
 	/* Event is a result of cache checking and cleanups. */
-	NM_PLATFORM_REASON_CACHE_CHECK
+	NM_PLATFORM_REASON_CACHE_CHECK,
+
+	/* Internal reason to suppress announcing change events */
+	_NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL,
 } NMPlatformReason;
 
 #define __NMPlatformObject_COMMON \
@@ -244,6 +247,23 @@ typedef union {
 
 
 typedef struct {
+	gboolean is_ip4;
+	int addr_family;
+	gsize sizeof_route;
+	int (*route_cmp) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b);
+	const char *(*route_to_string) (const NMPlatformIPXRoute *route);
+	GArray *(*route_get_all) (int ifindex, NMPlatformGetRouteMode mode);
+	gboolean (*route_add) (int ifindex, const NMPlatformIPXRoute *route, guint32 v4_pref_src);
+	gboolean (*route_delete) (int ifindex, const NMPlatformIPXRoute *route);
+	gboolean (*route_delete_default) (int ifindex, guint32 metric);
+	guint32 (*metric_normalize) (guint32 metric);
+} NMPlatformVTableRoute;
+
+extern const NMPlatformVTableRoute nm_platform_vtable_route_v4;
+extern const NMPlatformVTableRoute nm_platform_vtable_route_v6;
+
+
+typedef struct {
 	int peer;
 } NMPlatformVethProperties;
 
@@ -367,7 +387,8 @@ typedef struct {
 	guint32 (*link_get_mtu) (NMPlatform *, int ifindex);
 	gboolean (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
 
-	char * (*link_get_physical_port_id) (NMPlatform *, int ifindex);
+	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
+	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
 	gboolean (*link_get_wake_on_lan) (NMPlatform *, int ifindex);
 
 	gboolean (*link_supports_carrier_detect) (NMPlatform *, int ifindex);
@@ -480,6 +501,8 @@ char *nm_platform_sysctl_get (const char *path);
 gint32 nm_platform_sysctl_get_int32 (const char *path, gint32 fallback);
 gint64 nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gint64 max, gint64 fallback);
 
+gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (const char *iface, int value);
+
 gboolean nm_platform_link_get (int ifindex, NMPlatformLink *link);
 GArray *nm_platform_link_get_all (void);
 gboolean nm_platform_dummy_add (const char *name);
@@ -514,6 +537,7 @@ guint32 nm_platform_link_get_mtu (int ifindex);
 gboolean nm_platform_link_set_mtu (int ifindex, guint32 mtu);
 
 char    *nm_platform_link_get_physical_port_id (int ifindex);
+guint    nm_platform_link_get_dev_id (int ifindex);
 gboolean nm_platform_link_get_wake_on_lan (int ifindex);
 
 gboolean nm_platform_link_supports_carrier_detect (int ifindex);
@@ -569,7 +593,7 @@ gboolean nm_platform_ip6_address_delete (int ifindex, struct in6_addr address, i
 gboolean nm_platform_ip4_address_exists (int ifindex, in_addr_t address, int plen);
 gboolean nm_platform_ip6_address_exists (int ifindex, struct in6_addr address, int plen);
 gboolean nm_platform_ip4_address_sync (int ifindex, const GArray *known_addresses, guint32 device_route_metric);
-gboolean nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses);
+gboolean nm_platform_ip6_address_sync (int ifindex, const GArray *known_addresses, gboolean keep_link_local);
 gboolean nm_platform_address_flush (int ifindex);
 
 gboolean nm_platform_ip4_check_reinstall_device_route (int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric);
diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am
index b06e25e0..c10600eb 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -109,7 +109,7 @@ test_cleanup_linux_CPPFLAGS = \
 	-DKERNEL_HACKS=1
 test_cleanup_linux_LDADD = $(PLATFORM_LDADD)
 
-#@VALGRIND_RULES@
+@VALGRIND_RULES@
 TESTS = test-link-fake test-address-fake test-route-fake test-cleanup-fake test-address-linux test-route-linux test-cleanup-linux
 
 
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index 052636ab..16a48430 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.13.4 from Makefile.am.
+# Makefile.in generated by automake 1.15 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -15,7 +15,17 @@
 @SET_MAKE@
 
 VPATH = @srcdir@
-am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
 am__make_running_with_option = \
   case $${target_option-} in \
       ?) ;; \
@@ -89,8 +99,6 @@ TESTS = test-link-fake$(EXEEXT) test-address-fake$(EXEEXT) \
 	test-address-linux$(EXEEXT) test-route-linux$(EXEEXT) \
 	test-cleanup-linux$(EXEEXT)
 subdir = src/platform/tests
-DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
-	$(top_srcdir)/build-aux/depcomp
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
 	$(top_srcdir)/m4/compiler_warnings.m4 \
@@ -107,6 +115,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
 	$(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
 mkinstalldirs = $(install_sh) -d
 CONFIG_HEADER = $(top_builddir)/config.h
 CONFIG_CLEAN_FILES =
@@ -289,6 +298,8 @@ am__tty_colors = { \
     std=''; \
   fi; \
 }
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+	$(top_srcdir)/build-aux/depcomp
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
@@ -315,7 +326,6 @@ CXXCPP = @CXXCPP@
 CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
-DATADIRNAME = @DATADIRNAME@
 DBUS_CFLAGS = @DBUS_CFLAGS@
 DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
 DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
@@ -350,6 +360,7 @@ GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
 GNUTLS_LIBS = @GNUTLS_LIBS@
 GREP = @GREP@
 GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
 GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
 GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
 GTKDOC_MKPDF = @GTKDOC_MKPDF@
@@ -647,7 +658,6 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__confi
 	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/platform/tests/Makefile'; \
 	$(am__cd) $(top_srcdir) && \
 	  $(AUTOMAKE) --gnu src/platform/tests/Makefile
-.PRECIOUS: Makefile
 Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
 	@case '$?' in \
 	  *config.status*) \
@@ -777,14 +787,14 @@ distclean-compile:
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
-@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c $<
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
 
 .c.obj:
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
-@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'`
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
 
 .c.lo:
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@@ -1698,9 +1708,13 @@ uninstall-am:
 	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
 	tags tags-am uninstall uninstall-am
 
+.PRECIOUS: Makefile
+
 
 @GNOME_CODE_COVERAGE_RULES@
 
+@VALGRIND_RULES@
+
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
 .NOEXPORT:
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index 3ef13b8e..5561038d 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -247,6 +247,12 @@ test_ip6_address_external (void)
 }
 
 void
+init_tests (int *argc, char ***argv)
+{
+	nmtst_init_with_logging (argc, argv, NULL, "ALL");
+}
+
+void
 setup_tests (void)
 {
 	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index afbb438c..0d0f3f63 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -87,6 +87,12 @@ test_cleanup_internal (void)
 }
 
 void
+init_tests (int *argc, char ***argv)
+{
+	nmtst_init_with_logging (argc, argv, NULL, "ALL");
+}
+
+void
 setup_tests (void)
 {
 	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index ab40f652..1980282d 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -125,6 +125,113 @@ link_callback (NMPlatform *platform, int ifindex, NMPlatformLink *received, NMPl
 		g_error ("Added/changed link not found in the local cache.");
 }
 
+gboolean
+ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric)
+{
+	gs_free char *arg_network = NULL;
+	const char *argv[] = {
+		NULL,
+		"route",
+		"list",
+		"dev",
+		ifname,
+		"exact",
+		NULL,
+		NULL,
+	};
+	int exit_status;
+	gs_free char *std_out = NULL, *std_err = NULL;
+	char *out;
+	gboolean success;
+	gs_free_error GError *error = NULL;
+	gs_free char *metric_pattern = NULL;
+
+	g_assert (ifname && nm_utils_iface_valid_name (ifname));
+	g_assert (!strstr (ifname, " metric "));
+	g_assert (plen >= 0 && plen <= 32);
+
+	if (!NM_IS_LINUX_PLATFORM (nm_platform_get ())) {
+		/* If we don't test against linux-platform, we don't actually configure any
+		 * routes in the system. */
+		return -1;
+	}
+
+	argv[0] = nm_utils_file_search_in_paths ("ip", NULL,
+	                                         (const char *[]) { "/sbin", "/usr/sbin", NULL },
+	                                         G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, NULL);
+	argv[6] = arg_network = g_strdup_printf ("%s/%d", nm_utils_inet4_ntop (network, NULL), plen);
+
+	if (!argv[0]) {
+		/* Hm. There is no 'ip' binary. Return *unknown* */
+		return -1;
+	}
+
+	success = g_spawn_sync (NULL,
+	                        (char **) argv,
+	                        (char *[]) { NULL },
+	                        0,
+	                        NULL,
+	                        NULL,
+	                        &std_out,
+	                        &std_err,
+	                        &exit_status,
+	                        &error);
+	g_assert_no_error (error);
+	g_assert (success);
+	g_assert_cmpstr (std_err, ==, "");
+	g_assert (std_out);
+
+	metric_pattern = g_strdup_printf (" metric %u", metric);
+	out = std_out;
+	while (out) {
+		char *eol = strchr (out, '\n');
+		gs_free char *line = eol ? g_strndup (out, eol - out) : g_strdup (out);
+		const char *p;
+
+		out = eol ? &eol[1] : NULL;
+		if (!line[0])
+			continue;
+
+		if (metric == 0) {
+			if (!strstr (line, " metric "))
+				return TRUE;
+		}
+		p = strstr (line, metric_pattern);
+		if (p && NM_IN_SET (p[strlen (metric_pattern)], ' ', '\0'))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+void
+_assert_ip4_route_exists (const char *file, guint line, const char *func, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric)
+{
+	int ifindex;
+	gboolean exists_checked;
+
+	/* Check for existance of the route by spawning iproute2. Do this because platform
+	 * code might be entirely borked, but we expect ip-route to give a correct result.
+	 * If the ip command cannot be found, we accept this as success. */
+	exists_checked = ip4_route_exists (ifname, network, plen, metric);
+	if (exists_checked != -1 && !exists_checked != !exists) {
+		g_error ("[%s:%u] %s(): We expect the ip4 route %s/%d metric %u %s, but it %s",
+		         file, line, func,
+		         nm_utils_inet4_ntop (network, NULL), plen, metric,
+		         exists ? "to exist" : "not to exist",
+		         exists ? "doesn't" : "does");
+	}
+
+	ifindex = nm_platform_link_get_ifindex (ifname);
+	g_assert (ifindex > 0);
+	if (!nm_platform_ip4_route_exists (ifindex, network, plen, metric) != !exists) {
+		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u %s, but platform thinks %s",
+		         file, line, func,
+		         nm_utils_inet4_ntop (network, NULL), plen, metric,
+		         exists ? "exists" : "does not exist",
+		         exists ? "it doesn't" : "it does");
+	}
+}
+
 void
 run_command (const char *format, ...)
 {
@@ -148,20 +255,22 @@ main (int argc, char **argv)
 	int result;
 	const char *program = *argv;
 
-	nmtst_init_with_logging (&argc, &argv, NULL, "ALL");
+	init_tests (&argc, &argv);
 
+	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
 	if (SETUP == nm_linux_platform_setup && getuid() != 0) {
 		/* Try to exec as sudo, this function does not return, if a sudo-cmd is set. */
 		nmtst_reexec_sudo ();
 
 #ifdef REQUIRE_ROOT_TESTS
-		g_message ("Fail test: requires root privileges (%s)", program);
+		g_print ("Fail test: requires root privileges (%s)\n", program);
 		return EXIT_FAILURE;
 #else
-		g_message ("Skipping test: requires root privileges (%s)", program);
+		g_print ("Skipping test: requires root privileges (%s)\n", program);
 		return 77;
 #endif
 	}
+	NM_PRAGMA_WARNING_REENABLE
 
 	SETUP ();
 
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 8f6b3912..367833d1 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -9,6 +9,8 @@
 #include "nm-fake-platform.h"
 #include "nm-linux-platform.h"
 
+#include "nm-test-utils.h"
+
 #define DEVICE_NAME "nm-test-device"
 
 #define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
@@ -34,9 +36,15 @@ void accept_signal (SignalData *data);
 void wait_signal (SignalData *data);
 void free_signal (SignalData *data);
 
+gboolean ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric);
+
+void _assert_ip4_route_exists (const char *file, guint line, const char *func, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric);
+#define assert_ip4_route_exists(exists, ifname, network, plen, metric) _assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, exists, ifname, network, plen, metric)
+
 void link_callback (NMPlatform *platform, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data);
 
 void run_command (const char *format, ...);
 
+void init_tests (int *argc, char ***argv);
 void setup_tests (void);
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 8bd2ef44..99556e0a 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -365,12 +365,14 @@ test_bridge (void)
 static void
 test_bond (void)
 {
+	NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare")
 	if (SETUP == nm_linux_platform_setup &&
 	    !g_file_test ("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) &&
 	    system("modprobe --show bonding") != 0) {
 		g_test_skip ("Skipping test for bonding: bonding module not available");
 		return;
 	}
+	NM_PRAGMA_WARNING_REENABLE
 
 	test_software (NM_LINK_TYPE_BOND, "bond");
 }
@@ -538,6 +540,12 @@ test_external (void)
 }
 
 void
+init_tests (int *argc, char ***argv)
+{
+	nmtst_init_with_logging (argc, argv, NULL, "ALL");
+}
+
+void
 setup_tests (void)
 {
 	nm_platform_link_delete (nm_platform_link_get_ifindex (DEVICE_NAME));
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 2c70b412..2d3caaf1 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -2,6 +2,7 @@
 
 #include "test-common.h"
 #include "nm-test-utils.h"
+#include "NetworkManagerUtils.h"
 
 #define DEVICE_NAME "nm-test-device"
 
@@ -50,6 +51,75 @@ ip6_route_callback (NMPlatform *platform, int ifindex, NMPlatformIP6Route *recei
 }
 
 static void
+test_ip4_route_metric0 (void)
+{
+	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
+	SignalData *route_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_route_callback);
+	SignalData *route_changed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip4_route_callback);
+	SignalData *route_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_route_callback);
+	in_addr_t network = nmtst_inet4_from_string ("192.0.2.5"); /* from 192.0.2.0/24 (TEST-NET-1) (rfc5737) */
+	int plen = 32;
+	int metric = 22987;
+	int mss = 1000;
+
+	/* No routes initially */
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
+
+	/* add the first route */
+	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, metric, mss));
+	no_error ();
+	accept_signal (route_added);
+
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
+
+	/* Deleting route with metric 0 does nothing */
+	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, 0));
+	no_error ();
+	g_assert (!route_removed->received);
+
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
+
+	/* add the second route */
+	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, 0, mss));
+	no_error ();
+	accept_signal (route_added);
+
+	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
+
+	/* Delete route with metric 0 */
+	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, 0));
+	no_error ();
+	accept_signal (route_removed);
+
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
+
+	/* Delete route with metric 0 again (we expect nothing to happen) */
+	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, 0));
+	no_error ();
+	g_assert (!route_removed->received);
+
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (TRUE,  DEVICE_NAME, network, plen, metric);
+
+	/* Delete the other route */
+	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, metric));
+	no_error ();
+	accept_signal (route_removed);
+
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, 0);
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
+
+	free_signal (route_added);
+	free_signal (route_changed);
+	free_signal (route_removed);
+}
+
+static void
 test_ip4_route (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (DEVICE_NAME);
@@ -74,11 +144,11 @@ test_ip4_route (void)
 	accept_signal (route_added);
 
 	/* Add route */
-	g_assert (!nm_platform_ip4_route_exists (ifindex, network, plen, metric));
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
 	no_error ();
 	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss));
 	no_error ();
-	g_assert (nm_platform_ip4_route_exists (ifindex, network, plen, metric));
+	assert_ip4_route_exists (TRUE, DEVICE_NAME, network, plen, metric);
 	no_error ();
 	accept_signal (route_added);
 
@@ -88,11 +158,11 @@ test_ip4_route (void)
 	accept_signal (route_changed);
 
 	/* Add default route */
-	g_assert (!nm_platform_ip4_route_exists (ifindex, 0, 0, metric));
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, 0, 0, metric);
 	no_error ();
 	g_assert (nm_platform_ip4_route_add (ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss));
 	no_error ();
-	g_assert (nm_platform_ip4_route_exists (ifindex, 0, 0, metric));
+	assert_ip4_route_exists (TRUE, DEVICE_NAME, 0, 0, metric);
 	no_error ();
 	accept_signal (route_added);
 
@@ -127,13 +197,13 @@ test_ip4_route (void)
 	rts[2].mss = mss;
 	g_assert_cmpint (routes->len, ==, 3);
 	g_assert (!memcmp (routes->data, rts, sizeof (rts)));
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len);
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len, TRUE);
 	g_array_unref (routes);
 
 	/* Remove route */
 	g_assert (nm_platform_ip4_route_delete (ifindex, network, plen, metric));
 	no_error ();
-	g_assert (!nm_platform_ip4_route_exists (ifindex, network, plen, metric));
+	assert_ip4_route_exists (FALSE, DEVICE_NAME, network, plen, metric);
 	accept_signal (route_removed);
 
 	/* Remove route again */
@@ -205,25 +275,25 @@ test_ip6_route (void)
 	rts[0].plen = 128;
 	rts[0].ifindex = ifindex;
 	rts[0].gateway = in6addr_any;
-	rts[0].metric = metric;
+	rts[0].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[0].mss = mss;
 	rts[1].source = NM_IP_CONFIG_SOURCE_USER;
 	rts[1].network = network;
 	rts[1].plen = plen;
 	rts[1].ifindex = ifindex;
 	rts[1].gateway = gateway;
-	rts[1].metric = metric;
+	rts[1].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[1].mss = mss;
 	rts[2].source = NM_IP_CONFIG_SOURCE_USER;
 	rts[2].network = in6addr_any;
 	rts[2].plen = 0;
 	rts[2].ifindex = ifindex;
 	rts[2].gateway = gateway;
-	rts[2].metric = metric;
+	rts[2].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[2].mss = mss;
 	g_assert_cmpint (routes->len, ==, 3);
 	g_assert (!memcmp (routes->data, rts, sizeof (rts)));
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len);
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len, TRUE);
 	g_array_unref (routes);
 
 	/* Remove route */
@@ -242,6 +312,12 @@ test_ip6_route (void)
 }
 
 void
+init_tests (int *argc, char ***argv)
+{
+	nmtst_init_with_logging (argc, argv, NULL, "ALL");
+}
+
+void
 setup_tests (void)
 {
 	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
@@ -256,4 +332,5 @@ setup_tests (void)
 
 	g_test_add_func ("/route/ip4", test_ip4_route);
 	g_test_add_func ("/route/ip6", test_ip6_route);
+	g_test_add_func ("/route/ip4_metric0", test_ip4_route_metric0);
 }
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c
index 752da926..b31ffa1c 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/wifi-utils-nl80211.c
@@ -738,6 +738,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 		}
 	}
 
+	/* Find number of supported frequencies */
 	info->num_freqs = 0;
 
 	nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
@@ -757,6 +758,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 		}
 	}
 
+	/* Read supported frequencies */
 	info->freqs = g_malloc0 (sizeof (guint32) * info->num_freqs);
 
 	freq_idx = 0;
@@ -775,10 +777,19 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 
 			info->freqs[freq_idx] =
 				nla_get_u32 (tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+
+			info->caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID;
+
+			if (info->freqs[freq_idx] > 2400 && info->freqs[freq_idx] < 2500)
+				info->caps |= NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
+			if (info->freqs[freq_idx] > 4900 && info->freqs[freq_idx] < 6000)
+				info->caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
+
 			freq_idx++;
 		}
 	}
 
+	/* Read security/encryption support */
 	if (tb[NL80211_ATTR_CIPHER_SUITES]) {
 		int num;
 		int i;
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c
index e0331de7..8428010a 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/wifi-utils-wext.c
@@ -569,6 +569,7 @@ wifi_wext_init (const char *iface, int ifindex, gboolean check_scan)
 	guint32 response_len = 0;
 	struct iw_range_with_scan_capa *scan_capa_range;
 	int i;
+	gboolean freq_valid = FALSE, has_5ghz = FALSE, has_2ghz = FALSE;
 
 	wext = wifi_data_new (iface, ifindex, sizeof (*wext));
 	wext->parent.get_mode = wifi_wext_get_mode;
@@ -609,8 +610,14 @@ wifi_wext_init (const char *iface, int ifindex, gboolean check_scan)
 	wext->max_qual.updated = range.max_qual.updated;
 
 	wext->num_freqs = MIN (range.num_frequency, IW_MAX_FREQUENCIES);
-	for (i = 0; i < wext->num_freqs; i++)
+	for (i = 0; i < wext->num_freqs; i++) {
 		wext->freqs[i] = iw_freq_to_uint32 (&range.freq[i]);
+		freq_valid = TRUE;
+		if (wext->freqs[i] > 2400 && wext->freqs[i] < 2500)
+			has_2ghz = TRUE;
+		else if (wext->freqs[i] > 4900 && wext->freqs[i] < 6000)
+			has_5ghz = TRUE;
+	}
 
 	/* Check for scanning capability; cards that can't scan are not supported */
 	if (check_scan && (wext_can_scan (wext) == FALSE)) {
@@ -638,6 +645,12 @@ wifi_wext_init (const char *iface, int ifindex, gboolean check_scan)
 	}
 
 	wext->parent.caps = wext_get_caps (wext, &range);
+	if (freq_valid)
+		wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID;
+	if (has_2ghz)
+		wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
+	if (has_5ghz)
+		wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
 
 	nm_log_info (LOGD_HW | LOGD_WIFI,
 	             "(%s): using WEXT for WiFi device control",
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index f1b57ace..eb37bc22 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -44,7 +44,8 @@ gboolean wifi_utils_set_mode (WifiData *data, const NM80211Mode mode);
 /* Returns frequency in MHz */
 guint32 wifi_utils_get_freq (WifiData *data);
 
-/* Return the first supported frequency in the zero-terminated list */
+/* Return the first supported frequency in the zero-terminated list.
+ * Frequencies are specified in MHz. */
 guint32 wifi_utils_find_freq (WifiData *data, const guint32 *freqs);
 
 /* Caller must free returned byte array */