summary refs log tree commit diff
path: root/src/platform/nm-linux-platform.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/nm-linux-platform.c')
-rw-r--r--src/platform/nm-linux-platform.c4949
1 files changed, 2663 insertions, 2286 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index c337ee9c..80757f4e 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012-2013 Red Hat, Inc.
+ * Copyright (C) 2012-2015 Red Hat, Inc.
  */
 #include "config.h"
 
@@ -31,10 +31,6 @@
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
-#include <sys/ioctl.h>
-#include <linux/sockios.h>
-#include <linux/ethtool.h>
-#include <linux/mii.h>
 #include <netlink/netlink.h>
 #include <netlink/object.h>
 #include <netlink/cache.h>
@@ -44,9 +40,9 @@
 #include <netlink/route/route.h>
 #include <gudev/gudev.h>
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE || HAVE_LIBNL_INET6_TOKEN
 #include <netlink/route/link/inet6.h>
-#if HAVE_KERNEL_INET6_ADDR_GEN_MODE
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE && HAVE_KERNEL_INET6_ADDR_GEN_MODE
 #include <linux/if_link.h>
 #else
 #define IN6_ADDR_GEN_MODE_EUI64 0
@@ -55,21 +51,101 @@
 #endif
 
 #include "gsystem-local-alloc.h"
+#include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-linux-platform.h"
+#include "nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-logging.h"
 #include "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
+#include "nmp-object.h"
 
 /* This is only included for the translation of VLAN flags */
 #include "nm-setting-vlan.h"
 
-#define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__)
-#define warning(...) nm_log_warn (LOGD_PLATFORM, __VA_ARGS__)
-#define error(...) nm_log_err (LOGD_PLATFORM, __VA_ARGS__)
+/*********************************************************************************************/
+
+#define _LOG_DOMAIN LOGD_PLATFORM
+#define _LOG_PREFIX_NAME "platform-linux"
+
+#define _LOG(level, domain, self, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            char __prefix[32]; \
+            const char *__p_prefix = _LOG_PREFIX_NAME; \
+            const void *const __self = (self); \
+            \
+            if (__self && __self != nm_platform_try_get ()) { \
+                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _LOG_PREFIX_NAME, __self); \
+                __p_prefix = __prefix; \
+            } \
+            _nm_log (__level, __domain, 0, \
+                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+#define _LOG_LEVEL_ENABLED(level, domain) \
+    ( nm_logging_enabled ((level), (domain)) )
+
+#ifdef NM_MORE_LOGGING
+#define _LOGT_ENABLED()     _LOG_LEVEL_ENABLED (LOGL_TRACE, _LOG_DOMAIN)
+#define _LOGT(...)          _LOG (LOGL_TRACE, _LOG_DOMAIN, platform, __VA_ARGS__)
+#else
+#define _LOGT_ENABLED()     FALSE
+#define _LOGT(...)          G_STMT_START { if (FALSE) { _LOG (LOGL_TRACE, _LOG_DOMAIN, platform, __VA_ARGS__); } } G_STMT_END
+#endif
+
+#define _LOGD(...)      _LOG (LOGL_DEBUG, _LOG_DOMAIN, platform, __VA_ARGS__)
+#define _LOGI(...)      _LOG (LOGL_INFO , _LOG_DOMAIN, platform, __VA_ARGS__)
+#define _LOGW(...)      _LOG (LOGL_WARN , _LOG_DOMAIN, platform, __VA_ARGS__)
+#define _LOGE(...)      _LOG (LOGL_ERR  , _LOG_DOMAIN, platform, __VA_ARGS__)
+
+#define debug(...)      _LOG (LOGL_DEBUG, _LOG_DOMAIN, NULL, __VA_ARGS__)
+#define warning(...)    _LOG (LOGL_WARN , _LOG_DOMAIN, NULL, __VA_ARGS__)
+#define error(...)      _LOG (LOGL_ERR  , _LOG_DOMAIN, NULL, __VA_ARGS__)
+
+/******************************************************************
+ * Forward declarations and enums
+ ******************************************************************/
 
+typedef enum {
+	DELAYED_ACTION_TYPE_NONE                        = 0,
+	DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS           = (1LL << 0),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES   = (1LL << 1),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES   = (1LL << 2),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES      = (1LL << 3),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES      = (1LL << 4),
+	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL << 5),
+	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL << 6),
+	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL << 7),
+	__DELAYED_ACTION_TYPE_MAX,
+
+	DELAYED_ACTION_TYPE_REFRESH_ALL                 = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+
+	DELAYED_ACTION_TYPE_MAX                         = __DELAYED_ACTION_TYPE_MAX -1,
+} DelayedActionType;
+
+static gboolean tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props);
+static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
+static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
+static void do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action);
+static void do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action);
+static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
+static gboolean event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks);
+static NMPCacheOpsType cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason);
+
+/******************************************************************
+ * libnl unility functions and wrappers
+ ******************************************************************/
 
 struct libnl_vtable
 {
@@ -78,41 +154,6 @@ struct libnl_vtable
 	int (*f_nl_has_capability) (int capability);
 };
 
-
-typedef struct {
-	struct nl_sock *nlh;
-	struct nl_sock *nlh_event;
-	struct nl_cache *link_cache;
-	struct nl_cache *address_cache;
-	struct nl_cache *route_cache;
-	GIOChannel *event_channel;
-	guint event_id;
-
-	GUdevClient *udev_client;
-	GHashTable *udev_devices;
-
-	GHashTable *wifi_data;
-
-	int support_kernel_extended_ifa_flags;
-	int support_user_ipv6ll;
-} NMLinuxPlatformPrivate;
-
-#define NM_LINUX_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate))
-
-G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-static const char *to_string_object (NMPlatform *platform, struct nl_object *obj);
-static gboolean _address_match (struct rtnl_addr *addr, int family, int ifindex);
-static gboolean _route_match (struct rtnl_route *rtnlroute, int family, int ifindex, gboolean include_proto_kernel);
-
-void
-nm_linux_platform_setup (void)
-{
-	nm_platform_setup (NM_TYPE_LINUX_PLATFORM);
-}
-
-/******************************************************************/
-
 static int
 _nl_f_nl_has_capability (int capability)
 {
@@ -148,73 +189,10 @@ _nl_has_capability (int capability)
 	return (_nl_get_vtable ()->f_nl_has_capability) (capability);
 }
 
-/******************************************************************/
-
-static guint32
-_get_expiry (guint32 now_s, guint32 lifetime_s)
-{
-	gint64 t = ((gint64) now_s) + ((gint64) lifetime_s);
-
-	return MIN (t, NM_PLATFORM_LIFETIME_PERMANENT - 1);
-}
-
-/* The rtnl_addr object contains relative lifetimes @valid and @preferred
- * that count in seconds, starting from the moment when the kernel constructed
- * the netlink message.
- *
- * There is also a field rtnl_addr_last_update_time(), which is the absolute
- * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
- * was modified (wrapping every 497 days).
- * Immediately at the time when the address was last modified, #NOW and @last_update_time
- * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
- * However, this is not true in general. As time goes by, whenever kernel sends a new address
- * via netlink, the lifetimes keep counting down.
- *
- * As we cache the rtnl_addr object we must know the absolute expiries.
- * As a hack, modify the relative timestamps valid and preferred into absolute
- * timestamps of scale nm_utils_get_monotonic_timestamp_s().
- **/
-static void
-_rtnl_addr_hack_lifetimes_rel_to_abs (struct rtnl_addr *rtnladdr)
-{
-	guint32 a_valid  = rtnl_addr_get_valid_lifetime (rtnladdr);
-	guint32 a_preferred = rtnl_addr_get_preferred_lifetime (rtnladdr);
-	guint32 now;
-
-	if (a_valid == NM_PLATFORM_LIFETIME_PERMANENT &&
-	    a_preferred == NM_PLATFORM_LIFETIME_PERMANENT)
-		return;
-
-	now = (guint32) nm_utils_get_monotonic_timestamp_s ();
-
-	if (a_preferred > a_valid)
-		a_preferred = a_valid;
-
-	if (a_valid != NM_PLATFORM_LIFETIME_PERMANENT)
-		rtnl_addr_set_valid_lifetime (rtnladdr, _get_expiry (now, a_valid));
-	rtnl_addr_set_preferred_lifetime (rtnladdr, _get_expiry (now, a_preferred));
-}
-
-/******************************************************************/
-
-/* libnl library workarounds and additions */
-
 /* Automatic deallocation of local variables */
-#define auto_nl_cache __attribute__((cleanup(put_nl_cache)))
+#define auto_nl_object __attribute__((cleanup(_nl_auto_nl_object)))
 static void
-put_nl_cache (void *ptr)
-{
-	struct nl_cache **cache = ptr;
-
-	if (cache && *cache) {
-		nl_cache_free (*cache);
-		*cache = NULL;
-	}
-}
-
-#define auto_nl_object __attribute__((cleanup(put_nl_object)))
-static void
-put_nl_object (void *ptr)
+_nl_auto_nl_object (void *ptr)
 {
 	struct nl_object **object = ptr;
 
@@ -224,9 +202,9 @@ put_nl_object (void *ptr)
 	}
 }
 
-#define auto_nl_addr __attribute__((cleanup(put_nl_addr)))
+#define auto_nl_addr __attribute__((cleanup(_nl_auto_nl_addr)))
 static void
-put_nl_addr (void *ptr)
+_nl_auto_nl_addr (void *ptr)
 {
 	struct nl_addr **object = ptr;
 
@@ -236,12 +214,10 @@ put_nl_addr (void *ptr)
 	}
 }
 
-/*******************************************************************/
-
 /* wrap the libnl alloc functions and abort on out-of-memory*/
 
 static struct nl_addr *
-_nm_nl_addr_build (int family, const void *buf, size_t size)
+_nl_addr_build (int family, const void *buf, size_t size)
 {
 	struct nl_addr *addr;
 
@@ -253,7 +229,7 @@ _nm_nl_addr_build (int family, const void *buf, size_t size)
 }
 
 static struct rtnl_link *
-_nm_rtnl_link_alloc (int ifindex, const char*name)
+_nl_rtnl_link_alloc (int ifindex, const char*name)
 {
 	struct rtnl_link *rtnllink;
 
@@ -269,7 +245,7 @@ _nm_rtnl_link_alloc (int ifindex, const char*name)
 }
 
 static struct rtnl_addr *
-_nm_rtnl_addr_alloc (int ifindex)
+_nl_rtnl_addr_alloc (int ifindex)
 {
 	struct rtnl_addr *rtnladdr;
 
@@ -282,7 +258,7 @@ _nm_rtnl_addr_alloc (int ifindex)
 }
 
 static struct rtnl_route *
-_nm_rtnl_route_alloc (void)
+_nl_rtnl_route_alloc (void)
 {
 	struct rtnl_route *rtnlroute = rtnl_route_alloc ();
 
@@ -292,7 +268,7 @@ _nm_rtnl_route_alloc (void)
 }
 
 static struct rtnl_nexthop *
-_nm_rtnl_route_nh_alloc (void)
+_nl_rtnl_route_nh_alloc (void)
 {
 	struct rtnl_nexthop *nexthop;
 
@@ -302,11 +278,9 @@ _nm_rtnl_route_nh_alloc (void)
 	return nexthop;
 }
 
-/*******************************************************************/
-
 /* rtnl_addr_set_prefixlen fails to update the nl_addr prefixlen */
 static void
-nm_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
+_nl_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
 {
 	struct nl_addr *nladdr;
 
@@ -316,188 +290,30 @@ nm_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
 	if (nladdr)
 		nl_addr_set_prefixlen (nladdr, plen);
 }
-#define rtnl_addr_set_prefixlen nm_rtnl_addr_set_prefixlen
-
-typedef enum {
-	OBJECT_TYPE_UNKNOWN,
-	OBJECT_TYPE_LINK,
-	OBJECT_TYPE_IP4_ADDRESS,
-	OBJECT_TYPE_IP6_ADDRESS,
-	OBJECT_TYPE_IP4_ROUTE,
-	OBJECT_TYPE_IP6_ROUTE,
-	__OBJECT_TYPE_LAST,
-} ObjectType;
-
-static ObjectType
-object_type_from_nl_object (const struct nl_object *object)
-{
-	const char *type_str;
 
-	if (!object || !(type_str = nl_object_get_type (object)))
-		return OBJECT_TYPE_UNKNOWN;
-
-	if (!strcmp (type_str, "route/link"))
-		return OBJECT_TYPE_LINK;
-	else if (!strcmp (type_str, "route/addr")) {
-		switch (rtnl_addr_get_family ((struct rtnl_addr *) object)) {
-		case AF_INET:
-			return OBJECT_TYPE_IP4_ADDRESS;
-		case AF_INET6:
-			return OBJECT_TYPE_IP6_ADDRESS;
-		default:
-			return OBJECT_TYPE_UNKNOWN;
-		}
-	} else if (!strcmp (type_str, "route/route")) {
-		switch (rtnl_route_get_family ((struct rtnl_route *) object)) {
-		case AF_INET:
-			return OBJECT_TYPE_IP4_ROUTE;
-		case AF_INET6:
-			return OBJECT_TYPE_IP6_ROUTE;
-		default:
-			return OBJECT_TYPE_UNKNOWN;
-		}
-	} else
-		return OBJECT_TYPE_UNKNOWN;
-}
-
-static void
-_nl_link_family_unset (struct nl_object *obj, int *family)
-{
-	if (!obj || object_type_from_nl_object (obj) != OBJECT_TYPE_LINK)
-		*family = AF_UNSPEC;
-	else {
-		*family = rtnl_link_get_family ((struct rtnl_link *) obj);
-
-		/* Always explicitly set the family to AF_UNSPEC, even if rtnl_link_get_family() might
-		 * already return %AF_UNSPEC. The reason is, that %AF_UNSPEC is the default family
-		 * and libnl nl_object_identical() function will only succeed, if the family is
-		 * explicitly set (which we cannot be sure, unless setting it). */
-		rtnl_link_set_family ((struct rtnl_link *) obj, AF_UNSPEC);
-	}
-}
-
-/* In our link cache, we coerce the family of all link objects to AF_UNSPEC.
- * Thus, before searching for an object, we fixup @needle to have the right
- * id (by resetting the family). */
-static struct nl_object *
-nm_nl_cache_search (struct nl_cache *cache, struct nl_object *needle)
-{
-	int family;
-	struct nl_object *obj;
-
-	_nl_link_family_unset (needle, &family);
-	obj = nl_cache_search (cache, needle);
-	if (family != AF_UNSPEC) {
-		/* restore the family of the @needle instance. If the family was
-		 * unset before, we cannot make it unset again. Thus, in that case
-		 * we cannot undo _nl_link_family_unset() entirely. */
-		rtnl_link_set_family ((struct rtnl_link *) needle, family);
-	}
-
-	return obj;
-}
-
-/* Ask the kernel for an object identical (as in nl_cache_identical) to the
- * needle argument. This is a kernel counterpart for nl_cache_search.
- *
- * The returned object must be freed by the caller with nl_object_put().
- */
-static struct nl_object *
-get_kernel_object (struct nl_sock *sock, struct nl_object *needle)
+static const char *
+_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
 {
-	struct nl_object *object = NULL;
-	ObjectType type = object_type_from_nl_object (needle);
+	const char *str_type = NULL;
 
 	switch (type) {
-	case OBJECT_TYPE_LINK:
-		{
-			int ifindex = rtnl_link_get_ifindex ((struct rtnl_link *) needle);
-			const char *name = rtnl_link_get_name ((struct rtnl_link *) needle);
-			int nle;
-
-			nle = rtnl_link_get_kernel (sock, ifindex, name, (struct rtnl_link **) &object);
-			switch (nle) {
-			case -NLE_SUCCESS:
-				if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-					name = rtnl_link_get_name ((struct rtnl_link *) object);
-					debug ("get_kernel_object for link: %s (%d, family %d)",
-					       name ? name : "(unknown)",
-					       rtnl_link_get_ifindex ((struct rtnl_link *) object),
-					       rtnl_link_get_family ((struct rtnl_link *) object));
-				}
-
-				_nl_link_family_unset (object, &nle);
-				return object;
-			case -NLE_NODEV:
-				debug ("get_kernel_object for link %s (%d) had no result",
-				       name ? name : "(unknown)", ifindex);
-				return NULL;
-			default:
-				error ("get_kernel_object for link %s (%d) failed: %s (%d)",
-				       name ? name : "(unknown)", ifindex, nl_geterror (nle), nle);
-				return NULL;
-			}
-		}
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		/* Fallback to a one-time cache allocation. */
-		{
-			struct nl_cache *cache;
-			int nle;
-
-			/* FIXME: every time we refresh *one* object, we request an
-			 * entire dump. E.g. check_cache_items() gets O(n2) complexitly. */
-
-			nle = nl_cache_alloc_and_fill (
-					nl_cache_ops_lookup (nl_object_get_type (needle)),
-					sock, &cache);
-			if (nle) {
-				error ("get_kernel_object for type %d failed: %s (%d)",
-				       type, nl_geterror (nle), nle);
-				return NULL;
-			}
-
-			object = nl_cache_search (cache, needle);
-
-			nl_cache_free (cache);
-
-			if (object && (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS))
-				_rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object);
-
-			if (object)
-				debug ("get_kernel_object for type %d returned %p", type, object);
-			else
-				debug ("get_kernel_object for type %d had no result", type);
-			return object;
-		}
-	default:
-		g_return_val_if_reached (NULL);
-		return NULL;
+	case RTM_NEWLINK:  str_type = "NEWLINK";  break;
+	case RTM_DELLINK:  str_type = "DELLINK";  break;
+	case RTM_NEWADDR:  str_type = "NEWADDR";  break;
+	case RTM_DELADDR:  str_type = "DELADDR";  break;
+	case RTM_NEWROUTE: str_type = "NEWROUTE"; break;
+	case RTM_DELROUTE: str_type = "DELROUTE"; break;
 	}
+	if (str_type)
+		g_strlcpy (buf, str_type, len);
+	else
+		g_snprintf (buf, len, "(%d)", type);
+	return buf;
 }
 
-/* libnl 3.2 doesn't seem to provide such a generic way to add libnl-route objects. */
-static int
-add_kernel_object (struct nl_sock *sock, struct nl_object *object)
-{
-	switch (object_type_from_nl_object (object)) {
-	case OBJECT_TYPE_LINK:
-		return rtnl_link_add (sock, (struct rtnl_link *) object, NLM_F_CREATE);
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return rtnl_addr_add (sock, (struct rtnl_addr *) object, NLM_F_CREATE | NLM_F_REPLACE);
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		return rtnl_route_add (sock, (struct rtnl_route *) object, NLM_F_CREATE | NLM_F_REPLACE);
-	default:
-		g_return_val_if_reached (-NLE_INVAL);
-		return -NLE_INVAL;
-	}
-}
+/******************************************************************/
 
-/* nm_rtnl_link_parse_info_data(): Re-fetches a link from the kernel
+/* _nl_link_parse_info_data(): Re-fetches a link from the kernel
  * and parses its IFLA_INFO_DATA using a caller-provided parser.
  *
  * Code is stolen from rtnl_link_get_kernel(), nl_pickup(), and link_msg_parser().
@@ -519,7 +335,7 @@ static struct nla_policy info_data_link_info_policy[IFLA_INFO_MAX + 1] = {
 };
 
 static int
-info_data_parser (struct nl_msg *msg, void *arg)
+_nl_link_parse_info_data_cb (struct nl_msg *msg, void *arg)
 {
 	NMNLInfoDataClosure *closure = arg;
 	struct nlmsghdr *n = nlmsg_hdr (msg);
@@ -548,8 +364,8 @@ info_data_parser (struct nl_msg *msg, void *arg)
 }
 
 static int
-nm_rtnl_link_parse_info_data (struct nl_sock *sk, int ifindex,
-                              NMNLInfoDataParser parser, gpointer parser_data)
+_nl_link_parse_info_data (struct nl_sock *sk, int ifindex,
+                          NMNLInfoDataParser parser, gpointer parser_data)
 {
 	NMNLInfoDataClosure data = { .parser = parser, .parser_data = parser_data };
 	struct nl_msg *msg = NULL;
@@ -568,7 +384,7 @@ nm_rtnl_link_parse_info_data (struct nl_sock *sk, int ifindex,
 	cb = nl_cb_clone (nl_socket_get_cb (sk));
 	if (cb == NULL)
 		return -NLE_NOMEM;
-	nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, info_data_parser, &data);
+	nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, _nl_link_parse_info_data_cb, &data);
 
 	err = nl_recvmsgs (sk, cb);
 	nl_cb_put (cb);
@@ -581,75 +397,171 @@ nm_rtnl_link_parse_info_data (struct nl_sock *sk, int ifindex,
 
 /******************************************************************/
 
-static gboolean
-ethtool_get (const char *name, gpointer edata)
+static int
+_nl_sock_flush_data (struct nl_sock *sk)
+{
+	int nle;
+	struct nl_cb *cb;
+
+	cb = nl_cb_clone (nl_socket_get_cb (sk));
+	if (cb == NULL)
+		return -NLE_NOMEM;
+
+	nl_cb_set (cb, NL_CB_VALID, NL_CB_DEFAULT, NULL, NULL);
+	nl_cb_set (cb, NL_CB_SEQ_CHECK, NL_CB_DEFAULT, NULL, NULL);
+	nl_cb_err (cb, NL_CB_DEFAULT, NULL, NULL);
+	do {
+		errno = 0;
+
+		nle = nl_recvmsgs (sk, cb);
+
+		/* 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_cb_put (cb);
+	return nle;
+}
+
+static void
+_nl_msg_set_seq (struct nl_sock *sk, struct nl_msg *msg, guint32 *out_seq)
 {
-	struct ifreq ifr;
-	int fd;
+	guint32 seq;
 
-	memset (&ifr, 0, sizeof (ifr));
-	strncpy (ifr.ifr_name, name, IFNAMSIZ);
-	ifr.ifr_data = edata;
+	/* choose our own sequence number, because libnl does not ensure that
+	 * it isn't zero -- which would confuse our checking for outstanding
+	 * messages. */
+	seq = nl_socket_use_seq (sk);
+	if (seq == 0)
+		seq = nl_socket_use_seq (sk);
 
-	fd = socket (PF_INET, SOCK_DGRAM, 0);
-	if (fd < 0) {
-		error ("ethtool: Could not open socket.");
-		return FALSE;
-	}
+	nlmsg_hdr (msg)->nlmsg_seq = seq;
+	if (out_seq)
+		*out_seq = seq;
+}
 
-	if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) {
-		debug ("ethtool: Request failed: %s", strerror (errno));
-		close (fd);
-		return FALSE;
-	}
+static int
+_nl_sock_request_link (NMPlatform *platform, struct nl_sock *sk, int ifindex, const char *name, guint32 *out_seq)
+{
+	struct nl_msg *msg = NULL;
+	int err;
 
-	close (fd);
-	return TRUE;
+	if (name && !name[0])
+		name = NULL;
+
+	g_return_val_if_fail (ifindex > 0 || name, -NLE_INVAL);
+
+	_LOGT ("sock: request-link %d%s%s%s", ifindex, name ? ", \"" : "", name ? name : "", name ? "\"" : "");
+
+	if ((err = rtnl_link_build_get_request (ifindex, name, &msg)) < 0)
+		return err;
+
+	_nl_msg_set_seq (sk, msg, out_seq);
+
+	err = nl_send_auto (sk, msg);
+	nlmsg_free(msg);
+	if (err < 0)
+		return err;
+
+	return 0;
 }
 
 static int
-ethtool_get_stringset_index (const char *ifname, int stringset_id, const char *string)
+_nl_sock_request_all (NMPlatform *platform, struct nl_sock *sk, NMPObjectType obj_type, guint32 *out_seq)
 {
-	gs_free struct ethtool_sset_info *info = NULL;
-	gs_free struct ethtool_gstrings *strings = NULL;
-	guint32 len, i;
+	const NMPClass *klass;
+	struct rtgenmsg gmsg = { 0 };
+	struct nl_msg *msg;
+	int err;
 
-	info = g_malloc0 (sizeof (*info) + sizeof (guint32));
-	info->cmd = ETHTOOL_GSSET_INFO;
-	info->reserved = 0;
-	info->sset_mask = 1ULL << stringset_id;
+	klass = nmp_class_from_type (obj_type);
 
-	if (!ethtool_get (ifname, info))
-		return -1;
-	if (!info->sset_mask)
-		return -1;
+	_LOGT ("sock: request-all-%s", klass->obj_type_name);
 
-	len = info->data[0];
+	/* reimplement
+	 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
+	 * because we need the sequence number.
+	 */
+	msg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+	if (!msg)
+		return -NLE_NOMEM;
+
+	gmsg.rtgen_family = klass->addr_family;
+	err = nlmsg_append (msg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
+	if (err < 0)
+		goto errout;
 
-	strings = g_malloc0 (sizeof (*strings) + len * ETH_GSTRING_LEN);
-	strings->cmd = ETHTOOL_GSTRINGS;
-	strings->string_set = stringset_id;
-	strings->len = len;
-	if (!ethtool_get (ifname, strings))
-		return -1;
+	_nl_msg_set_seq (sk, msg, out_seq);
 
-	for (i = 0; i < len; i++) {
-		if (!strcmp ((char *) &strings->data[i * ETH_GSTRING_LEN], string))
-			return i;
-	}
+	err = nl_send_auto (sk, msg);
+errout:
+	nlmsg_free(msg);
+
+	return err >= 0 ? 0 : err;
+}
+
+/******************************************************************/
 
-	return -1;
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+static int _support_user_ipv6ll = 0;
+#define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0))
+#else
+#define _support_user_ipv6ll_still_undecided() (FALSE)
+#endif
+
+static gboolean
+_support_user_ipv6ll_get (void)
+{
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+	if (_support_user_ipv6ll_still_undecided ()) {
+		_support_user_ipv6ll = -1;
+		nm_log_warn (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support");
+	} else
+		return _support_user_ipv6ll > 0;
+#endif
+
+	return FALSE;
+}
+
+static void
+_support_user_ipv6ll_detect (const struct rtnl_link *rtnl_link)
+{
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+	/* If we ever see a link with valid IPv6 link-local address
+	 * generation modes, the kernel supports it.
+	 */
+	if (_support_user_ipv6ll_still_undecided ()) {
+		uint8_t mode;
+
+		if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) rtnl_link, &mode) == 0) {
+			_support_user_ipv6ll = 1;
+			nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected");
+		} else {
+			_support_user_ipv6ll = -1;
+			nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected");
+		}
+	}
+#endif
 }
 
 /******************************************************************/
 
+static int _support_kernel_extended_ifa_flags = 0;
+
+#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == 0))
+
 static void
-_check_support_kernel_extended_ifa_flags_init (NMLinuxPlatformPrivate *priv, struct nl_msg *msg)
+_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
 {
-	struct nlmsghdr *msg_hdr = nlmsg_hdr (msg);
+	struct nlmsghdr *msg_hdr;
+
+	if (!_support_kernel_extended_ifa_flags_still_undecided ())
+		return;
 
-	g_return_if_fail (priv->support_kernel_extended_ifa_flags == 0);
-	g_return_if_fail (msg_hdr->nlmsg_type == RTM_NEWADDR);
+	msg_hdr = nlmsg_hdr (msg);
+	if (msg_hdr->nlmsg_type != RTM_NEWADDR)
+		return;
 
 	/* the extended address flags are only set for AF_INET6 */
 	if (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family != AF_INET6)
@@ -659,424 +571,507 @@ _check_support_kernel_extended_ifa_flags_init (NMLinuxPlatformPrivate *priv, str
 	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
 	 * and IFA_F_NOPREFIXROUTE (they were added together).
 	 **/
-	priv->support_kernel_extended_ifa_flags =
+	_support_kernel_extended_ifa_flags =
 	    nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), 8 /* IFA_FLAGS */)
 	    ? 1 : -1;
 }
 
 static gboolean
-check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+_support_kernel_extended_ifa_flags_get (void)
 {
-	NMLinuxPlatformPrivate *priv;
+	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
+		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for extended IFA_FLAGS. Assume no kernel support.");
+		_support_kernel_extended_ifa_flags = -1;
+	}
+	return _support_kernel_extended_ifa_flags > 0;
+}
 
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+/******************************************************************
+ * Object type specific utilities
+ ******************************************************************/
 
-	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+static guint
+_nm_ip_config_source_to_rtprot (NMIPConfigSource source)
+{
+	switch (source) {
+	case NM_IP_CONFIG_SOURCE_UNKNOWN:
+		return RTPROT_UNSPEC;
+	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;
+	case NM_IP_CONFIG_SOURCE_RDISC:
+		return RTPROT_RA;
 
-	if (priv->support_kernel_extended_ifa_flags == 0) {
-		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for extended IFA_FLAGS. Assume no kernel support.");
-		priv->support_kernel_extended_ifa_flags = -1;
+	default:
+		return RTPROT_STATIC;
 	}
-
-	return priv->support_kernel_extended_ifa_flags > 0;
 }
 
-static gboolean
-check_support_user_ipv6ll (NMPlatform *platform)
+static NMIPConfigSource
+_nm_ip_config_source_from_rtprot (guint rtprot)
 {
-	NMLinuxPlatformPrivate *priv;
+	switch (rtprot) {
+	case RTPROT_UNSPEC:
+		return NM_IP_CONFIG_SOURCE_UNKNOWN;
+	case RTPROT_KERNEL:
+		return NM_IP_CONFIG_SOURCE_RTPROT_KERNEL;
+	case RTPROT_REDIRECT:
+		return NM_IP_CONFIG_SOURCE_KERNEL;
+	case RTPROT_RA:
+		return NM_IP_CONFIG_SOURCE_RDISC;
+	case RTPROT_DHCP:
+		return NM_IP_CONFIG_SOURCE_DHCP;
 
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	default:
+		return NM_IP_CONFIG_SOURCE_USER;
+	}
+}
 
-	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+/******************************************************************/
+
+typedef struct {
+	const NMLinkType nm_type;
+	const char *type_string;
+
+	/* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type
+	 * should only be specified if the device type can be created without
+	 * additional parameters, and if the device type can be determined from
+	 * the rtnl_type.  eg, tun/tap should not be specified since both
+	 * tun and tap devices use "tun", and InfiniBand should not be
+	 * specified because a PKey is required at creation. Drivers set this
+	 * value from their 'struct rtnl_link_ops' structure.
+	 */
+	const char *rtnl_type;
+
+	/* uevent DEVTYPE where applicable, from /sys/class/net/<ifname>/uevent;
+	 * drivers set this value from their SET_NETDEV_DEV() call and the
+	 * 'struct device_type' name member.
+	 */
+	const char *devtype;
+} LinkDesc;
+
+static const LinkDesc linktypes[] = {
+	{ NM_LINK_TYPE_NONE,          "none",        NULL,          NULL },
+	{ NM_LINK_TYPE_UNKNOWN,       "unknown",     NULL,          NULL },
+
+	{ NM_LINK_TYPE_ETHERNET,      "ethernet",    NULL,          NULL },
+	{ 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_WIMAX,         "wimax",       "wimax",       "wimax" },
+
+	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
+	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
+	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
+	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         NULL },
+	{ NM_LINK_TYPE_LOOPBACK,      "loopback",    NULL,          NULL },
+	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
+	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
+	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
+	{ NM_LINK_TYPE_TAP,           "tap",         NULL,          NULL },
+	{ NM_LINK_TYPE_TUN,           "tun",         NULL,          NULL },
+	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
+	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
+	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
+	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
+
+	{ NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
+	{ NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
+	{ NM_LINK_TYPE_TEAM,          "team",        "team",        NULL },
+};
+
+static const char *
+nm_link_type_to_rtnl_type_string (NMLinkType type)
+{
+	int i;
 
-	if (priv->support_user_ipv6ll == 0) {
-		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for IFLA_INET6_ADDR_GEN_MODE. Assume no kernel support.");
-		priv->support_user_ipv6ll = -1;
+	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
+		if (type == linktypes[i].nm_type)
+			return linktypes[i].rtnl_type;
 	}
+	g_return_val_if_reached (NULL);
+}
+
+const char *
+nm_link_type_to_string (NMLinkType type)
+{
+	int i;
 
-	return priv->support_user_ipv6ll > 0;
+	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
+		if (type == linktypes[i].nm_type)
+			return linktypes[i].type_string;
+	}
+	g_return_val_if_reached (NULL);
 }
 
+/******************************************************************
+ * NMPlatform types and functions
+ ******************************************************************/
 
-/* Object type specific utilities */
+typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate;
 
-static const char *
-type_to_string (NMLinkType type)
+struct _NMLinuxPlatformPrivate {
+	struct nl_sock *nlh;
+	struct nl_sock *nlh_event;
+	guint32 nlh_seq_expect;
+	guint32 nlh_seq_last;
+	NMPCache *cache;
+	GIOChannel *event_channel;
+	guint event_id;
+
+	GUdevClient *udev_client;
+
+	struct {
+		DelayedActionType flags;
+		GPtrArray *list_master_connected;
+		GPtrArray *list_refresh_link;
+		gint is_handling;
+		guint idle_id;
+	} delayed_action;
+
+	GHashTable *prune_candidates;
+	GHashTable *delayed_deletion;
+
+	GHashTable *wifi_data;
+};
+
+static inline NMLinuxPlatformPrivate *
+NM_LINUX_PLATFORM_GET_PRIVATE (const void *self)
 {
-	/* Note that this only has to support virtual types */
-	switch (type) {
-	case NM_LINK_TYPE_DUMMY:
-		return "dummy";
-	case NM_LINK_TYPE_GRE:
-		return "gre";
-	case NM_LINK_TYPE_GRETAP:
-		return "gretap";
-	case NM_LINK_TYPE_IFB:
-		return "ifb";
-	case NM_LINK_TYPE_MACVLAN:
-		return "macvlan";
-	case NM_LINK_TYPE_MACVTAP:
-		return "macvtap";
-	case NM_LINK_TYPE_TAP:
-		return "tap";
-	case NM_LINK_TYPE_TUN:
-		return "tun";
-	case NM_LINK_TYPE_VETH:
-		return "veth";
-	case NM_LINK_TYPE_VLAN:
-		return "vlan";
-	case NM_LINK_TYPE_VXLAN:
-		return "vxlan";
-	case NM_LINK_TYPE_BRIDGE:
-		return "bridge";
-	case NM_LINK_TYPE_BOND:
-		return "bond";
-	case NM_LINK_TYPE_TEAM:
-		return "team";
-	default:
-		g_warning ("Wrong type: %d", type);
-		return NULL;
-	}
+	nm_assert (NM_IS_LINUX_PLATFORM (self));
+
+	return ((NMLinuxPlatform *) self)->priv;
 }
 
-#define return_type(t, name) \
-	G_STMT_START { \
-		if (out_name) \
-			*out_name = name; \
-		return t; \
-	} G_STMT_END
+G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
 
-static NMLinkType
-link_type_from_udev (NMPlatform *platform, int ifindex, const char *ifname, int arptype, const char **out_name)
+void
+nm_linux_platform_setup (void)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GUdevDevice *udev_device;
-	const char *prop, *sysfs_path;
+	g_object_new (NM_TYPE_LINUX_PLATFORM,
+	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
+	              NULL);
+}
 
-	g_assert (ifname);
+/******************************************************************/
 
-	udev_device = g_hash_table_lookup (priv->udev_devices, GINT_TO_POINTER (ifindex));
-	if (!udev_device)
-		return_type (NM_LINK_TYPE_UNKNOWN, "unknown");
+NMPObjectType
+_nlo_get_object_type (const struct nl_object *object)
+{
+	const char *type_str;
+
+	if (!object || !(type_str = nl_object_get_type (object)))
+		return NMP_OBJECT_TYPE_UNKNOWN;
 
-	if (   g_udev_device_get_property (udev_device, "ID_NM_OLPC_MESH")
-	    || g_udev_device_get_sysfs_attr (udev_device, "anycast_mask"))
-		return_type (NM_LINK_TYPE_OLPC_MESH, "olpc-mesh");
+	if (!strcmp (type_str, "route/link"))
+		return NMP_OBJECT_TYPE_LINK;
+	else if (!strcmp (type_str, "route/addr")) {
+		switch (rtnl_addr_get_family ((struct rtnl_addr *) object)) {
+		case AF_INET:
+			return NMP_OBJECT_TYPE_IP4_ADDRESS;
+		case AF_INET6:
+			return NMP_OBJECT_TYPE_IP6_ADDRESS;
+		default:
+			return NMP_OBJECT_TYPE_UNKNOWN;
+		}
+	} else if (!strcmp (type_str, "route/route")) {
+		switch (rtnl_route_get_family ((struct rtnl_route *) object)) {
+		case AF_INET:
+			return NMP_OBJECT_TYPE_IP4_ROUTE;
+		case AF_INET6:
+			return NMP_OBJECT_TYPE_IP6_ROUTE;
+		default:
+			return NMP_OBJECT_TYPE_UNKNOWN;
+		}
+	} else
+		return NMP_OBJECT_TYPE_UNKNOWN;
+}
 
-	prop = g_udev_device_get_property (udev_device, "DEVTYPE");
-	sysfs_path = g_udev_device_get_sysfs_path (udev_device);
-	if (wifi_utils_is_wifi (ifname, sysfs_path, prop))
-		return_type (NM_LINK_TYPE_WIFI, "wifi");
-	else if (g_strcmp0 (prop, "wwan") == 0)
-		return_type (NM_LINK_TYPE_WWAN_ETHERNET, "wwan");
-	else if (g_strcmp0 (prop, "wimax") == 0)
-		return_type (NM_LINK_TYPE_WIMAX, "wimax");
+/******************************************************************/
 
-	if (arptype == ARPHRD_ETHER)
-		return_type (NM_LINK_TYPE_ETHERNET, "ethernet");
+static gboolean
+check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
 
-	return_type (NM_LINK_TYPE_UNKNOWN, "unknown");
+	return _support_kernel_extended_ifa_flags_get ();
 }
 
 static gboolean
-link_is_software (struct rtnl_link *rtnllink)
+check_support_user_ipv6ll (NMPlatform *platform)
 {
-	const char *type;
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
 
-	/* FIXME: replace somehow with NMLinkType or nm_platform_is_software(), but
-	 * solve the infinite callstack problems that getting the type of a TUN/TAP
-	 * device causes.
-	 */
+	return _support_user_ipv6ll_get ();
+}
 
-	if (   rtnl_link_get_arptype (rtnllink) == ARPHRD_INFINIBAND
-	    && strchr (rtnl_link_get_name (rtnllink), '.'))
-		return TRUE;
+static void
+process_events (NMPlatform *platform)
+{
+	delayed_action_handle_all (platform, TRUE);
+}
 
-	type = rtnl_link_get_type (rtnllink);
-	if (type == NULL)
-		return FALSE;
+/******************************************************************/
 
-	if (!strcmp (type, "dummy") ||
-	    !strcmp (type, "gre") ||
-	    !strcmp (type, "gretap") ||
-	    !strcmp (type, "macvlan") ||
-	    !strcmp (type, "macvtap") ||
-	    !strcmp (type, "tun") ||
-	    !strcmp (type, "veth") ||
-	    !strcmp (type, "vlan") ||
-	    !strcmp (type, "vxlan") ||
-	    !strcmp (type, "bridge") ||
-	    !strcmp (type, "bond") ||
-	    !strcmp (type, "team"))
-		return TRUE;
+#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
+	((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
+	                                              nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \
+	                                              NULL))
 
-	return FALSE;
-}
 
-static const char *
-ethtool_get_driver (const char *ifname)
-{
-	struct ethtool_drvinfo drvinfo = { 0 };
+/******************************************************************/
 
-	g_return_val_if_fail (ifname != NULL, NULL);
+#define DEVTYPE_PREFIX "DEVTYPE="
 
-	drvinfo.cmd = ETHTOOL_GDRVINFO;
-	if (!ethtool_get (ifname, &drvinfo))
-		return NULL;
+static char *
+read_devtype (const char *sysfs_path)
+{
+	gs_free char *uevent = g_strdup_printf ("%s/uevent", sysfs_path);
+	char *contents = NULL;
+	char *cont, *end;
 
-	if (!*drvinfo.driver)
+	if (!g_file_get_contents (uevent, &contents, NULL, NULL))
 		return NULL;
-
-	return g_intern_string (drvinfo.driver);
+	for (cont = contents; cont; cont = end) {
+		end = strpbrk (cont, "\r\n");
+		if (end)
+			*end++ = '\0';
+		if (strncmp (cont, DEVTYPE_PREFIX, STRLEN (DEVTYPE_PREFIX)) == 0) {
+			cont += STRLEN (DEVTYPE_PREFIX);
+			memmove (contents, cont, strlen (cont) + 1);
+			return contents;
+		}
+	}
+	g_free (contents);
+	return NULL;
 }
 
-static gboolean
-link_is_announceable (NMPlatform *platform, struct rtnl_link *rtnllink)
+static const NMPObject *
+_lookup_link_cached (NMPlatform *platform, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj;
 
-	/* Software devices are always visible outside the platform */
-	if (link_is_software (rtnllink))
-		return TRUE;
+	nm_assert (completed_from_cache && link_cached);
 
-	/* Hardware devices must be found by udev so rules get run and tags set */
-	if (g_hash_table_lookup (priv->udev_devices,
-	                         GINT_TO_POINTER (rtnl_link_get_ifindex (rtnllink))))
-		return TRUE;
+	if (!*completed_from_cache) {
+		obj = ifindex > 0 ? nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex) : NULL;
 
-	return FALSE;
+		if (obj && !obj->_link.netlink.is_in_netlink)
+			*link_cached = obj;
+		else
+			*link_cached = NULL;
+		*completed_from_cache = TRUE;
+	}
+	return *link_cached;
 }
 
 static NMLinkType
-link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, const char **out_name)
+link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *completed_from_cache, const NMPObject **link_cached, const char **out_kind)
 {
-	const char *type;
+	const char *rtnl_type, *ifname;
+	int i, arptype;
 
-	if (!rtnllink)
-		return_type (NM_LINK_TYPE_NONE, NULL);
+	if (!rtnllink) {
+		if (out_kind)
+			*out_kind = NULL;
+		return NM_LINK_TYPE_NONE;
+	}
+
+	rtnl_type = rtnl_link_get_type (rtnllink);
+	if (!rtnl_type && completed_from_cache) {
+		const NMPObject *obj;
+
+		obj = _lookup_link_cached (platform, rtnl_link_get_ifindex (rtnllink), completed_from_cache, link_cached);
+		if (obj && obj->link.kind) {
+			rtnl_type = obj->link.kind;
+			_LOGT ("link_extract_type(): complete kind from cache: ifindex=%d, kind=%s", rtnl_link_get_ifindex (rtnllink), rtnl_type);
+		}
+	}
+	if (out_kind)
+		*out_kind = rtnl_type;
+	if (rtnl_type) {
+		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
+			if (g_strcmp0 (rtnl_type, linktypes[i].rtnl_type) == 0)
+				return linktypes[i].nm_type;
+		}
 
-	type = rtnl_link_get_type (rtnllink);
+		if (!strcmp (rtnl_type, "tun")) {
+			NMPlatformTunProperties props;
+			guint flags;
 
-	if (!type) {
-		int arptype = rtnl_link_get_arptype (rtnllink);
-		const char *driver;
-		const char *ifname;
+			if (tun_get_properties_ifname (platform, rtnl_link_get_name (rtnllink), &props)) {
+				if (!g_strcmp0 (props.mode, "tap"))
+					return NM_LINK_TYPE_TAP;
+				if (!g_strcmp0 (props.mode, "tun"))
+					return NM_LINK_TYPE_TUN;
+			}
+			flags = rtnl_link_get_flags (rtnllink);
+
+			nm_log_dbg (LOGD_PLATFORM, "Failed to read tun properties for interface %d (link flags: %X)",
+			            rtnl_link_get_ifindex (rtnllink), flags);
+
+			/* try guessing the type using the link flags instead... */
+			if (flags & IFF_POINTOPOINT)
+				return NM_LINK_TYPE_TUN;
+			return NM_LINK_TYPE_TAP;
+		}
+	}
 
-		if (arptype == ARPHRD_LOOPBACK)
-			return_type (NM_LINK_TYPE_LOOPBACK, "loopback");
-		else if (arptype == ARPHRD_INFINIBAND)
-			return_type (NM_LINK_TYPE_INFINIBAND, "infiniband");
+	arptype = rtnl_link_get_arptype (rtnllink);
+	if (arptype == ARPHRD_LOOPBACK)
+		return NM_LINK_TYPE_LOOPBACK;
+	else if (arptype == ARPHRD_INFINIBAND)
+		return NM_LINK_TYPE_INFINIBAND;
 
-		ifname = rtnl_link_get_name (rtnllink);
-		if (!ifname)
-			return_type (NM_LINK_TYPE_UNKNOWN, type);
+	ifname = rtnl_link_get_name (rtnllink);
+	if (ifname) {
+		gs_free char *driver = NULL;
+		gs_free char *sysfs_path = NULL;
+		gs_free char *anycast_mask = NULL;
+		gs_free char *devtype = NULL;
 
-		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.
 			 */
 			if (!g_strcmp0 (driver, "ctcm"))
-				return_type (NM_LINK_TYPE_ETHERNET, "ethernet");
+				return NM_LINK_TYPE_ETHERNET;
 		}
 
-		if (!g_strcmp0 (driver, "openvswitch"))
-			return_type (NM_LINK_TYPE_OPENVSWITCH, "openvswitch");
-
-		return link_type_from_udev (platform,
-		                            rtnl_link_get_ifindex (rtnllink),
-		                            ifname,
-		                            arptype,
-		                            out_name);
-	} else if (!strcmp (type, "dummy"))
-		return_type (NM_LINK_TYPE_DUMMY, "dummy");
-	else if (!strcmp (type, "gre"))
-		return_type (NM_LINK_TYPE_GRE, "gre");
-	else if (!strcmp (type, "gretap"))
-		return_type (NM_LINK_TYPE_GRETAP, "gretap");
-	else if (!strcmp (type, "ifb"))
-		return_type (NM_LINK_TYPE_IFB, "ifb");
-	else if (!strcmp (type, "macvlan"))
-		return_type (NM_LINK_TYPE_MACVLAN, "macvlan");
-	else if (!strcmp (type, "macvtap"))
-		return_type (NM_LINK_TYPE_MACVTAP, "macvtap");
-	else if (!strcmp (type, "tun")) {
-		NMPlatformTunProperties props;
-		guint flags;
-
-		if (nm_platform_tun_get_properties (rtnl_link_get_ifindex (rtnllink), &props)) {
-			if (!g_strcmp0 (props.mode, "tap"))
-				return_type (NM_LINK_TYPE_TAP, "tap");
-			if (!g_strcmp0 (props.mode, "tun"))
-				return_type (NM_LINK_TYPE_TUN, "tun");
+		/* Fallback OVS detection for kernel <= 3.16 */
+		if (nmp_utils_ethtool_get_driver_info (ifname, &driver, NULL, NULL)) {
+			if (!g_strcmp0 (driver, "openvswitch"))
+				return NM_LINK_TYPE_OPENVSWITCH;
 		}
-		flags = rtnl_link_get_flags (rtnllink);
-
-		nm_log_dbg (LOGD_PLATFORM, "Failed to read tun properties for interface %d (link flags: %X)",
-		                           rtnl_link_get_ifindex (rtnllink), flags);
-
-		/* try guessing the type using the link flags instead... */
-		if (flags & IFF_POINTOPOINT)
-			return_type (NM_LINK_TYPE_TUN, "tun");
-		return_type (NM_LINK_TYPE_TAP, "tap");
-	} else if (!strcmp (type, "veth"))
-		return_type (NM_LINK_TYPE_VETH, "veth");
-	else if (!strcmp (type, "vlan"))
-		return_type (NM_LINK_TYPE_VLAN, "vlan");
-	else if (!strcmp (type, "vxlan"))
-		return_type (NM_LINK_TYPE_VXLAN, "vxlan");
-	else if (!strcmp (type, "bridge"))
-		return_type (NM_LINK_TYPE_BRIDGE, "bridge");
-	else if (!strcmp (type, "bond"))
-		return_type (NM_LINK_TYPE_BOND, "bond");
-	else if (!strcmp (type, "team"))
-		return_type (NM_LINK_TYPE_TEAM, "team");
-
-	return_type (NM_LINK_TYPE_UNKNOWN, type);
-}
 
-static const char *
-udev_get_driver (NMPlatform *platform, GUdevDevice *device, int ifindex)
-{
-	GUdevDevice *parent = NULL, *grandparent = NULL;
-	const char *driver, *subsys;
-
-	driver = g_udev_device_get_driver (device);
-	if (driver)
-		return driver;
-
-	/* Try the parent */
-	parent = g_udev_device_get_parent (device);
-	if (parent) {
-		driver = g_udev_device_get_driver (parent);
-		if (!driver) {
-			/* Try the grandparent if it's an ibmebus device or if the
-			 * subsys is NULL which usually indicates some sort of
-			 * platform device like a 'gadget' net interface.
-			 */
-			subsys = g_udev_device_get_subsystem (parent);
-			if (   (g_strcmp0 (subsys, "ibmebus") == 0)
-			    || (subsys == NULL)) {
-				grandparent = g_udev_device_get_parent (parent);
-				if (grandparent) {
-					driver = g_udev_device_get_driver (grandparent);
+		sysfs_path = g_strdup_printf ("/sys/class/net/%s", ifname);
+		anycast_mask = g_strdup_printf ("%s/anycast_mask", sysfs_path);
+		if (g_file_test (anycast_mask, G_FILE_TEST_EXISTS))
+			return NM_LINK_TYPE_OLPC_MESH;
+
+		devtype = read_devtype (sysfs_path);
+		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) {
+					/* Both BNEP and 6lowpan use DEVTYPE=bluetooth, so we must
+					 * use arptype to distinguish between them.
+					 */
+					if (arptype != ARPHRD_ETHER)
+						continue;
 				}
+				return linktypes[i].nm_type;
 			}
 		}
-	}
 
-	/* Intern the string so we don't have to worry about memory
-	 * management in NMPlatformLink.
-	 */
-	if (driver)
-		driver = g_intern_string (driver);
+		/* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */
+		if (wifi_utils_is_wifi (ifname, sysfs_path))
+			return NM_LINK_TYPE_WIFI;
 
-	g_clear_object (&parent);
-	g_clear_object (&grandparent);
+		/* 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
+		 * when they should be Generic instead.
+		 */
+		if (arptype == ARPHRD_ETHER && !rtnl_type && !devtype)
+			return NM_LINK_TYPE_ETHERNET;
+	}
 
-	return driver;
+	return NM_LINK_TYPE_UNKNOWN;
 }
 
-static gboolean
-init_link (NMPlatform *platform, NMPlatformLink *info, struct rtnl_link *rtnllink)
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GUdevDevice *udev_device;
+	NMPlatformLink *obj = (NMPlatformLink *) _obj;
+	NMPObjectLink *obj_priv = (NMPObjectLink *) _obj;
+	struct rtnl_link *nlo = (struct rtnl_link *) _nlo;
 	const char *name;
+	struct nl_addr *nladdr;
+	const char *kind;
+	gboolean completed_from_cache_val = FALSE;
+	gboolean *completed_from_cache = complete_from_cache ? &completed_from_cache_val : NULL;
+	const NMPObject *link_cached = NULL;
 
-	g_return_val_if_fail (rtnllink, FALSE);
+	nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
 
-	name = rtnl_link_get_name (rtnllink);
-	memset (info, 0, sizeof (*info));
+	if (_LOGT_ENABLED () && !NM_IN_SET (rtnl_link_get_family (nlo), AF_UNSPEC, AF_BRIDGE))
+		_LOGT ("netlink object for ifindex %d has unusual family %d", rtnl_link_get_ifindex (nlo), rtnl_link_get_family (nlo));
 
-	info->ifindex = rtnl_link_get_ifindex (rtnllink);
-	if (name)
-		g_strlcpy (info->name, name, sizeof (info->name));
-	else
-		info->name[0] = '\0';
-	info->type = link_extract_type (platform, rtnllink, &info->type_name);
-	info->up = !!(rtnl_link_get_flags (rtnllink) & IFF_UP);
-	info->connected = !!(rtnl_link_get_flags (rtnllink) & IFF_LOWER_UP);
-	info->arp = !(rtnl_link_get_flags (rtnllink) & IFF_NOARP);
-	info->master = rtnl_link_get_master (rtnllink);
-	info->parent = rtnl_link_get_link (rtnllink);
-	info->mtu = rtnl_link_get_mtu (rtnllink);
-
-	udev_device = g_hash_table_lookup (priv->udev_devices, GINT_TO_POINTER (info->ifindex));
-	if (udev_device) {
-		info->driver = udev_get_driver (platform, udev_device, info->ifindex);
-		if (!info->driver)
-			info->driver = g_intern_string (rtnl_link_get_type (rtnllink));
-		if (!info->driver)
-			info->driver = ethtool_get_driver (info->name);
-		if (!info->driver)
-			info->driver = "unknown";
-		info->udi = g_udev_device_get_sysfs_path (udev_device);
-	}
+	obj->ifindex = rtnl_link_get_ifindex (nlo);
 
-	return TRUE;
-}
+	if (id_only)
+		return TRUE;
 
-/* Hack: Empty bridges and bonds have IFF_LOWER_UP flag and therefore they break
- * the carrier detection. This hack makes nm-platform think they don't have the
- * IFF_LOWER_UP flag. This seems to also apply to bonds (specifically) with all
- * slaves down.
- *
- * Note: This is still a bit racy but when NetworkManager asks for enslaving a slave,
- * nm-platform will do that synchronously and will immediately ask for both master
- * and slave information after the enslaving request. After the synchronous call, the
- * master carrier is already updated with the slave carrier in mind.
- *
- * https://bugzilla.redhat.com/show_bug.cgi?id=910348
- */
-static void
-hack_empty_master_iff_lower_up (NMPlatform *platform, struct nl_object *object)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct rtnl_link *rtnllink;
-	int ifindex;
-	struct nl_object *slave;
-	const char *type;
+	name = rtnl_link_get_name (nlo);
+	if (name)
+		g_strlcpy (obj->name, name, sizeof (obj->name));
+	obj->type = link_extract_type (platform, nlo, completed_from_cache, &link_cached, &kind);
+	obj->kind = g_intern_string (kind);
+	obj->flags = rtnl_link_get_flags (nlo);
+	obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP);
+	obj->master = rtnl_link_get_master (nlo);
+	obj->parent = rtnl_link_get_link (nlo);
+	obj->mtu = rtnl_link_get_mtu (nlo);
+	obj->arptype = rtnl_link_get_arptype (nlo);
+
+	if (obj->type == NM_LINK_TYPE_VLAN) {
+		if (!g_strcmp0 (rtnl_link_get_type (nlo), "vlan"))
+			obj->vlan_id = rtnl_link_vlan_get_id (nlo);
+		else if (completed_from_cache) {
+			_lookup_link_cached (platform, obj->ifindex, completed_from_cache, &link_cached);
+			if (link_cached)
+				obj->vlan_id = link_cached->link.vlan_id;
+		}
+	}
 
-	if (!object)
-		return;
-	if (strcmp (nl_object_get_type (object), "route/link"))
-		return;
+	if ((nladdr = rtnl_link_get_addr (nlo))) {
+		unsigned int l = 0;
 
-	rtnllink = (struct rtnl_link *) object;
+		l = nl_addr_get_len (nladdr);
+		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
+			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data));
+			memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l);
+			obj->addr.len = l;
+		}
+	}
 
-	ifindex = rtnl_link_get_ifindex (rtnllink);
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+	if (_support_user_ipv6ll_get ()) {
+		guint8 mode = 0;
 
-	type = rtnl_link_get_type (rtnllink);
-	if (!type || (strcmp (type, "bridge") != 0 && strcmp (type, "bond") != 0))
-		return;
+		if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0)
+			obj->inet6_addr_gen_mode_inv = _nm_platform_uint8_inv (mode);
+	}
+#endif
 
-	for (slave = nl_cache_get_first (priv->link_cache); slave; slave = nl_cache_get_next (slave)) {
-		struct rtnl_link *rtnlslave = (struct rtnl_link *) slave;
-		if (rtnl_link_get_master (rtnlslave) == ifindex
-				&& rtnl_link_get_flags (rtnlslave) & IFF_LOWER_UP)
-			return;
+#if HAVE_LIBNL_INET6_TOKEN
+	if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) {
+		if (   nl_addr_get_family (nladdr) == AF_INET6
+		    && nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) {
+			struct in6_addr *addr;
+			NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid;
+
+			addr = nl_addr_get_binary_addr (nladdr);
+			iid->id_u8[7] = addr->s6_addr[15];
+			iid->id_u8[6] = addr->s6_addr[14];
+			iid->id_u8[5] = addr->s6_addr[13];
+			iid->id_u8[4] = addr->s6_addr[12];
+			iid->id_u8[3] = addr->s6_addr[11];
+			iid->id_u8[2] = addr->s6_addr[10];
+			iid->id_u8[1] = addr->s6_addr[9];
+			iid->id_u8[0] = addr->s6_addr[8];
+			obj->inet6_token.is_valid = TRUE;
+		}
+		nl_addr_put (nladdr);
 	}
+#endif
 
-	rtnl_link_unset_flags (rtnllink, IFF_LOWER_UP);
-}
+	obj_priv->netlink.is_in_netlink = TRUE;
 
-static guint32
-_get_remaining_time (guint32 start_timestamp, guint32 end_timestamp)
-{
-	/* Return the remaining time between @start_timestamp until @end_timestamp.
-	 *
-	 * If @end_timestamp is NM_PLATFORM_LIFETIME_PERMANENT, it returns
-	 * NM_PLATFORM_LIFETIME_PERMANENT. If @start_timestamp already passed
-	 * @end_timestamp it returns 0. Beware, NMPlatformIPAddress treats a @lifetime
-	 * of 0 as permanent.
-	 */
-	if (end_timestamp == NM_PLATFORM_LIFETIME_PERMANENT)
-		return NM_PLATFORM_LIFETIME_PERMANENT;
-	if (start_timestamp >= end_timestamp)
-		return 0;
-	return end_timestamp - start_timestamp;
+	return TRUE;
 }
 
 /* _timestamp_nl_to_ms:
@@ -1112,15 +1107,18 @@ _timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
 }
 
 static guint32
-_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr)
+_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out_now_nm)
 {
 	guint32 last_update_time = rtnl_addr_get_last_update_time ((struct rtnl_addr *) rtnladdr);
 	struct timespec tp;
 	gint64 now_nl, now_nm, result;
 
 	/* timestamp is unset. Default to 1. */
-	if (!last_update_time)
+	if (!last_update_time) {
+		if (out_now_nm)
+			*out_now_nm = 0;
 		return 1;
+	}
 
 	/* do all the calculations in milliseconds scale */
 
@@ -1131,6 +1129,9 @@ _rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr)
 
 	result = now_nm - (now_nl - _timestamp_nl_to_ms (last_update_time, now_nl));
 
+	if (out_now_nm)
+		*out_now_nm = now_nm / 1000;
+
 	/* converting the last_update_time into nm_utils_get_monotonic_timestamp_ms() scale is
 	 * a good guess but fails in the following situations:
 	 *
@@ -1148,686 +1149,952 @@ _rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr)
 	return result / 1000;
 }
 
-static void
-_init_ip_address_lifetime (NMPlatformIPAddress *address, const struct rtnl_addr *rtnladdr)
+static guint32
+_extend_lifetime (guint32 lifetime, guint32 seconds)
 {
-	guint32 a_valid = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
-	guint32 a_preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
-
-	/* the meaning of the valid and preferred lifetimes is different from the
-	 * original meaning. See _rtnl_addr_hack_lifetimes_rel_to_abs().
-	 * Beware: this function expects hacked rtnl_addr objects.
-	 */
+	guint64 v;
 
-	if (a_valid == NM_PLATFORM_LIFETIME_PERMANENT &&
-	    a_preferred == NM_PLATFORM_LIFETIME_PERMANENT) {
-		address->timestamp = 0;
-		address->lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		address->preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-		return;
-	}
+	if (   lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    || seconds == 0)
+		return lifetime;
 
-	/* The valies are hacked and absolute expiry times. They must
-	 * be positive and preferred<=valid. */
-	g_assert (a_preferred <= a_valid &&
-	          a_valid > 0 &&
-	          a_preferred > 0);
-
-	if (a_valid <= 1) {
-		/* Since we want to have positive @timestamp and @valid != 0,
-		 * we must handle this case special. */
-		address->timestamp = 1;
-		address->lifetime = 1; /* Extend the lifetime by one second */
-		address->preferred = 0; /* no longer preferred. */
-		return;
-	}
+	v = (guint64) lifetime + (guint64) seconds;
+	return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+}
 
-	/* _rtnl_addr_last_update_time_to_nm() might be wrong, so don't rely on
-	 * timestamp to have any meaning beyond anchoring the relative durations
-	 * @lifetime and @preferred.
-	 */
-	address->timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr);
+/* The rtnl_addr object contains relative lifetimes @valid and @preferred
+ * that count in seconds, starting from the moment when the kernel constructed
+ * the netlink message.
+ *
+ * There is also a field rtnl_addr_last_update_time(), which is the absolute
+ * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
+ * was modified (wrapping every 497 days).
+ * Immediately at the time when the address was last modified, #NOW and @last_update_time
+ * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
+ * However, this is not true in general. As time goes by, whenever kernel sends a new address
+ * via netlink, the lifetimes keep counting down.
+ **/
+static void
+_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr,
+                              guint32 *out_timestamp,
+                              guint32 *out_lifetime,
+                              guint32 *out_preferred)
+{
+	guint32 timestamp = 0;
+	gint32 now;
+	guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
+	guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
+
+	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
+	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
+		if (preferred > lifetime)
+			preferred = lifetime;
+		timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now);
+
+		if (now == 0) {
+			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
+			nm_assert (timestamp == 1);
+			now = nm_utils_get_monotonic_timestamp_s ();
+		}
+		if (timestamp < now) {
+			guint32 diff = now - timestamp;
 
-	/* We would expect @timestamp to be less then @a_valid. Just to be sure,
-	 * fix it up. */
-	address->timestamp = MIN (address->timestamp, a_valid - 1);
-	address->lifetime = _get_remaining_time (address->timestamp, a_valid);
-	address->preferred = _get_remaining_time (address->timestamp, a_preferred);
+			lifetime = _extend_lifetime (lifetime, diff);
+			preferred = _extend_lifetime (preferred, diff);
+		} else
+			nm_assert (timestamp == now);
+	}
+	*out_timestamp = timestamp;
+	*out_lifetime = lifetime;
+	*out_preferred = preferred;
 }
 
-static gboolean
-init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
 {
-	struct nl_addr *nladdr = rtnl_addr_get_local (rtnladdr);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (rtnladdr);
+	NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj;
+	struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
+	struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
+	struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
 	const char *label;
 
-	g_return_val_if_fail (nladdr, FALSE);
+	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
+		g_return_val_if_reached (FALSE);
 
-	memset (address, 0, sizeof (*address));
+	obj->ifindex = rtnl_addr_get_ifindex (nlo);
+	obj->plen = rtnl_addr_get_prefixlen (nlo);
+	memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
 
-	address->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	address->ifindex = rtnl_addr_get_ifindex (rtnladdr);
-	address->plen = rtnl_addr_get_prefixlen (rtnladdr);
-	_init_ip_address_lifetime ((NMPlatformIPAddress *) address, rtnladdr);
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) {
-		g_return_val_if_reached (FALSE);
-		return FALSE;
-	}
-	memcpy (&address->address, nl_addr_get_binary_addr (nladdr), sizeof (address->address));
+	if (id_only)
+		return TRUE;
+
+	obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
+	_nlo_rtnl_addr_get_lifetimes (nlo,
+	                              &obj->timestamp,
+	                              &obj->lifetime,
+	                              &obj->preferred);
 	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (address->peer_address)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&address->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (address->peer_address));
+		if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
+			g_warn_if_reached ();
+		else
+			memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
 	}
-	label = rtnl_addr_get_label (rtnladdr);
+	label = rtnl_addr_get_label (nlo);
 	/* Check for ':'; we're only interested in labels used as interface aliases */
 	if (label && strchr (label, ':'))
-		g_strlcpy (address->label, label, sizeof (address->label));
+		g_strlcpy (obj->label, label, sizeof (obj->label));
 
 	return TRUE;
 }
 
-static gboolean
-init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
 {
-	struct nl_addr *nladdr = rtnl_addr_get_local (rtnladdr);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (rtnladdr);
+	NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj;
+	struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
+	struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
+	struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
 
-	memset (address, 0, sizeof (*address));
-
-	address->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	address->ifindex = rtnl_addr_get_ifindex (rtnladdr);
-	address->plen = rtnl_addr_get_prefixlen (rtnladdr);
-	_init_ip_address_lifetime ((NMPlatformIPAddress *) address, rtnladdr);
-	address->flags = rtnl_addr_get_flags (rtnladdr);
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) {
+	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
 		g_return_val_if_reached (FALSE);
-		return FALSE;
-	}
-	memcpy (&address->address, nl_addr_get_binary_addr (nladdr), sizeof (address->address));
-	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (address->peer_address)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&address->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (address->peer_address));
-	}
 
-	return TRUE;
-}
+	obj->ifindex = rtnl_addr_get_ifindex (nlo);
+	obj->plen = rtnl_addr_get_prefixlen (nlo);
+	memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
 
-static guint
-source_to_rtprot (NMIPConfigSource source)
-{
-	switch (source) {
-	case NM_IP_CONFIG_SOURCE_UNKNOWN:
-		return RTPROT_UNSPEC;
-	case NM_IP_CONFIG_SOURCE_KERNEL:
-		return RTPROT_KERNEL;
-	case NM_IP_CONFIG_SOURCE_DHCP:
-		return RTPROT_DHCP;
-	case NM_IP_CONFIG_SOURCE_RDISC:
-		return RTPROT_RA;
+	if (id_only)
+		return TRUE;
 
-	default:
-		return RTPROT_STATIC;
-	}
-}
+	obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
+	_nlo_rtnl_addr_get_lifetimes (nlo,
+	                              &obj->timestamp,
+	                              &obj->lifetime,
+	                              &obj->preferred);
+	obj->flags = rtnl_addr_get_flags (nlo);
 
-static NMIPConfigSource
-rtprot_to_source (guint rtprot)
-{
-	switch (rtprot) {
-	case RTPROT_UNSPEC:
-		return NM_IP_CONFIG_SOURCE_UNKNOWN;
-	case RTPROT_REDIRECT:
-	case RTPROT_KERNEL:
-		return NM_IP_CONFIG_SOURCE_KERNEL;
-	case RTPROT_RA:
-		return NM_IP_CONFIG_SOURCE_RDISC;
-	case RTPROT_DHCP:
-		return NM_IP_CONFIG_SOURCE_DHCP;
-
-	default:
-		return NM_IP_CONFIG_SOURCE_USER;
+	if (nlpeer) {
+		if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
+			g_warn_if_reached ();
+		else
+			memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
 	}
-}
 
-static gboolean
-_rtnl_route_is_default (const struct rtnl_route *rtnlroute)
-{
-	struct nl_addr *dst;
-
-	return    rtnlroute
-	       && (dst = rtnl_route_get_dst ((struct rtnl_route *) rtnlroute))
-	       && nl_addr_get_prefixlen (dst) == 0;
+	return TRUE;
 }
 
-static gboolean
-init_ip4_route (NMPlatformIP4Route *route, struct rtnl_route *rtnlroute)
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
 {
+	NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj;
+	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
 	struct nl_addr *dst, *gw;
 	struct rtnl_nexthop *nexthop;
+	struct nl_addr *pref_src;
 
-	memset (route, 0, sizeof (*route));
-
-	/* Multi-hop routes not supported. */
-	if (rtnl_route_get_nnexthops (rtnlroute) != 1)
+	if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
+	    rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
+	    rtnl_route_get_tos (nlo) != 0 ||
+	    rtnl_route_get_nnexthops (nlo) != 1)
 		return FALSE;
 
-	nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
-	dst = rtnl_route_get_dst (rtnlroute);
-	gw = rtnl_route_nh_get_gateway (nexthop);
+	nexthop = rtnl_route_nexthop_n (nlo, 0);
+	if (!nexthop)
+		g_return_val_if_reached (FALSE);
+
+	dst = rtnl_route_get_dst (nlo);
+	if (!dst)
+		g_return_val_if_reached (FALSE);
 
-	route->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	route->plen = nl_addr_get_prefixlen (dst);
-	/* Workaround on previous workaround for libnl default route prefixlen bug. */
 	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (route->network)) {
+		if (nl_addr_get_len (dst) != sizeof (obj->network))
 			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->network, nl_addr_get_binary_addr (dst), sizeof (route->network));
+		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
 	}
+	obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
+	obj->plen = nl_addr_get_prefixlen (dst);
+	obj->metric = rtnl_route_get_priority (nlo);
+	obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo));
+
+	gw = rtnl_route_nh_get_gateway (nexthop);
 	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (route->network)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->gateway, nl_addr_get_binary_addr (gw), sizeof (route->gateway));
+		if (nl_addr_get_len (gw) != sizeof (obj->gateway))
+			g_warn_if_reached ();
+		else
+			memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+	}
+	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
+	if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) {
+		/* we must not straight way reject cloned routes, because we might have cached
+		 * a non-cloned route. If we now receive an update of the route with the route
+		 * being cloned, we must still return the object, so that we can remove the old
+		 * one from the cache.
+		 *
+		 * This happens, because this route is not nmp_object_is_alive().
+		 * */
+		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+	} else
+		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
+
+	pref_src = rtnl_route_get_pref_src (nlo);
+	if (pref_src) {
+		if (nl_addr_get_len (pref_src) != sizeof (obj->pref_src))
+			g_warn_if_reached ();
+		else
+			memcpy (&obj->pref_src, nl_addr_get_binary_addr (pref_src), sizeof (obj->pref_src));
 	}
-	route->metric = rtnl_route_get_priority (rtnlroute);
-	rtnl_route_get_metric (rtnlroute, RTAX_ADVMSS, &route->mss);
-	route->source = rtprot_to_source (rtnl_route_get_protocol (rtnlroute));
 
 	return TRUE;
 }
 
-static gboolean
-init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute)
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
 {
+	NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj;
+	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
 	struct nl_addr *dst, *gw;
 	struct rtnl_nexthop *nexthop;
 
-	memset (route, 0, sizeof (*route));
-
-	/* Multi-hop routes not supported. */
-	if (rtnl_route_get_nnexthops (rtnlroute) != 1)
+	if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
+	    rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
+	    rtnl_route_get_tos (nlo) != 0 ||
+	    rtnl_route_get_nnexthops (nlo) != 1)
 		return FALSE;
 
-	nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
-	dst = rtnl_route_get_dst (rtnlroute);
-	gw = rtnl_route_nh_get_gateway (nexthop);
+	nexthop = rtnl_route_nexthop_n (nlo, 0);
+	if (!nexthop)
+		g_return_val_if_reached (FALSE);
+
+	dst = rtnl_route_get_dst (nlo);
+	if (!dst)
+		g_return_val_if_reached (FALSE);
 
-	route->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	route->plen = nl_addr_get_prefixlen (dst);
-	/* Workaround on previous workaround for libnl default route prefixlen bug. */
 	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (route->network)) {
+		if (nl_addr_get_len (dst) != sizeof (obj->network))
 			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->network, nl_addr_get_binary_addr (dst), sizeof (route->network));
+		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
 	}
+	obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
+	obj->plen = nl_addr_get_prefixlen (dst);
+	obj->metric = rtnl_route_get_priority (nlo);
+
+	if (id_only)
+		return TRUE;
+
+	gw = rtnl_route_nh_get_gateway (nexthop);
 	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (route->network)) {
-			g_return_val_if_reached (FALSE);
-			return FALSE;
-		}
-		memcpy (&route->gateway, nl_addr_get_binary_addr (gw), sizeof (route->gateway));
+		if (nl_addr_get_len (gw) != sizeof (obj->gateway))
+			g_warn_if_reached ();
+		else
+			memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
 	}
-	route->metric = rtnl_route_get_priority (rtnlroute);
-	rtnl_route_get_metric (rtnlroute, RTAX_ADVMSS, &route->mss);
-	route->source = rtprot_to_source (rtnl_route_get_protocol (rtnlroute));
+	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
+	if (rtnl_route_get_flags (nlo) & RTM_F_CLONED)
+		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+	else
+		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
 
 	return TRUE;
 }
 
-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__); \
-		return to_string_buffer; \
-	} G_STMT_END
+/******************************************************************/
 
-static const char *
-to_string_link (NMPlatform *platform, struct rtnl_link *obj)
+static void
+do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible, NMPlatformReason reason)
 {
-	NMPlatformLink pl_obj;
+	gboolean is_visible;
+	NMPObject obj_clone;
+	const NMPClass *klass;
 
-	if (init_link (platform, &pl_obj, obj))
-		return nm_platform_link_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid link %p)", obj);
-}
+	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED));
 
-static const char *
-to_string_ip4_address (struct rtnl_addr *obj)
-{
-	NMPlatformIP4Address pl_obj;
+	nm_assert (obj || cache_op == NMP_CACHE_OPS_UNCHANGED);
+	nm_assert (!obj || cache_op == NMP_CACHE_OPS_REMOVED || obj == nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
+	nm_assert (!obj || cache_op != NMP_CACHE_OPS_REMOVED || obj != nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
 
-	if (init_ip4_address (&pl_obj, obj))
-		return nm_platform_ip4_address_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip4 address %p)", obj);
+	switch (cache_op) {
+	case NMP_CACHE_OPS_ADDED:
+		if (!nmp_object_is_visible (obj))
+			return;
+		break;
+	case NMP_CACHE_OPS_UPDATED:
+		is_visible = nmp_object_is_visible (obj);
+		if (!was_visible && is_visible)
+			cache_op = NMP_CACHE_OPS_ADDED;
+		else if (was_visible && !is_visible) {
+			/* This is a bit ugly. The object was visible and changed in a way that it became invisible.
+			 * We raise a removed signal, but contrary to a real 'remove', @obj is already changed to be
+			 * different from what it was when the user saw it the last time.
+			 *
+			 * The more correct solution would be to have cache_pre_hook() create a clone of the original
+			 * value before it was changed to become invisible.
+			 *
+			 * But, don't bother. Probably nobody depends on the original values and only cares about the
+			 * id properties (which are still correct).
+			 */
+			cache_op = NMP_CACHE_OPS_REMOVED;
+		} else if (!is_visible)
+			return;
+		break;
+	case NMP_CACHE_OPS_REMOVED:
+		if (!was_visible)
+			return;
+		break;
+	default:
+		g_assert (cache_op == NMP_CACHE_OPS_UNCHANGED);
+		return;
+	}
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	_LOGT ("emit signal %s %s: %s (%ld)",
+	       klass->signal_type,
+	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
+	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0),
+	       (long) reason);
+
+	/* don't expose @obj directly, but clone the public fields. A signal handler might
+	 * call back into NMPlatform which could invalidate (or modify) @obj. */
+	memcpy (&obj_clone.object, &obj->object, klass->sizeof_public);
+	g_signal_emit_by_name (platform, klass->signal_type, klass->obj_type, obj_clone.object.ifindex, &obj_clone.object, (NMPlatformSignalChangeType) cache_op, reason);
 }
 
-static const char *
-to_string_ip6_address (struct rtnl_addr *obj)
+/******************************************************************/
+
+static DelayedActionType
+delayed_action_refresh_from_object_type (NMPObjectType obj_type)
 {
-	NMPlatformIP6Address pl_obj;
+	switch (obj_type) {
+	case NMP_OBJECT_TYPE_LINK:          return DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS;
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:   return DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES;
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:   return DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:     return DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:     return DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES;
+	default: g_return_val_if_reached (DELAYED_ACTION_TYPE_NONE);
+	}
+}
 
-	if (init_ip6_address (&pl_obj, obj))
-		return nm_platform_ip6_address_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip6 address %p)", obj);
+static NMPObjectType
+delayed_action_refresh_to_object_type (DelayedActionType action_type)
+{
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS:             return NMP_OBJECT_TYPE_LINK;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES:     return NMP_OBJECT_TYPE_IP4_ADDRESS;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES:     return NMP_OBJECT_TYPE_IP6_ADDRESS;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES:        return NMP_OBJECT_TYPE_IP4_ROUTE;
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES:        return NMP_OBJECT_TYPE_IP6_ROUTE;
+	default: g_return_val_if_reached (NMP_OBJECT_TYPE_UNKNOWN);
+	}
 }
 
 static const char *
-to_string_ip4_route (struct rtnl_route *obj)
+delayed_action_to_string (DelayedActionType action_type)
+{
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS              : return "refresh-all-links";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES      : return "refresh-all-ip4-addresses";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES      : return "refresh-all-ip6-addresses";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES         : return "refresh-all-ip4-routes";
+	case DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES         : return "refresh-all-ip6-routes";
+	case DELAYED_ACTION_TYPE_REFRESH_LINK                   : return "refresh-link";
+	case DELAYED_ACTION_TYPE_MASTER_CONNECTED               : return "master-connected";
+	case DELAYED_ACTION_TYPE_READ_NETLINK                   : return "read-netlink";
+	default:
+		return "unknown";
+	}
+}
+
+#define _LOGT_delayed_action(action_type, arg, operation) \
+    _LOGT ("delayed-action: %s %s (%d) [%p / %d]", ""operation, delayed_action_to_string (action_type), (int) action_type, arg, GPOINTER_TO_INT (arg))
+
+static void
+delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
 {
-	NMPlatformIP4Route pl_obj;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	auto_nmp_obj NMPObject *obj_cache = NULL;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-	if (init_ip4_route (&pl_obj, obj))
-		return nm_platform_ip4_route_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip4 route %p)", obj);
+	cache_op = nmp_cache_update_link_master_connected (priv->cache, master_ifindex, &obj_cache, &was_visible, cache_pre_hook, platform);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
 }
 
-static const char *
-to_string_ip6_route (struct rtnl_route *obj)
+static void
+delayed_action_handle_REFRESH_LINK (NMPlatform *platform, int ifindex)
 {
-	NMPlatformIP6Route pl_obj;
-
-	if (init_ip6_route (&pl_obj, obj))
-		return nm_platform_ip6_route_to_string (&pl_obj);
-	SET_AND_RETURN_STRING_BUFFER ("(invalid ip6 route %p)", obj);
+	do_request_link (platform, ifindex, NULL, FALSE);
 }
 
-static const char *
-to_string_object_with_type (NMPlatform *platform, struct nl_object *obj, ObjectType type)
+static void
+delayed_action_handle_REFRESH_ALL (NMPlatform *platform, DelayedActionType flags)
 {
-	switch (type) {
-	case OBJECT_TYPE_LINK:
-		return to_string_link (platform, (struct rtnl_link *) obj);
-	case OBJECT_TYPE_IP4_ADDRESS:
-		return to_string_ip4_address ((struct rtnl_addr *) obj);
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return to_string_ip6_address ((struct rtnl_addr *) obj);
-	case OBJECT_TYPE_IP4_ROUTE:
-		return to_string_ip4_route ((struct rtnl_route *) obj);
-	case OBJECT_TYPE_IP6_ROUTE:
-		return to_string_ip6_route ((struct rtnl_route *) obj);
-	default:
-		SET_AND_RETURN_STRING_BUFFER ("(unknown netlink object %p)", obj);
-	}
+	do_request_all (platform, flags, FALSE);
 }
 
-static const char *
-to_string_object (NMPlatform *platform, struct nl_object *obj)
+static void
+delayed_action_handle_READ_NETLINK (NMPlatform *platform)
 {
-	return to_string_object_with_type (platform, obj, object_type_from_nl_object (obj));
+	event_handler_read_netlink_all (platform, TRUE);
 }
 
-#undef SET_AND_RETURN_STRING_BUFFER
+static gboolean
+delayed_action_handle_one (NMPlatform *platform)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	gpointer user_data;
 
-/******************************************************************/
+	if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE) {
+		nm_clear_g_source (&priv->delayed_action.idle_id);
+		return FALSE;
+	}
 
-/* Object and cache manipulation */
+	/* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions.
+	 * This type of action is entirely cache-internal and is here to resolve a
+	 * cache inconsistency. It should be fixed right away. */
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) {
+		nm_assert (priv->delayed_action.list_master_connected->len > 0);
 
-static const char *signal_by_type_and_status[__OBJECT_TYPE_LAST] = {
-	[OBJECT_TYPE_LINK]        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
-	[OBJECT_TYPE_IP4_ADDRESS] = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
-	[OBJECT_TYPE_IP6_ADDRESS] = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
-	[OBJECT_TYPE_IP4_ROUTE]   = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
-	[OBJECT_TYPE_IP6_ROUTE]   = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
-};
+		user_data = priv->delayed_action.list_master_connected->pdata[0];
+		g_ptr_array_remove_index_fast (priv->delayed_action.list_master_connected, 0);
+		if (priv->delayed_action.list_master_connected->len == 0)
+			priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_MASTER_CONNECTED;
+		nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_master_connected->pdata, priv->delayed_action.list_master_connected->len, user_data) < 0);
 
-static struct nl_cache *
-choose_cache_by_type (NMPlatform *platform, ObjectType object_type)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+		_LOGT_delayed_action (DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle");
+		delayed_action_handle_MASTER_CONNECTED (platform, GPOINTER_TO_INT (user_data));
+		return TRUE;
+	}
+	nm_assert (priv->delayed_action.list_master_connected->len == 0);
+
+	/* Next we prefer read-netlink, because the buffer size is limited and we want to process events
+	 * from netlink early. */
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) {
+		_LOGT_delayed_action (DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle");
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK;
+		delayed_action_handle_READ_NETLINK (platform);
+		return TRUE;
+	}
 
-	switch (object_type) {
-	case OBJECT_TYPE_LINK:
-		return priv->link_cache;
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return priv->address_cache;
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		return priv->route_cache;
-	default:
-		g_return_val_if_reached (NULL);
-		return NULL;
+	if (NM_FLAGS_ANY (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_ALL)) {
+		DelayedActionType flags, iflags;
+
+		flags = priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_ALL;
+
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
+
+		if (_LOGT_ENABLED ()) {
+			for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+				if (NM_FLAGS_HAS (flags, iflags))
+					_LOGT_delayed_action (iflags, NULL, "handle");
+			}
+		}
+
+		delayed_action_handle_REFRESH_ALL (platform, flags);
+		return TRUE;
 	}
+
+	nm_assert (priv->delayed_action.flags == DELAYED_ACTION_TYPE_REFRESH_LINK);
+	nm_assert (priv->delayed_action.list_refresh_link->len > 0);
+
+	user_data = priv->delayed_action.list_refresh_link->pdata[0];
+	g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, 0);
+	if (priv->delayed_action.list_master_connected->len == 0)
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+	nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0);
+
+	_LOGT_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
+
+	delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
+
+	return TRUE;
 }
 
-static struct nl_cache *
-choose_cache (NMPlatform *platform, struct nl_object *object)
+static gboolean
+delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink)
 {
-	return choose_cache_by_type (platform, object_type_from_nl_object (object));
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	gboolean any = FALSE;
+
+	nm_clear_g_source (&priv->delayed_action.idle_id);
+	priv->delayed_action.is_handling++;
+	if (read_netlink)
+		delayed_action_schedule (platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL);
+	while (delayed_action_handle_one (platform))
+		any = TRUE;
+	priv->delayed_action.is_handling--;
+	return any;
 }
 
 static gboolean
-object_has_ifindex (struct nl_object *object, int ifindex)
-{
-	switch (object_type_from_nl_object (object)) {
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
-		return ifindex == rtnl_addr_get_ifindex ((struct rtnl_addr *) object);
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
-		{
-			struct rtnl_route *rtnlroute = (struct rtnl_route *) object;
-			struct rtnl_nexthop *nexthop;
+delayed_action_handle_idle (gpointer user_data)
+{
+	NM_LINUX_PLATFORM_GET_PRIVATE (user_data)->delayed_action.idle_id = 0;
+	delayed_action_handle_all (user_data, FALSE);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	DelayedActionType iflags;
+
+	nm_assert (action_type != DELAYED_ACTION_TYPE_NONE);
+
+	if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
+		nm_assert (nm_utils_is_power_of_two (action_type));
+		if (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0)
+			g_ptr_array_add (priv->delayed_action.list_refresh_link, user_data);
+	} else if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) {
+		nm_assert (nm_utils_is_power_of_two (action_type));
+		if (_nm_utils_ptrarray_find_first (priv->delayed_action.list_master_connected->pdata, priv->delayed_action.list_master_connected->len, user_data) < 0)
+			g_ptr_array_add (priv->delayed_action.list_master_connected, user_data);
+	} else
+		nm_assert (!user_data);
 
-			if (rtnl_route_get_nnexthops (rtnlroute) != 1)
-				return FALSE;
-			nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
+	priv->delayed_action.flags |= action_type;
 
-			return ifindex == rtnl_route_nh_get_ifindex (nexthop);
+	if (_LOGT_ENABLED ()) {
+		for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+			if (NM_FLAGS_HAS (action_type, iflags))
+				_LOGT_delayed_action (iflags, user_data, "schedule");
 		}
-	default:
-		g_assert_not_reached ();
 	}
+
+	if (priv->delayed_action.is_handling == 0 && priv->delayed_action.idle_id == 0)
+		priv->delayed_action.idle_id = g_idle_add (delayed_action_handle_idle, platform);
 }
 
-static gboolean refresh_object (NMPlatform *platform, struct nl_object *object, gboolean removed, NMPlatformReason reason);
+/******************************************************************/
 
 static void
-check_cache_items (NMPlatform *platform, struct nl_cache *cache, int ifindex)
+cache_prune_candidates_record_all (NMPlatform *platform, NMPObjectType obj_type)
 {
-	auto_nl_cache struct nl_cache *cloned_cache = nl_cache_clone (cache);
-	struct nl_object *object;
-	GPtrArray *objects_to_refresh = g_ptr_array_new_with_free_func ((GDestroyNotify) nl_object_put);
-	guint i;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	for (object = nl_cache_get_first (cloned_cache); object; object = nl_cache_get_next (object)) {
-		if (object_has_ifindex (object, ifindex)) {
-			nl_object_get (object);
-			g_ptr_array_add (objects_to_refresh, object);
-		}
-	}
+	priv->prune_candidates = nmp_cache_lookup_all_to_hash (priv->cache,
+	                                                       nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, obj_type, FALSE),
+	                                                       priv->prune_candidates);
+	_LOGT ("cache-prune: record %s (now %u candidates)", nmp_class_from_type (obj_type)->obj_type_name,
+	       priv->prune_candidates ? g_hash_table_size (priv->prune_candidates) : 0);
+}
+
+static void
+cache_prune_candidates_record_one (NMPlatform *platform, NMPObject *obj)
+{
+	NMLinuxPlatformPrivate *priv;
+
+	if (!obj)
+		return;
+
+	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	for (i = 0; i < objects_to_refresh->len; i++)
-		refresh_object (platform, objects_to_refresh->pdata[i], TRUE, NM_PLATFORM_REASON_CACHE_CHECK);
+	if (!priv->prune_candidates)
+		priv->prune_candidates = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
 
-	g_ptr_array_free (objects_to_refresh, TRUE);
+	if (_LOGT_ENABLED () && !g_hash_table_contains (priv->prune_candidates, obj))
+		_LOGT ("cache-prune: record-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+	g_hash_table_add (priv->prune_candidates, nmp_object_ref (obj));
 }
 
 static void
-announce_object (NMPlatform *platform, const struct nl_object *object, NMPlatformSignalChangeType change_type, NMPlatformReason reason)
+cache_prune_candidates_drop (NMPlatform *platform, const NMPObject *obj)
+{
+	NMLinuxPlatformPrivate *priv;
+
+	if (!obj)
+		return;
+
+	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	if (priv->prune_candidates) {
+		if (_LOGT_ENABLED () && g_hash_table_contains (priv->prune_candidates, obj))
+			_LOGT ("cache-prune: drop-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+		g_hash_table_remove (priv->prune_candidates, obj);
+	}
+}
+
+static void
+cache_prune_candidates_prune (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	ObjectType object_type = object_type_from_nl_object (object);
-	const char *sig = signal_by_type_and_status[object_type];
+	GHashTable *prune_candidates;
+	GHashTableIter iter;
+	const NMPObject *obj;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-	switch (object_type) {
-	case OBJECT_TYPE_LINK:
-		{
-			struct rtnl_link *rtnl_link = (struct rtnl_link *) object;
-			NMPlatformLink device;
+	if (!priv->prune_candidates)
+		return;
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-			/* If we ever see a link with valid IPv6 link-local address
-			 * generation modes, the kernel supports it.
-			 */
-			if (priv->support_user_ipv6ll == 0) {
-				uint8_t mode;
+	prune_candidates = priv->prune_candidates;
+	priv->prune_candidates = NULL;
 
-				if (rtnl_link_inet6_get_addr_gen_mode (rtnl_link, &mode) == 0)
-					priv->support_user_ipv6ll = 1;
-			}
-#endif
+	g_hash_table_iter_init (&iter, prune_candidates);
+	while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) {
+		auto_nmp_obj NMPObject *obj_cache = NULL;
 
-			if (!init_link (platform, &device, rtnl_link))
-				return;
+		_LOGT ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+		cache_op = nmp_cache_remove (priv->cache, obj, TRUE, &obj_cache, &was_visible, cache_pre_hook, platform);
+		do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+	}
 
-			/* Skip hardware devices not yet discovered by udev. They will be
-			 * announced by udev_device_added(). This doesn't apply to removed
-			 * devices, as those come either from udev_device_removed(),
-			 * event_notification() or link_delete() which block the announcment
-			 * themselves when appropriate.
-			 */
-			switch (change_type) {
-			case NM_PLATFORM_SIGNAL_ADDED:
-			case NM_PLATFORM_SIGNAL_CHANGED:
-				if (!link_is_software (rtnl_link) && !device.driver)
-					return;
-				break;
-			default:
-				break;
-			}
+	g_hash_table_unref (prune_candidates);
+}
 
-			/* Link deletion or setting down is sometimes accompanied by address
-			 * and/or route deletion.
-			 *
-			 * More precisely, kernel removes routes when interface goes !IFF_UP and
-			 * removes both addresses and routes when interface is removed.
-			 */
-			switch (change_type) {
-			case NM_PLATFORM_SIGNAL_CHANGED:
-				if (!device.connected)
-					check_cache_items (platform, priv->route_cache, device.ifindex);
-				break;
-			case NM_PLATFORM_SIGNAL_REMOVED:
-				check_cache_items (platform, priv->address_cache, device.ifindex);
-				check_cache_items (platform, priv->route_cache, device.ifindex);
-				g_hash_table_remove (priv->wifi_data, GINT_TO_POINTER (device.ifindex));
-				break;
-			default:
-				break;
-			}
+static void
+cache_delayed_deletion_prune (NMPlatform *platform)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	GPtrArray *prune_list = NULL;
+	GHashTableIter iter;
+	guint i;
+	NMPObject *obj;
 
-			g_signal_emit_by_name (platform, sig, device.ifindex, &device, change_type, reason);
-		}
+	if (g_hash_table_size (priv->delayed_deletion) == 0)
 		return;
-	case OBJECT_TYPE_IP4_ADDRESS:
-		{
-			NMPlatformIP4Address address;
 
-			/* Address deletion is sometimes accompanied by route deletion. We need to
-			 * check all routes belonging to the same interface.
-			 */
-			switch (change_type) {
-			case NM_PLATFORM_SIGNAL_REMOVED:
-				check_cache_items (platform,
-				                   priv->route_cache,
-				                   rtnl_addr_get_ifindex ((struct rtnl_addr *) object));
-				break;
-			default:
-				break;
-			}
+	g_hash_table_iter_init (&iter, priv->delayed_deletion);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &obj)) {
+		if (obj) {
+			if (!prune_list)
+				prune_list = g_ptr_array_new_full (g_hash_table_size (priv->delayed_deletion), (GDestroyNotify) nmp_object_unref);
+			g_ptr_array_add (prune_list, nmp_object_ref (obj));
+		}
+	}
 
-			if (!_address_match ((struct rtnl_addr *) object, AF_INET, 0)) {
-				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP4 address %s", to_string_ip4_address ((struct rtnl_addr *) object));
-				return;
-			}
-			if (!init_ip4_address (&address, (struct rtnl_addr *) object))
-				return;
-			g_signal_emit_by_name (platform, sig, address.ifindex, &address, change_type, reason);
+	g_hash_table_remove_all (priv->delayed_deletion);
+
+	if (prune_list) {
+		for (i = 0; i < prune_list->len; i++) {
+			obj = prune_list->pdata[i];
+			_LOGT ("delayed-deletion: delete %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			cache_remove_netlink (platform, obj, NULL, NULL, NM_PLATFORM_REASON_EXTERNAL);
 		}
-		return;
-	case OBJECT_TYPE_IP6_ADDRESS:
-		{
-			NMPlatformIP6Address address;
+		g_ptr_array_unref (prune_list);
+	}
+}
 
-			if (!_address_match ((struct rtnl_addr *) object, AF_INET6, 0)) {
-				nm_log_dbg (LOGD_PLATFORM, "skip announce unmatching IP6 address %s", to_string_ip6_address ((struct rtnl_addr *) object));
-				return;
+static void
+cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+{
+	NMPlatform *platform = NM_PLATFORM (user_data);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPClass *klass;
+	char str_buf[sizeof (_nm_platform_to_string_buffer)];
+	char str_buf2[sizeof (_nm_platform_to_string_buffer)];
+
+	nm_assert (old || new);
+	nm_assert (NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_REMOVED, NMP_CACHE_OPS_UPDATED));
+	nm_assert (ops_type != NMP_CACHE_OPS_ADDED   || (old == NULL && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new)));
+	nm_assert (ops_type != NMP_CACHE_OPS_REMOVED || (new == NULL && NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old)));
+	nm_assert (ops_type != NMP_CACHE_OPS_UPDATED || (NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old) && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new)));
+	nm_assert (new == NULL || old == NULL || nmp_object_id_equal (new, old));
+
+	klass = old ? NMP_OBJECT_GET_CLASS (old) : NMP_OBJECT_GET_CLASS (new);
+
+	nm_assert (klass == (new ? NMP_OBJECT_GET_CLASS (new) : NMP_OBJECT_GET_CLASS (old)));
+
+	_LOGT ("update-cache-%s: %s: %s%s%s",
+	       klass->obj_type_name,
+	       (ops_type == NMP_CACHE_OPS_UPDATED
+	           ? "UPDATE"
+	           : (ops_type == NMP_CACHE_OPS_REMOVED
+	                 ? "REMOVE"
+	                 : (ops_type == NMP_CACHE_OPS_ADDED) ? "ADD" : "???")),
+	       (ops_type != NMP_CACHE_OPS_ADDED
+	           ? nmp_object_to_string (old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))
+	           : nmp_object_to_string (new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))),
+	       (ops_type == NMP_CACHE_OPS_UPDATED) ? " -> " : "",
+	       (ops_type == NMP_CACHE_OPS_UPDATED
+	           ? nmp_object_to_string (new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof (str_buf))
+	           : ""));
+
+	switch (klass->obj_type) {
+	case NMP_OBJECT_TYPE_LINK:
+		{
+			/* check whether changing a slave link can cause a master link (bridge or bond) to go up/down */
+			if (   old
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (priv->cache, old->link.master, new, old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (old->link.master));
+			if (   new
+			    && (!old || old->link.master != new->link.master)
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (priv->cache, new->link.master, new, old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (new->link.master));
+		}
+		{
+			/* check whether we are about to change a master link that needs toggling connected state. */
+			if (   new /* <-- nonsensical, make coverity happy */
+			    && nmp_cache_link_connected_needs_toggle (cache, new, new, old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (new->link.ifindex));
+		}
+		{
+			int ifindex = 0;
+
+			/* if we remove a link (from netlink), we must refresh the addresses and routes */
+			if (   ops_type == NMP_CACHE_OPS_REMOVED
+			    && old /* <-- nonsensical, make coverity happy */)
+				ifindex = old->link.ifindex;
+			else if (   ops_type == NMP_CACHE_OPS_UPDATED
+			         && old && new /* <-- nonsensical, make coverity happy */
+			         && !new->_link.netlink.is_in_netlink
+			         && new->_link.netlink.is_in_netlink != old->_link.netlink.is_in_netlink)
+				ifindex = new->link.ifindex;
+
+			if (ifindex > 0) {
+				delayed_action_schedule (platform,
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+				                         NULL);
 			}
-			if (!init_ip6_address (&address, (struct rtnl_addr *) object))
-				return;
-			g_signal_emit_by_name (platform, sig, address.ifindex, &address, change_type, reason);
 		}
-		return;
-	case OBJECT_TYPE_IP4_ROUTE:
 		{
-			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;
+			/* if a link goes down, we must refresh routes */
+			if (   ops_type == NMP_CACHE_OPS_UPDATED
+			    && old && new /* <-- nonsensical, make coverity happy */
+			    && old->_link.netlink.is_in_netlink
+			    && NM_FLAGS_HAS (old->link.flags, IFF_LOWER_UP)
+			    && new->_link.netlink.is_in_netlink
+			    && !NM_FLAGS_HAS (new->link.flags, IFF_LOWER_UP)) {
+				delayed_action_schedule (platform,
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+				                         NULL);
 			}
-			if (init_ip4_route (&route, (struct rtnl_route *) object))
-				g_signal_emit_by_name (platform, sig, route.ifindex, &route, change_type, reason);
 		}
-		return;
-	case OBJECT_TYPE_IP6_ROUTE:
 		{
-			NMPlatformIP6Route route;
-
-			if (reason == _NM_PLATFORM_REASON_CACHE_CHECK_INTERNAL)
-				return;
+			/* on enslave/release, we also refresh the master. */
+			int ifindex1 = 0, ifindex2 = 0;
+			gboolean changed_master, changed_connected;
+
+			changed_master =    (new && new->_link.netlink.is_in_netlink && new->link.master > 0 ? new->link.master : 0)
+			                 != (old && old->_link.netlink.is_in_netlink && old->link.master > 0 ? old->link.master : 0);
+			changed_connected =    (new && new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (new->link.flags, IFF_LOWER_UP) : 2)
+			                    != (old && old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (old->link.flags, IFF_LOWER_UP) : 2);
+
+			if (changed_master || changed_connected) {
+				ifindex1 = (old && old->_link.netlink.is_in_netlink && old->link.master > 0) ? old->link.master : 0;
+				ifindex2 = (new && new->_link.netlink.is_in_netlink && new->link.master > 0) ? new->link.master : 0;
+
+				if (ifindex1 > 0)
+					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex1));
+				if (ifindex2 > 0 && ifindex1 != ifindex2)
+					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex2));
+			}
 
-			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;
+		}
+		break;
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		{
+			/* Address deletion is sometimes accompanied by route deletion. We need to
+			 * check all routes belonging to the same interface. */
+			if (ops_type == NMP_CACHE_OPS_REMOVED) {
+				delayed_action_schedule (platform,
+				                         (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS)
+				                             ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
+				                             : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+				                         NULL);
 			}
-			if (init_ip6_route (&route, (struct rtnl_route *) object))
-				g_signal_emit_by_name (platform, sig, route.ifindex, &route, change_type, reason);
 		}
-		return;
 	default:
-		g_return_if_reached ();
+		break;
 	}
 }
 
-static struct nl_object * build_rtnl_link (int ifindex, const char *name, NMLinkType type);
+static NMPCacheOpsType
+cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	NMPObject *obj_cache;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-static gboolean
-refresh_object (NMPlatform *platform, struct nl_object *object, gboolean removed, NMPlatformReason reason)
+	cache_op = nmp_cache_remove_netlink (priv->cache, obj_needle, &obj_cache, &was_visible, cache_pre_hook, platform);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+
+	if (out_obj_cache)
+		*out_obj_cache = obj_cache;
+	else
+		nmp_object_unref (obj_cache);
+	if (out_was_visible)
+		*out_was_visible = was_visible;
+
+	return cache_op;
+}
+
+static NMPCacheOpsType
+cache_update_netlink (NMPlatform *platform, NMPObject *obj, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct nl_object *cached_object = NULL;
-	auto_nl_object struct nl_object *kernel_object = NULL;
-	struct nl_cache *cache;
-	int nle;
+	NMPObject *obj_cache;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
 
-	cache = choose_cache (platform, object);
-	cached_object = nm_nl_cache_search (cache, object);
-	kernel_object = get_kernel_object (priv->nlh, object);
+	/* This is basically a convenience method to call nmp_cache_update() and do_emit_signal()
+	 * at once. */
 
-	if (removed) {
-		if (kernel_object)
-			return TRUE;
+	cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible, reason);
 
-		/* Only announce object if it was still in the cache. */
-		if (cached_object) {
-			nl_cache_remove (cached_object);
+	if (out_obj_cache)
+		*out_obj_cache = obj_cache;
+	else
+		nmp_object_unref (obj_cache);
+	if (out_was_visible)
+		*out_was_visible = was_visible;
 
-			announce_object (platform, cached_object, NM_PLATFORM_SIGNAL_REMOVED, reason);
-		}
-	} else {
-		ObjectType type;
+	return cache_op;
+}
 
-		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);
+static void
+_new_sequence_number (NMPlatform *platform, guint32 seq)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-		hack_empty_master_iff_lower_up (platform, kernel_object);
+	_LOGT ("_new_sequence_number(): new sequence number %u", seq);
 
-		if (cached_object)
-			nl_cache_remove (cached_object);
-		nle = nl_cache_add (cache, kernel_object);
-		if (nle) {
-			nm_log_dbg (LOGD_PLATFORM, "refresh_object(reason %d) failed during nl_cache_add with %d", reason, nle);
-			return FALSE;
-		}
+	priv->nlh_seq_expect = seq;
+}
+
+static void
+do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	guint32 seq;
 
-		announce_object (platform, kernel_object, cached_object ? NM_PLATFORM_SIGNAL_CHANGED : NM_PLATFORM_SIGNAL_ADDED, reason);
+	_LOGT ("do_request_link (%d,%s)", ifindex, name ? name : "");
 
-		/* Refresh the master device (even on enslave/release) */
-		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;
+	if (ifindex > 0) {
+		NMPObject *obj;
 
-			if (kernel_master) {
-				master_object = build_rtnl_link (kernel_master, NULL, NM_LINK_TYPE_NONE);
-				refresh_object (platform, master_object, FALSE, NM_PLATFORM_REASON_INTERNAL);
-				nl_object_put (master_object);
-			}
-			if (cached_master && cached_master != kernel_master) {
-				master_object = build_rtnl_link (cached_master, NULL, NM_LINK_TYPE_NONE);
-				refresh_object (platform, master_object, FALSE, NM_PLATFORM_REASON_INTERNAL);
-				nl_object_put (master_object);
+		cache_prune_candidates_record_one (platform,
+		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
+		obj = nmp_object_new_link (ifindex);
+		_LOGT ("delayed-deletion: protect object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+		g_hash_table_insert (priv->delayed_deletion, obj, NULL);
+	}
+
+	event_handler_read_netlink_all (platform, FALSE);
+
+	if (_nl_sock_request_link (platform, priv->nlh_event, ifindex, name, &seq) == 0)
+		_new_sequence_number (platform, seq);
+
+	event_handler_read_netlink_all (platform, TRUE);
+
+	cache_delayed_deletion_prune (platform);
+	cache_prune_candidates_prune (platform);
+
+	if (handle_delayed_action)
+		delayed_action_handle_all (platform, FALSE);
+}
+
+static void
+do_request_one_type (NMPlatform *platform, NMPObjectType obj_type, gboolean handle_delayed_action)
+{
+	do_request_all (platform, delayed_action_refresh_from_object_type (obj_type), handle_delayed_action);
+}
+
+static void
+do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	guint32 seq;
+	DelayedActionType iflags;
+
+	nm_assert (!NM_FLAGS_ANY (action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
+	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
+
+	for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+		if (NM_FLAGS_HAS (action_type, iflags))
+			cache_prune_candidates_record_all (platform, delayed_action_refresh_to_object_type (iflags));
+	}
+
+	for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
+		if (NM_FLAGS_HAS (action_type, iflags)) {
+			NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
+
+			/* clear any delayed action that request a refresh of this object type. */
+			priv->delayed_action.flags &= ~iflags;
+			if (obj_type == NMP_OBJECT_TYPE_LINK) {
+				priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+				g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
 			}
+
+			event_handler_read_netlink_all (platform, FALSE);
+
+			if (_nl_sock_request_all (platform, priv->nlh_event, obj_type, &seq) == 0)
+				_new_sequence_number (platform, seq);
 		}
 	}
+	event_handler_read_netlink_all (platform, TRUE);
 
-	return TRUE;
+	cache_prune_candidates_prune (platform);
+
+	if (handle_delayed_action)
+		delayed_action_handle_all (platform, FALSE);
 }
 
-/* Decreases the reference count if @obj for convenience */
 static gboolean
-add_object (NMPlatform *platform, struct nl_object *obj)
+kernel_add_object (NMPlatform *platform, NMPObjectType obj_type, const struct nl_object *nlo)
 {
-	auto_nl_object struct nl_object *object = obj;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int nle;
 
-	g_return_val_if_fail (object, FALSE);
+	g_return_val_if_fail (nlo, FALSE);
 
-	nle = add_kernel_object (priv->nlh, object);
+	switch (obj_type) {
+	case NMP_OBJECT_TYPE_LINK:
+		nle = rtnl_link_add (priv->nlh, (struct rtnl_link *) nlo, NLM_F_CREATE);
+		break;
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		nle = rtnl_addr_add (priv->nlh, (struct rtnl_addr *) nlo, NLM_F_CREATE | NLM_F_REPLACE);
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		nle = rtnl_route_add (priv->nlh, (struct rtnl_route *) nlo, NLM_F_CREATE | NLM_F_REPLACE);
+		break;
+	default:
+		g_return_val_if_reached (-NLE_INVAL);
+	}
+
+	_LOGT ("kernel-add-%s: returned %s (%d)",
+	       nmp_class_from_type (obj_type)->obj_type_name, nl_geterror (nle), -nle);
 
-	/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-	 * never know when something sends an identical object just before
-	 * NetworkManager.
-	 */
 	switch (nle) {
 	case -NLE_SUCCESS:
+		return -NLE_SUCCESS;
 	case -NLE_EXIST:
-		break;
+		/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
+		 * never know when something sends an identical object just before
+		 * NetworkManager. */
+		if (obj_type != NMP_OBJECT_TYPE_LINK)
+			return -NLE_SUCCESS;
+		/* fall-through */
 	default:
-		error ("Netlink error adding %s: %s", to_string_object (platform, object),  nl_geterror (nle));
-		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;
+		return nle;
 	}
-
-	return refresh_object (platform, object, FALSE, NM_PLATFORM_REASON_INTERNAL);
 }
 
-/* Decreases the reference count if @obj for convenience */
-static gboolean
-delete_object (NMPlatform *platform, struct nl_object *object, gboolean do_refresh_object)
+static int
+kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const struct nl_object *object)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int object_type;
 	int nle;
-	gboolean result = FALSE;
-
-	object_type = object_type_from_nl_object (object);
-	g_return_val_if_fail (object_type != OBJECT_TYPE_UNKNOWN, FALSE);
 
 	switch (object_type) {
-	case OBJECT_TYPE_LINK:
+	case NMP_OBJECT_TYPE_LINK:
 		nle = rtnl_link_delete (priv->nlh, (struct rtnl_link *) object);
 		break;
-	case OBJECT_TYPE_IP4_ADDRESS:
-	case OBJECT_TYPE_IP6_ADDRESS:
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
 		nle = rtnl_addr_delete (priv->nlh, (struct rtnl_addr *) object, 0);
 		break;
-	case OBJECT_TYPE_IP4_ROUTE:
-	case OBJECT_TYPE_IP6_ROUTE:
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
 		nle = rtnl_route_delete (priv->nlh, (struct rtnl_route *) object, 0);
 		break;
 	default:
@@ -1836,40 +2103,134 @@ delete_object (NMPlatform *platform, struct nl_object *object, gboolean do_refre
 
 	switch (nle) {
 	case -NLE_SUCCESS:
-		break;
+		return NLE_SUCCESS;
 	case -NLE_OBJ_NOTFOUND:
-		debug("delete_object failed with \"%s\" (%d), meaning the object was already removed",
-		      nl_geterror (nle), nle);
-		break;
+		_LOGT ("kernel-delete-%s: failed with \"%s\" (%d), meaning the object was already removed",
+		       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
+		return -NLE_SUCCESS;
 	case -NLE_FAILURE:
-		if (object_type == OBJECT_TYPE_IP6_ADDRESS) {
+		if (object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
 			/* On RHEL7 kernel, deleting a non existing address fails with ENXIO (which libnl maps to NLE_FAILURE) */
-			debug("delete_object for address failed with \"%s\" (%d), meaning the address was already removed",
-			      nl_geterror (nle), nle);
-			break;
+			_LOGT ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
+			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
+			return NLE_SUCCESS;
 		}
-		goto DEFAULT;
+		break;
 	case -NLE_NOADDR:
-		if (object_type == OBJECT_TYPE_IP4_ADDRESS || object_type == OBJECT_TYPE_IP6_ADDRESS) {
-			debug("delete_object for address failed with \"%s\" (%d), meaning the address was already removed",
-			      nl_geterror (nle), nle);
-			break;
+		if (object_type == NMP_OBJECT_TYPE_IP4_ADDRESS || object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
+			_LOGT ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
+			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
+			return -NLE_SUCCESS;
 		}
-		goto DEFAULT;
-	DEFAULT:
+		break;
 	default:
-		error ("Netlink error deleting %s: %s (%d)", to_string_object (platform, object), nl_geterror (nle), nle);
-		goto out;
+		break;
 	}
+	_LOGT ("kernel-delete-%s: failed with %s (%d)",
+	       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
+	return nle;
+}
 
-	if (do_refresh_object)
-		refresh_object (platform, object, TRUE, NM_PLATFORM_REASON_INTERNAL);
+static int
+kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *complete_from_cache)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	struct nl_msg *msg;
+	int nle;
+	const int nlflags = 0;
+	int ifindex;
 
-	result = TRUE;
+	ifindex = rtnl_link_get_ifindex (nlo);
 
-out:
-	nl_object_put (object);
-	return result;
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	/* Previously, we were using rtnl_link_change(), which builds a request based
+	 * on the diff with an original link instance.
+	 *
+	 * The diff only reused ifi_family, ifi_index, ifi_flags, and name from
+	 * the original link (see rtnl_link_build_change_request()).
+	 *
+	 * We don't do that anymore as we don't have an "orig" netlink instance that
+	 * we can use. Instead the caller must ensure to properly initialize @nlo,
+	 * especially it must set family, ifindex (or ifname) and flags.
+	 * ifname should be set *only* if the caller wishes to change the name.
+	 *
+	 * @complete_from_cache is a convenience to copy the link flags over the link inside
+	 * the platform cache. */
+
+	if (*complete_from_cache) {
+		const NMPObject *obj_cache;
+
+		obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
+		if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) {
+			_LOGT ("kernel-change-link: failure changing link %d: cannot complete link", ifindex);
+			*complete_from_cache = FALSE;
+			return -NLE_INVAL;
+		}
+
+		rtnl_link_set_flags (nlo, obj_cache->link.flags);
+
+		/* If the caller wants to rename the link, he should explicitly set
+		 * rtnl_link_set_name(). In all other cases, it should leave the name
+		 * unset. Unfortunately, there is not public API in libnl to modify the
+		 * attribute mask and clear (link->ce_mask = ~LINK_ATTR_IFNAME), so we
+		 * require the caller to do the right thing -- i.e. don't set the name.
+		 */
+	}
+
+	/* We don't use rtnl_link_change() because we have no original rtnl_link object
+	 * at hand. We also don't use rtnl_link_add() because that doesn't have the
+	 * hack to retry with RTM_SETLINK. Reimplement a mix of both. */
+
+	nle = rtnl_link_build_add_request (nlo, nlflags, &msg);
+	if (nle < 0) {
+		_LOGT ("kernel-change-link: failure changing link %d: cannot construct message (%s, %d)",
+		       ifindex, nl_geterror (nle), -nle);
+		return nle;
+	}
+
+retry:
+	nle = nl_send_auto_complete (priv->nlh, msg);
+	if (nle < 0)
+		goto errout;
+
+	nle = nl_wait_for_ack(priv->nlh);
+	if (nle == -NLE_OPNOTSUPP && nlmsg_hdr (msg)->nlmsg_type == RTM_NEWLINK) {
+		nlmsg_hdr (msg)->nlmsg_type = RTM_SETLINK;
+		goto retry;
+	}
+
+errout:
+	nlmsg_free(msg);
+
+	/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
+	 * never know when something sends an identical object just before
+	 * NetworkManager.
+	 *
+	 * When netlink returns NLE_OBJ_NOTFOUND, it usually means it failed to find
+	 * firmware for the device, especially on nm_platform_link_set_up ().
+	 * This is basically the same check as in the original code and could
+	 * potentially be improved.
+	 */
+	switch (nle) {
+	case -NLE_SUCCESS:
+		_LOGT ("kernel-change-link: success changing link %d", ifindex);
+		break;
+	case -NLE_EXIST:
+		_LOGT ("kernel-change-link: success changing link %d: %s (%d)",
+		       ifindex, nl_geterror (nle), -nle);
+		break;
+	case -NLE_OBJ_NOTFOUND:
+		_LOGT ("kernel-change-link: failure changing link %d: firmware not found (%s, %d)",
+		       ifindex, nl_geterror (nle), -nle);
+		break;
+	default:
+		_LOGT ("kernel-change-link: failure changing link %d: netlink error (%s, %d)",
+		       ifindex, nl_geterror (nle), -nle);
+		break;
+	}
+
+	return nle;
 }
 
 static void
@@ -1881,47 +2242,40 @@ ref_object (struct nl_object *obj, void *data)
 	*out = obj;
 }
 
-static gboolean
-_rtnl_addr_timestamps_equal_fuzzy (guint32 ts1, guint32 ts2)
+static int
+event_seq_check (struct nl_msg *msg, gpointer user_data)
 {
-	guint32 diff;
+	NMPlatform *platform = NM_PLATFORM (user_data);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	struct nlmsghdr *hdr;
 
-	if (ts1 == ts2)
-		return TRUE;
-	if (ts1 == NM_PLATFORM_LIFETIME_PERMANENT ||
-	    ts2 == NM_PLATFORM_LIFETIME_PERMANENT)
-		return FALSE;
+	hdr = nlmsg_hdr (msg);
 
-	/** accept the timestamps as equal if they are within two seconds. */
-	diff = ts1 > ts2 ? ts1 - ts2 : ts2 - ts1;
-	return diff <= 2;
-}
+	if (hdr->nlmsg_seq == 0)
+		return NL_OK;
 
-static gboolean
-nm_nl_object_diff (ObjectType type, struct nl_object *_a, struct nl_object *_b)
-{
-	if (nl_object_diff (_a, _b)) {
-		/* libnl thinks objects are different*/
-		return TRUE;
-	}
+	priv->nlh_seq_last = hdr->nlmsg_seq;
 
-	if (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS) {
-		struct rtnl_addr *a = (struct rtnl_addr *) _a;
-		struct rtnl_addr *b = (struct rtnl_addr *) _b;
+	if (priv->nlh_seq_expect == 0)
+		_LOGT ("event_seq_check(): seq %u received (not waited)", hdr->nlmsg_seq);
+	else if (hdr->nlmsg_seq == priv->nlh_seq_expect) {
+		_LOGT ("event_seq_check(): seq %u received", hdr->nlmsg_seq);
 
-		/* libnl nl_object_diff() ignores differences in timestamp. Let's care about
-		 * them (if they are large enough).
-		 *
-		 * Note that these valid and preferred timestamps are absolute, after
-		 * _rtnl_addr_hack_lifetimes_rel_to_abs(). */
-		if (   !_rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_preferred_lifetime (a),
-							  rtnl_addr_get_preferred_lifetime (b))
-		    || !_rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_valid_lifetime (a),
-							  rtnl_addr_get_valid_lifetime (b)))
-			return TRUE;
-	}
+		priv->nlh_seq_expect = 0;
+	} else
+		_LOGT ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last);
 
-	return FALSE;
+	return NL_OK;
+}
+
+static int
+event_err (struct sockaddr_nl *nla, struct nlmsgerr *nlerr, gpointer platform)
+{
+	_LOGT ("event_err(): error from kernel: %s (%d) for request %d",
+	       strerror (nlerr ? -nlerr->error : 0),
+	       nlerr ? -nlerr->error : 0,
+	       NM_LINUX_PLATFORM_GET_PRIVATE (platform)->nlh_seq_last);
+	return NL_OK;
 }
 
 /* This function does all the magic to avoid race conditions caused
@@ -1934,123 +2288,71 @@ static int
 event_notification (struct nl_msg *msg, gpointer user_data)
 {
 	NMPlatform *platform = NM_PLATFORM (user_data);
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_cache *cache;
-	auto_nl_object struct nl_object *object = NULL;
-	auto_nl_object struct nl_object *cached_object = NULL;
-	auto_nl_object struct nl_object *kernel_object = NULL;
-	int event;
-	int nle;
-	ObjectType type;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (user_data);
+	auto_nl_object struct nl_object *nlo = NULL;
+	auto_nmp_obj NMPObject *obj = NULL;
+	struct nlmsghdr *msghdr;
+	char buf_nlmsg_type[16];
 
-	event = nlmsg_hdr (msg)->nlmsg_type;
+	msghdr = nlmsg_hdr (msg);
 
-	if (priv->support_kernel_extended_ifa_flags == 0 && event == RTM_NEWADDR) {
-		/* if kernel support for extended ifa flags is still undecided, use the opportunity
-		 * now and use @msg to decide it. This saves a blocking net link request.
-		 **/
-		_check_support_kernel_extended_ifa_flags_init (priv, msg);
-	}
+	if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR)
+		_support_kernel_extended_ifa_flags_detect (msg);
 
-	nl_msg_parse (msg, ref_object, &object);
-	g_return_val_if_fail (object, NL_OK);
-
-	type = object_type_from_nl_object (object);
+	nl_msg_parse (msg, ref_object, &nlo);
+	if (!nlo)
+		return NL_OK;
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-		if (type == OBJECT_TYPE_LINK) {
-			const char *name = rtnl_link_get_name ((struct rtnl_link *) object);
+	if (_support_user_ipv6ll_still_undecided() && msghdr->nlmsg_type == RTM_NEWLINK)
+		_support_user_ipv6ll_detect ((struct rtnl_link *) nlo);
 
-			debug ("netlink event (type %d) for link: %s (%d, family %d)",
-			       event, name ? name : "(unknown)",
-			       rtnl_link_get_ifindex ((struct rtnl_link *) object),
-			       rtnl_link_get_family ((struct rtnl_link *) object));
-		} else
-			debug ("netlink event (type %d)", event);
-	}
+	obj = nmp_object_from_nl (platform, nlo, FALSE, TRUE);
 
-	cache = choose_cache_by_type (platform, type);
-	cached_object = nm_nl_cache_search (cache, object);
-	kernel_object = get_kernel_object (priv->nlh, object);
+	_LOGD ("event-notification: %s, seq %u: %s",
+	       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
+	       msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	if (obj) {
+		auto_nmp_obj NMPObject *obj_cache = NULL;
 
-	hack_empty_master_iff_lower_up (platform, kernel_object);
+		switch (msghdr->nlmsg_type) {
 
-	/* Removed object */
-	switch (event) {
-	case RTM_DELLINK:
-	case RTM_DELADDR:
-	case RTM_DELROUTE:
-		/* Ignore inconsistent deletion
-		 *
-		 * Quick external deletion and addition can be occasionally
-		 * seen as just a change.
-		 */
-		if (kernel_object)
-			return NL_OK;
-		/* Ignore internal deletion */
-		if (!cached_object)
-			return NL_OK;
-
-		nl_cache_remove (cached_object);
-		/* Don't announce removed interfaces that are not recognized by
-		 * udev. They were either not yet discovered or they have been
-		 * already removed and announced.
-		 */
-		if (event == RTM_DELLINK) {
-			if (!link_is_announceable (platform, (struct rtnl_link *) cached_object))
-				return NL_OK;
-		}
-		announce_object (platform, cached_object, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL);
+		case RTM_NEWLINK:
+			if (   NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK
+			    && g_hash_table_lookup (priv->delayed_deletion, obj) != NULL) {
+				/* the object is scheduled for delayed deletion. Replace that object
+				 * by clearing the value from priv->delayed_deletion. */
+				_LOGT ("delayed-deletion: clear delayed deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), NULL);
+			}
+			/* fall-through */
+		case RTM_NEWADDR:
+		case RTM_NEWROUTE:
+			cache_update_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
+			break;
 
-		return NL_OK;
-	case RTM_NEWLINK:
-	case RTM_NEWADDR:
-	case RTM_NEWROUTE:
-		/* Ignore inconsistent addition or change (kernel will send a good one)
-		 *
-		 * Quick sequence of RTM_NEWLINK notifications can be occasionally
-		 * collapsed to just one addition or deletion, depending of whether we
-		 * already have the object in cache.
-		 */
-		if (!kernel_object)
-			return NL_OK;
-
-		/* 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);
-			if (nle) {
-				error ("netlink cache error: %s", nl_geterror (nle));
-				return NL_OK;
+		case RTM_DELLINK:
+			if (   NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK
+			    && g_hash_table_contains (priv->delayed_deletion, obj)) {
+				/* We sometimes receive spurious RTM_DELLINK events. In this case, we want to delay
+				 * the deletion of the object until later. */
+				_LOGT ("delayed-deletion: delay deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), nmp_object_ref (obj));
+				break;
 			}
-			announce_object (platform, kernel_object, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL);
-			return NL_OK;
-		}
-		/* Ignore non-change
-		 *
-		 * This also catches notifications for internal addition or change, unless
-		 * another action occured very soon after it.
-		 */
-		if (!nm_nl_object_diff (type, kernel_object, cached_object))
-			return NL_OK;
-
-		/* Handle external change */
-		nl_cache_remove (cached_object);
-		nle = nl_cache_add (cache, kernel_object);
-		if (nle) {
-			error ("netlink cache error: %s", nl_geterror (nle));
-			return NL_OK;
+			/* fall-through */
+		case RTM_DELADDR:
+		case RTM_DELROUTE:
+			cache_remove_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
+			break;
+
+		default:
+			break;
 		}
-		announce_object (platform, kernel_object, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_EXTERNAL);
 
-		return NL_OK;
-	default:
-		error ("Unknown netlink event: %d", event);
-		return NL_OK;
+		cache_prune_candidates_drop (platform, obj_cache);
 	}
+
+	return NL_OK;
 }
 
 /******************************************************************/
@@ -2219,40 +2521,81 @@ sysctl_get (NMPlatform *platform, const char *path)
 
 /******************************************************************/
 
+static const NMPObject *
+cache_lookup_link (NMPlatform *platform, int ifindex)
+{
+	const NMPObject *obj_cache;
+
+	obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
+	if (!nmp_object_is_visible (obj_cache))
+		return NULL;
+
+	return obj_cache;
+}
+
 static GArray *
 link_get_all (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *links = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformLink), nl_cache_nitems (priv->link_cache));
-	NMPlatformLink device;
-	struct nl_object *object;
 
-	for (object = nl_cache_get_first (priv->link_cache); object; object = nl_cache_get_next (object)) {
-		struct rtnl_link *rtnl_link = (struct rtnl_link *) object;
+	return nmp_cache_lookup_multi_to_array (priv->cache,
+	                                        NMP_OBJECT_TYPE_LINK,
+	                                        nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, NMP_OBJECT_TYPE_LINK, TRUE));
+}
 
-		if (link_is_announceable (platform, rtnl_link)) {
-			if (init_link (platform, &device, rtnl_link))
-				g_array_append_val (links, device);
-		}
-	}
+static const NMPlatformLink *
+_nm_platform_link_get (NMPlatform *platform, int ifindex)
+{
+	const NMPObject *obj;
 
-	return links;
+	obj = cache_lookup_link (platform, ifindex);
+	return obj ? &obj->link : NULL;
 }
 
-static gboolean
-_nm_platform_link_get (NMPlatform *platform, int ifindex, NMPlatformLink *l)
+static const NMPlatformLink *
+_nm_platform_link_get_by_ifname (NMPlatform *platform,
+                                 const char *ifname)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
+	const NMPObject *obj = NULL;
 
-	rtnllink = rtnl_link_get (priv->link_cache, ifindex);
-	if (rtnllink) {
-		if (link_is_announceable (platform, rtnllink)) {
-			if (init_link (platform, l, rtnllink))
-				return TRUE;
-		}
+	if (ifname && *ifname) {
+		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+		                                  0, ifname, TRUE, NM_LINK_TYPE_NONE, NULL, NULL);
 	}
-	return FALSE;
+	return obj ? &obj->link : NULL;
+}
+
+struct _nm_platform_link_get_by_address_data {
+	gconstpointer address;
+	guint8 length;
+};
+
+static gboolean
+_nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_platform_link_get_by_address_data *d)
+{
+	return obj->link.addr.len == d->length && !memcmp (obj->link.addr.data, d->address, d->length);
+}
+
+static const NMPlatformLink *
+_nm_platform_link_get_by_address (NMPlatform *platform,
+                                  gconstpointer address,
+                                  size_t length)
+{
+	const NMPObject *obj;
+	struct _nm_platform_link_get_by_address_data d = {
+		.address = address,
+		.length = length,
+	};
+
+	if (length <= 0 || length > NM_UTILS_HWADDR_LEN_MAX)
+		return NULL;
+	if (!address)
+		return NULL;
+
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, NULL, TRUE, NM_LINK_TYPE_NONE,
+	                                  (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, &d);
+	return obj ? &obj->link : NULL;
 }
 
 static struct nl_object *
@@ -2261,331 +2604,392 @@ build_rtnl_link (int ifindex, const char *name, NMLinkType type)
 	struct rtnl_link *rtnllink;
 	int nle;
 
-	rtnllink = _nm_rtnl_link_alloc (ifindex, name);
+	rtnllink = _nl_rtnl_link_alloc (ifindex, name);
 	if (type) {
-		nle = rtnl_link_set_type (rtnllink, type_to_string (type));
+		nle = rtnl_link_set_type (rtnllink, nm_link_type_to_rtnl_type_string (type));
 		g_assert (!nle);
 	}
 	return (struct nl_object *) rtnllink;
 }
 
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+	const NMPlatformLink *obj = (const NMPlatformLink *) _obj;
+
+	return build_rtnl_link (obj->ifindex,
+	                        obj->name[0] ? obj->name : NULL,
+	                        obj->type);
+}
+
 static gboolean
-link_add (NMPlatform *platform, const char *name, NMLinkType type, const void *address, size_t address_len)
+do_add_link (NMPlatform *platform, const char *name, const struct rtnl_link *nlo)
 {
-	int r;
-	struct nl_object *l;
+	NMPObject obj_needle;
+	int nle;
 
-	if (type == NM_LINK_TYPE_BOND) {
-		/* When the kernel loads the bond module, either via explicit modprobe
-		 * or automatically in response to creating a bond master, it will also
-		 * create a 'bond0' interface.  Since the bond we're about to create may
-		 * or may not be named 'bond0' prevent potential confusion about a bond
-		 * that the user didn't want by telling the bonding module not to create
-		 * bond0 automatically.
-		 */
-		if (!g_file_test ("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS))
-			/* Ignore return value to shut up the compiler */
-			r = system ("modprobe bonding max_bonds=0");
-	}
+	event_handler_read_netlink_all (platform, FALSE);
 
-	debug ("link: add link '%s' of type '%s' (%d)",
-	       name, type_to_string (type), (int) type);
+	nle = kernel_add_object (platform, NMP_OBJECT_TYPE_LINK, (const struct nl_object *) nlo);
+	if (nle < 0) {
+		_LOGE ("do-add-link: failure adding link '%s': %s", name, nl_geterror (nle));
+		return FALSE;
+	}
+	_LOGD ("do-add-link: success adding link '%s'", name);
 
-	l = build_rtnl_link (0, name, type);
+	nmp_object_stackinit_id_link (&obj_needle, 0);
+	g_strlcpy (obj_needle.link.name, name, sizeof (obj_needle.link.name));
 
-	g_assert ( (address != NULL) ^ (address_len == 0) );
-	if (address) {
-		auto_nl_addr struct nl_addr *nladdr = _nm_nl_addr_build (AF_LLC, address, address_len);
+	delayed_action_handle_all (platform, TRUE);
 
-		rtnl_link_set_addr ((struct rtnl_link *) l, nladdr);
+	/* FIXME: we add the link object via the second netlink socket. Sometimes,
+	 * the notification is not yet ready via nlh_event, so we have to re-request the
+	 * link so that it is in the cache. A better solution would be to do everything
+	 * via one netlink socket. */
+	if (!nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, 0, obj_needle.link.name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
+		_LOGT ("do-add-link: reload: the added link is not yet ready. Request %s", obj_needle.link.name);
+		do_request_link (platform, 0, obj_needle.link.name, TRUE);
 	}
-	return add_object (platform, l);
+
+	/* Return true, because kernel_add_object() succeeded. This doesn't indicate that the
+	 * object is now actuall in the cache, because there could be a race.
+	 *
+	 * For that, you'd have to look at @out_obj. */
+	return TRUE;
 }
 
-static struct rtnl_link *
-link_get (NMPlatform *platform, int ifindex)
+static gboolean
+do_add_link_with_lookup (NMPlatform *platform, const char *name, const struct rtnl_link *nlo, NMLinkType expected_link_type, NMPlatformLink *out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct rtnl_link *rtnllink = rtnl_link_get (priv->link_cache, ifindex);
+	const NMPObject *obj;
 
-	if (!rtnllink) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		return NULL;
+	do_add_link (platform, name, nlo);
+
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, name, FALSE, expected_link_type, NULL, NULL);
+	if (out_link && obj)
+		*out_link = obj->link;
+	return !!obj;
+}
+
+static gboolean
+do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
+{
+	int nle;
+
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
+	                      NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                      NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
+
+	event_handler_read_netlink_all (platform, FALSE);
+
+	nle = kernel_add_object (platform, NMP_OBJECT_GET_CLASS (obj_id)->obj_type, (const struct nl_object *) nlo);
+	if (nle < 0) {
+		_LOGW ("do-add-%s: failure adding %s '%s': %s (%d)",
+		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
+		       nl_geterror (nle), -nle);
+		return FALSE;
 	}
+	_LOGD ("do-add-%s: success adding object %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 
-	/* physical interfaces must be found by udev before they can be used */
-	if (!link_is_announceable (platform, rtnllink)) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		rtnl_link_put (rtnllink);
-		return NULL;
+	delayed_action_handle_all (platform, TRUE);
+
+	/* FIXME: instead of re-requesting the added object, add it via nlh_event
+	 * so that the events are in sync. */
+	if (!nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj_id)) {
+		_LOGT ("do-add-%s: reload: the added object is not yet ready. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
 	}
 
-	return rtnllink;
+	/* The return value doesn't say, whether the object is in the platform cache after adding
+	 * it.
+	 * Instead the return value says, whether kernel_add_object() succeeded. */
+	return TRUE;
 }
 
+
 static gboolean
-link_change (NMPlatform *platform, int ifindex, struct rtnl_link *change)
+do_delete_object (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+	auto_nl_object struct nl_object *nlo_free = NULL;
 	int nle;
 
-	if (!rtnllink)
-		return FALSE;
-	g_return_val_if_fail (rtnl_link_get_ifindex (change) > 0, FALSE);
+	event_handler_read_netlink_all (platform, FALSE);
 
-	nle = rtnl_link_change (priv->nlh, rtnllink, change, 0);
+	if (!nlo)
+		nlo = nlo_free = nmp_object_to_nl (platform, obj_id, FALSE);
+
+	nle = kernel_delete_object (platform, NMP_OBJECT_GET_TYPE (obj_id), nlo);
+	if (nle < 0)
+		_LOGE ("do-delete-%s: failure deleting '%s': %s (%d)", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), nl_geterror (nle), -nle);
+	else
+		_LOGD ("do-delete-%s: success deleting '%s'", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+
+	delayed_action_handle_all (platform, TRUE);
+
+	/* FIXME: instead of re-requesting the deleted object, add it via nlh_event
+	 * so that the events are in sync. */
+	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_LINK) {
+		const NMPObject *obj;
+
+		obj = nmp_cache_lookup_link_full (priv->cache, obj_id->link.ifindex, obj_id->link.ifindex <= 0 && obj_id->link.name[0] ? obj_id->link.name : NULL, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
+		if (obj && obj->_link.netlink.is_in_netlink) {
+			_LOGT ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			do_request_link (platform, obj_id->link.ifindex, obj_id->link.name, TRUE);
+		}
+	} else {
+		if (nmp_cache_lookup_obj (priv->cache, obj_id)) {
+			_LOGT ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
+		}
+	}
+
+	/* The return value doesn't say, whether the object is in the platform cache after adding
+	 * it.
+	 * Instead the return value says, whether kernel_add_object() succeeded. */
+	return nle >= 0;
+}
+
+static NMPlatformError
+do_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean complete_from_cache)
+{
+	int nle;
+	int ifindex;
+	gboolean complete_from_cache2 = complete_from_cache;
+
+	ifindex = rtnl_link_get_ifindex (nlo);
+	if (ifindex <= 0)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+
+	nle = kernel_change_link (platform, nlo, &complete_from_cache2);
 
-	/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-	 * never know when something sends an identical object just before
-	 * NetworkManager.
-	 *
-	 * When netlink returns NLE_OBJ_NOTFOUND, it usually means it failed to find
-	 * firmware for the device, especially on nm_platform_link_set_up ().
-	 * This is basically the same check as in the original code and could
-	 * potentially be improved.
-	 */
 	switch (nle) {
 	case -NLE_SUCCESS:
+		_LOGD ("do-change-link: success changing link %d", ifindex);
+		break;
 	case -NLE_EXIST:
+		_LOGD ("do-change-link: success changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle);
 		break;
 	case -NLE_OBJ_NOTFOUND:
-		error ("Firmware not found for changing link %s; Netlink error: %s)", to_string_link (platform, change), nl_geterror (nle));
-		platform->error = NM_PLATFORM_ERROR_NO_FIRMWARE;
-		return FALSE;
+		/* fall-through */
 	default:
-		error ("Netlink error changing link %s: %s", to_string_link (platform, change), nl_geterror (nle));
-		return FALSE;
+		if (complete_from_cache != complete_from_cache2)
+			_LOGD ("do-change-link: failure changing link %d: link does not exist in cache", ifindex);
+		else
+			_LOGE ("do-change-link: failure changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle);
+		return nle == -NLE_OBJ_NOTFOUND ? NM_PLATFORM_ERROR_NO_FIRMWARE : NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
 
-	return refresh_object (platform, (struct nl_object *) rtnllink, FALSE, NM_PLATFORM_REASON_INTERNAL);
+	/* FIXME: as we modify the link via a separate socket, the cache is not in
+	 * sync and we have to refetch the link. */
+	do_request_link (platform, ifindex, NULL, TRUE);
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
-link_delete (NMPlatform *platform, int ifindex)
+link_add (NMPlatform *platform,
+          const char *name,
+          NMLinkType type,
+          const void *address,
+          size_t address_len,
+          NMPlatformLink *out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct rtnl_link *rtnllink = rtnl_link_get (priv->link_cache, ifindex);
+	auto_nl_object struct nl_object *l = NULL;
 
-	if (!rtnllink) {
-		platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
-		return FALSE;
+	if (type == NM_LINK_TYPE_BOND) {
+		/* When the kernel loads the bond module, either via explicit modprobe
+		 * or automatically in response to creating a bond master, it will also
+		 * create a 'bond0' interface.  Since the bond we're about to create may
+		 * or may not be named 'bond0' prevent potential confusion about a bond
+		 * that the user didn't want by telling the bonding module not to create
+		 * bond0 automatically.
+		 */
+		if (!g_file_test ("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS))
+			nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
-	return delete_object (platform, build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_NONE), TRUE);
-}
+	debug ("link: add link '%s' of type '%s' (%d)",
+	       name, nm_link_type_to_string (type), (int) type);
 
-static int
-link_get_ifindex (NMPlatform *platform, const char *ifname)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	l = build_rtnl_link (0, name, type);
 
-	return rtnl_link_name2i (priv->link_cache, ifname);
-}
+	g_assert ( (address != NULL) ^ (address_len == 0) );
+	if (address) {
+		auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, address_len);
 
-static const char *
-link_get_name (NMPlatform *platform, int ifindex)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+		rtnl_link_set_addr ((struct rtnl_link *) l, nladdr);
+	}
 
-	return rtnllink ? rtnl_link_get_name (rtnllink) : NULL;
+	return do_add_link_with_lookup (platform, name, (struct rtnl_link *) l, type, out_link);
 }
 
-static NMLinkType
-link_get_type (NMPlatform *platform, int ifindex)
+static gboolean
+link_delete (NMPlatform *platform, int ifindex)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	NMPObject obj_needle;
+	const NMPObject *obj;
+
+	obj = nmp_cache_lookup_link (priv->cache, ifindex);
+	if (!obj || !obj->_link.netlink.is_in_netlink)
+		return FALSE;
 
-	return link_extract_type (platform, rtnllink, NULL);
+	nmp_object_stackinit_id_link (&obj_needle, ifindex);
+	return do_delete_object (platform, &obj_needle, NULL);
 }
 
 static const char *
 link_get_type_name (NMPlatform *platform, int ifindex)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-	const char *type;
-
-	link_extract_type (platform, rtnllink, &type);
-	return type;
-}
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
 
-static guint32
-link_get_flags (NMPlatform *platform, int ifindex)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-
-	if (!rtnllink)
-		return IFF_NOARP;
+	if (!obj)
+		return NULL;
 
-	return rtnl_link_get_flags (rtnllink);
+	if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
+		/* We could detect the @link_type. In this case the function returns
+		 * our internel module names, which differs from rtnl_link_get_type():
+		 *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
+		 *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
+		 * Note that this functions is only used by NMDeviceGeneric to
+		 * set type_description. */
+		return nm_link_type_to_string (obj->link.type);
+	}
+	/* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */
+	return str_if_set (obj->link.kind, "unknown");
 }
 
 static gboolean
-link_refresh (NMPlatform *platform, int ifindex)
+link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged)
 {
-	auto_nl_object struct rtnl_link *rtnllink = _nm_rtnl_link_alloc (ifindex, NULL);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *link;
+	GUdevDevice *udev_device = NULL;
 
-	return refresh_object (platform, (struct nl_object *) rtnllink, FALSE, NM_PLATFORM_REASON_EXTERNAL);
-}
+	link = nmp_cache_lookup_link (priv->cache, ifindex);
+	if (link)
+		udev_device = link->_link.udev.device;
 
-static gboolean
-link_is_up (NMPlatform *platform, int ifindex)
-{
-	return !!(link_get_flags (platform, ifindex) & IFF_UP);
-}
+	if (udev_device && g_udev_device_get_property (udev_device, "NM_UNMANAGED")) {
+		*unmanaged = g_udev_device_get_property_as_boolean (udev_device, "NM_UNMANAGED");
+		return TRUE;
+	}
 
-static gboolean
-link_is_connected (NMPlatform *platform, int ifindex)
-{
-	return !!(link_get_flags (platform, ifindex) & IFF_LOWER_UP);
+	return FALSE;
 }
 
 static gboolean
-link_uses_arp (NMPlatform *platform, int ifindex)
+link_refresh (NMPlatform *platform, int ifindex)
 {
-	return !(link_get_flags (platform, ifindex) & IFF_NOARP);
+	do_request_link (platform, ifindex, NULL, TRUE);
+	return !!cache_lookup_link (platform, ifindex);
 }
 
-static gboolean
+static NMPlatformError
 link_change_flags (NMPlatform *platform, int ifindex, unsigned int flags, gboolean value)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
+	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
+	const NMPObject *obj_cache;
+	char buf[256];
 
+	obj_cache = cache_lookup_link (platform, ifindex);
+	if (!obj_cache)
+		return NM_PLATFORM_ERROR_NOT_FOUND;
+
+	rtnl_link_set_flags (change, obj_cache->link.flags);
 	if (value)
 		rtnl_link_set_flags (change, flags);
 	else
 		rtnl_link_unset_flags (change, flags);
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-		char buf[512];
-
-		rtnl_link_flags2str (flags, buf, sizeof (buf));
-		debug ("link: change %d: flags %s '%s' (%d)", ifindex, value ? "set" : "unset", buf, flags);
-	}
+	_LOGD ("link: change %d: flags %s '%s' (%d)", ifindex,
+	       value ? "set" : "unset",
+	       rtnl_link_flags2str (flags, buf, sizeof (buf)),
+	       flags);
 
-	return link_change (platform, ifindex, change);
+	return do_change_link (platform, change, FALSE);
 }
 
 static gboolean
-link_set_up (NMPlatform *platform, int ifindex)
+link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, TRUE);
+	NMPlatformError plerr;
+
+	plerr = link_change_flags (platform, ifindex, IFF_UP, TRUE);
+	if (out_no_firmware)
+		*out_no_firmware = plerr == NM_PLATFORM_ERROR_NO_FIRMWARE;
+	return plerr == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_down (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, FALSE);
+	return link_change_flags (platform, ifindex, IFF_UP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_arp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, FALSE);
+	return link_change_flags (platform, ifindex, IFF_NOARP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_noarp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, TRUE);
+	return link_change_flags (platform, ifindex, IFF_NOARP, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gboolean
-link_get_user_ipv6ll_enabled (NMPlatform *platform, int ifindex)
+static const char *
+link_get_udi (NMPlatform *platform, int ifindex)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
 
-	if (priv->support_user_ipv6ll > 0) {
-		auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-		uint8_t mode = 0;
+	if (   !obj
+	    || !obj->_link.netlink.is_in_netlink
+	    || !obj->_link.udev.device)
+		return NULL;
+	return g_udev_device_get_sysfs_path (obj->_link.udev.device);
+}
 
-		if (rtnllink) {
-			if (rtnl_link_inet6_get_addr_gen_mode (rtnllink, &mode) != 0) {
-				/* Default to "disabled" on error */
-				return FALSE;
-			}
-			return mode == IN6_ADDR_GEN_MODE_NONE;
-		}
-	}
-#endif
-	return FALSE;
+static GObject *
+link_get_udev_device (NMPlatform *platform, int ifindex)
+{
+	const NMPObject *obj_cache;
+
+	/* we don't use cache_lookup_link() because this would return NULL
+	 * if the link is not visible in libnl. For link_get_udev_device()
+	 * we want to return whatever we have, even if the link itself
+	 * appears invisible via other platform functions. */
+
+	obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
+	return obj_cache ? (GObject *) obj_cache->_link.udev.device : NULL;
 }
 
 static gboolean
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 #if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	if (check_support_user_ipv6ll (platform)) {
-		auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
+	if (_support_user_ipv6ll_get ()) {
+		auto_nl_object struct rtnl_link *nlo = _nl_rtnl_link_alloc (ifindex, NULL);
 		guint8 mode = enabled ? IN6_ADDR_GEN_MODE_NONE : IN6_ADDR_GEN_MODE_EUI64;
 		char buf[32];
 
-		rtnl_link_inet6_set_addr_gen_mode (change, mode);
+		rtnl_link_inet6_set_addr_gen_mode (nlo, mode);
 		debug ("link: change %d: set IPv6 address generation mode to %s",
 		       ifindex, rtnl_link_inet6_addrgenmode2str (mode, buf, sizeof (buf)));
-		return link_change (platform, ifindex, change);
+		return do_change_link (platform, nlo, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 	}
 #endif
 	return FALSE;
 }
 
 static gboolean
-supports_ethtool_carrier_detect (const char *ifname)
-{
-	struct ethtool_cmd edata = { .cmd = ETHTOOL_GLINK };
-
-	/* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we
-	 * assume the device supports carrier-detect, otherwise we assume it
-	 * doesn't.
-	 */
-	return ethtool_get (ifname, &edata);
-}
-
-static gboolean
-supports_mii_carrier_detect (const char *ifname)
-{
-	int fd;
-	struct ifreq ifr;
-	struct mii_ioctl_data *mii;
-	gboolean supports_mii = FALSE;
-
-	fd = socket (PF_INET, SOCK_DGRAM, 0);
-	if (fd < 0) {
-		nm_log_err (LOGD_PLATFORM, "couldn't open control socket.");
-		return FALSE;
-	}
-
-	memset (&ifr, 0, sizeof (struct ifreq));
-	strncpy (ifr.ifr_name, ifname, IFNAMSIZ);
-
-	errno = 0;
-	if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) {
-		nm_log_dbg (LOGD_PLATFORM, "SIOCGMIIPHY failed: %d", errno);
-		goto out;
-	}
-
-	/* If we can read the BMSR register, we assume that the card supports MII link detection */
-	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
-	mii->reg_num = MII_BMSR;
-
-	if (ioctl (fd, SIOCGMIIREG, &ifr) == 0) {
-		nm_log_dbg (LOGD_PLATFORM, "SIOCGMIIREG result 0x%X", mii->val_out);
-		supports_mii = TRUE;
-	} else {
-		nm_log_dbg (LOGD_PLATFORM, "SIOCGMIIREG failed: %d", errno);
-	}
-
- out:
-	close (fd);
-	nm_log_dbg (LOGD_PLATFORM, "MII %s supported", supports_mii ? "is" : "not");
-	return supports_mii;	
-}
-
-static gboolean
 link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 {
-	const char *name = nm_platform_link_get_name (ifindex);
+	const char *name = nm_platform_link_get_name (platform, ifindex);
 
 	if (!name)
 		return FALSE;
@@ -2594,103 +2998,56 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 	 * us whether the device actually supports carrier detection in the first
 	 * place. We assume any device that does implements one of these two APIs.
 	 */
-	return supports_ethtool_carrier_detect (name) || supports_mii_carrier_detect (name);
+	return nmp_utils_ethtool_supports_carrier_detect (name) || nmp_utils_mii_supports_carrier_detect (name);
 }
 
 static gboolean
 link_supports_vlans (NMPlatform *platform, int ifindex)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-	const char *name = nm_platform_link_get_name (ifindex);
-	gs_free struct ethtool_gfeatures *features = NULL;
-	int idx, block, bit, size;
+	const NMPObject *obj;
 
-	/* Only ARPHRD_ETHER links can possibly support VLANs. */
-	if (!rtnllink || rtnl_link_get_arptype (rtnllink) != ARPHRD_ETHER)
-		return FALSE;
+	obj = cache_lookup_link (platform, ifindex);
 
-	if (!name)
-		return FALSE;
-
-	idx = ethtool_get_stringset_index (name, ETH_SS_FEATURES, "vlan-challenged");
-	if (idx == -1) {
-		debug ("vlan-challenged ethtool feature does not exist?");
-		return FALSE;
-	}
-
-	block = idx /  32;
-	bit = idx % 32;
-	size = block + 1;
-
-	features = g_malloc0 (sizeof (*features) + size * sizeof (struct ethtool_get_features_block));
-	features->cmd = ETHTOOL_GFEATURES;
-	features->size = size;
-
-	if (!ethtool_get (name, features))
+	/* Only ARPHRD_ETHER links can possibly support VLANs. */
+	if (!obj || obj->link.arptype != ARPHRD_ETHER)
 		return FALSE;
 
-	return !(features->features[block].active & (1 << bit));
+	return nmp_utils_ethtool_supports_vlans (obj->link.name);
 }
 
 static gboolean
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
-	auto_nl_addr struct nl_addr *nladdr = _nm_nl_addr_build (AF_LLC, address, length);
+	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
+	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, length);
+	gs_free char *mac = NULL;
 
 	rtnl_link_set_addr (change, nladdr);
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
-		char *mac = nm_utils_hwaddr_ntoa (address, length);
-
-		debug ("link: change %d: address %s (%lu bytes)", ifindex, mac, (unsigned long) length);
-		g_free (mac);
-	}
-
-	return link_change (platform, ifindex, change);
+	_LOGD ("link: change %d: address %s (%lu bytes)", ifindex,
+	       (mac = nm_utils_hwaddr_ntoa (address, length)),
+	       (unsigned long) length);
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gconstpointer
-link_get_address (NMPlatform *platform, int ifindex, size_t *length)
+static gboolean
+link_get_permanent_address (NMPlatform *platform,
+                            int ifindex,
+                            guint8 *buf,
+                            size_t *length)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-	struct nl_addr *nladdr;
-	size_t l = 0;
-	gconstpointer a = NULL;
-
-	if (rtnllink &&
-	    (nladdr = rtnl_link_get_addr (rtnllink))) {
-		l = nl_addr_get_len (nladdr);
-		if (l > NM_UTILS_HWADDR_LEN_MAX) {
-			if (length)
-				*length = 0;
-			g_return_val_if_reached (NULL);
-		} else if (l > 0)
-			a = nl_addr_get_binary_addr (nladdr);
-	}
-
-	if (length)
-		*length = l;
-	return a;
+	return nmp_utils_ethtool_get_permanent_address (nm_platform_link_get_name (platform, ifindex), buf, length);
 }
 
 static gboolean
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
+	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
 
 	rtnl_link_set_mtu (change, mtu);
 	debug ("link: change %d: mtu %lu", ifindex, (unsigned long)mtu);
 
-	return link_change (platform, ifindex, change);
-}
-
-static guint32
-link_get_mtu (NMPlatform *platform, int ifindex)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
-
-	return rtnllink ? rtnl_link_get_mtu (rtnllink) : 0;
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static char *
@@ -2699,7 +3056,7 @@ link_get_physical_port_id (NMPlatform *platform, int ifindex)
 	const char *ifname;
 	char *path, *id;
 
-	ifname = nm_platform_link_get_name (ifindex);
+	ifname = nm_platform_link_get_name (platform, ifindex);
 	if (!ifname)
 		return NULL;
 
@@ -2719,7 +3076,7 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 	gs_free char *path = NULL, *id = NULL;
 	gint64 int_val;
 
-	ifname = nm_platform_link_get_name (ifindex);
+	ifname = nm_platform_link_get_name (platform, ifindex);
 	if (!ifname)
 		return 0;
 
@@ -2731,16 +3088,20 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 		return 0;
 
 	/* Value is reported as hex */
-	int_val = nm_utils_ascii_str_to_int64 (id, 16, 0, G_MAXUINT16, 0);
+	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)
-{
-	struct nl_object *object = build_rtnl_link (0, name, NM_LINK_TYPE_VLAN);
-	struct rtnl_link *rtnllink = (struct rtnl_link *) object;
+vlan_add (NMPlatform *platform,
+          const char *name,
+          int parent,
+          int vlan_id,
+          guint32 vlan_flags,
+          NMPlatformLink *out_link)
+{
+	auto_nl_object struct rtnl_link *rtnllink = (struct rtnl_link *) build_rtnl_link (0, name, NM_LINK_TYPE_VLAN);
 	unsigned int kernel_flags;
 
 	kernel_flags = 0;
@@ -2758,63 +3119,61 @@ vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint
 	debug ("link: add vlan '%s', parent %d, vlan id %d, flags %X (native: %X)",
 	       name, parent, vlan_id, (unsigned int) vlan_flags, kernel_flags);
 
-	return add_object (platform, object);
+	return do_add_link_with_lookup (platform, name, rtnllink, NM_LINK_TYPE_VLAN, out_link);
 }
 
 static gboolean
 vlan_get_info (NMPlatform *platform, int ifindex, int *parent, int *vlan_id)
 {
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
+	int p = 0, v = 0;
 
+	if (obj) {
+		p = obj->link.parent;
+		v = obj->link.vlan_id;
+	}
 	if (parent)
-		*parent = rtnllink ? rtnl_link_get_link (rtnllink) : 0;
+		*parent = p;
 	if (vlan_id)
-		*vlan_id = rtnllink ? rtnl_link_vlan_get_id (rtnllink) : 0;
-
-	return !!rtnllink;
+		*vlan_id = v;
+	return !!obj;
 }
 
 static gboolean
 vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to)
 {
-	/* We have to use link_get() because a "blank" rtnl_link won't have the
-	 * right data structures to be able to call rtnl_link_vlan_set_ingress_map()
-	 * on it. (Likewise below in vlan_set_egress_map().)
-	 */
-	auto_nl_object struct rtnl_link *change = link_get (platform, ifindex);
+	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
 
-	if (!change)
-		return FALSE;
+	rtnl_link_set_type (change, "vlan");
 	rtnl_link_vlan_set_ingress_map (change, from, to);
 
 	debug ("link: change %d: vlan ingress map %d -> %d", ifindex, from, to);
 
-	return link_change (platform, ifindex, change);
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to)
 {
-	auto_nl_object struct rtnl_link *change = link_get (platform, ifindex);
+	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
 
-	if (!change)
-		return FALSE;
+	rtnl_link_set_type (change, "vlan");
 	rtnl_link_vlan_set_egress_map (change, from, to);
 
 	debug ("link: change %d: vlan egress map %d -> %d", ifindex, from, to);
 
-	return link_change (platform, ifindex, change);
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_enslave (NMPlatform *platform, int master, int slave)
 {
-	auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (slave, NULL);
+	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (slave, NULL);
 
 	rtnl_link_set_master (change, master);
 	debug ("link: change %d: enslave to master %d", slave, master);
 
-	return link_change (platform, slave, change);
+	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -2823,19 +3182,11 @@ link_release (NMPlatform *platform, int master, int slave)
 	return link_enslave (platform, 0, slave);
 }
 
-static int
-link_get_master (NMPlatform *platform, int slave)
-{
-	auto_nl_object struct rtnl_link *rtnllink = link_get (platform, slave);
-
-	return rtnllink ? rtnl_link_get_master (rtnllink) : 0;
-}
-
 static char *
-link_option_path (int master, const char *category, const char *option)
+link_option_path (NMPlatform *platform, int master, const char *category, const char *option)
 {
-	const char *name = nm_platform_link_get_name (master);
-   
+	const char *name = nm_platform_link_get_name (platform, master);
+
 	if (!name || !category || !option)
 		return NULL;
 
@@ -2846,31 +3197,30 @@ link_option_path (int master, const char *category, const char *option)
 }
 
 static gboolean
-link_set_option (int master, const char *category, const char *option, const char *value)
+link_set_option (NMPlatform *platform, int master, const char *category, const char *option, const char *value)
 {
-	gs_free char *path = link_option_path (master, category, option);
+	gs_free char *path = link_option_path (platform, master, category, option);
 
-	return path && nm_platform_sysctl_set (path, value);
+	return path && nm_platform_sysctl_set (platform, path, value);
 }
 
 static char *
-link_get_option (int master, const char *category, const char *option)
+link_get_option (NMPlatform *platform, int master, const char *category, const char *option)
 {
-	gs_free char *path = link_option_path (master, category, option);
+	gs_free char *path = link_option_path (platform, master, category, option);
 
-	return path ? nm_platform_sysctl_get (path) : NULL;
+	return path ? nm_platform_sysctl_get (platform, path) : NULL;
 }
 
 static const char *
 master_category (NMPlatform *platform, int master)
 {
-	switch (link_get_type (platform, master)) {
+	switch (nm_platform_link_get_type (platform, master)) {
 	case NM_LINK_TYPE_BRIDGE:
 		return "bridge";
 	case NM_LINK_TYPE_BOND:
 		return "bonding";
 	default:
-		g_return_val_if_reached (NULL);
 		return NULL;
 	}
 }
@@ -2878,18 +3228,15 @@ master_category (NMPlatform *platform, int master)
 static const char *
 slave_category (NMPlatform *platform, int slave)
 {
-	int master = link_get_master (platform, slave);
+	int master = nm_platform_link_get_master (platform, slave);
 
-	if (master <= 0) {
-		platform->error = NM_PLATFORM_ERROR_NOT_SLAVE;
+	if (master <= 0)
 		return NULL;
-	}
 
-	switch (link_get_type (platform, master)) {
+	switch (nm_platform_link_get_type (platform, master)) {
 	case NM_LINK_TYPE_BRIDGE:
 		return "brport";
 	default:
-		g_return_val_if_reached (NULL);
 		return NULL;
 	}
 }
@@ -2897,84 +3244,187 @@ slave_category (NMPlatform *platform, int slave)
 static gboolean
 master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
 {
-	return link_set_option (master, master_category (platform, master), option, value);
+	return link_set_option (platform, master, master_category (platform, master), option, value);
 }
 
 static char *
 master_get_option (NMPlatform *platform, int master, const char *option)
 {
-	return link_get_option (master, master_category (platform, master), option);
+	return link_get_option (platform, master, master_category (platform, master), option);
 }
 
 static gboolean
 slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value)
 {
-	return link_set_option (slave, slave_category (platform, slave), option, value);
+	return link_set_option (platform, slave, slave_category (platform, slave), option, value);
 }
 
 static char *
 slave_get_option (NMPlatform *platform, int slave, const char *option)
 {
-	return link_get_option (slave, slave_category (platform, slave), option);
+	return link_get_option (platform, slave, slave_category (platform, slave), option);
 }
 
 static gboolean
-infiniband_partition_add (NMPlatform *platform, int parent, int p_key)
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link)
 {
-	const char *parent_name;
-	char *path, *id;
-	gboolean success;
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj_parent;
+	const NMPObject *obj;
+	gs_free char *path = NULL;
+	gs_free char *id = NULL;
+	gs_free char *ifname = NULL;
+
+	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
+	if (!obj_parent || !obj_parent->link.name[0])
+		g_return_val_if_reached (FALSE);
 
-	parent_name = nm_platform_link_get_name (parent);
-	g_return_val_if_fail (parent_name != NULL, FALSE);
+	ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
 
-	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (parent_name));
+	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
 	id = g_strdup_printf ("0x%04x", p_key);
-	success = nm_platform_sysctl_set (path, id);
-	g_free (id);
-	g_free (path);
+	if (!nm_platform_sysctl_set (platform, path, id))
+		return FALSE;
 
-	if (success) {
-		gs_free char *ifname = g_strdup_printf ("%s.%04x", parent_name, p_key);
-		auto_nl_object struct rtnl_link *rtnllink = _nm_rtnl_link_alloc (0, ifname);
+	do_request_link (platform, 0, ifname, TRUE);
 
-		success = refresh_object (platform, (struct nl_object *) rtnllink, FALSE, NM_PLATFORM_REASON_INTERNAL);
+	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 && obj)
+		*out_link = obj->link;
+	return !!obj;
+}
+
+typedef struct {
+	int p_key;
+	const char *mode;
+} IpoibInfo;
+
+/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers
+ * we're building against might not have those properties even though the
+ * running kernel might.
+ */
+#define IFLA_IPOIB_UNSPEC 0
+#define IFLA_IPOIB_PKEY   1
+#define IFLA_IPOIB_MODE   2
+#define IFLA_IPOIB_UMCAST 3
+#undef IFLA_IPOIB_MAX
+#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST
+
+#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
+#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
+
+static const struct nla_policy infiniband_info_policy[IFLA_IPOIB_MAX + 1] = {
+	[IFLA_IPOIB_PKEY]	= { .type = NLA_U16 },
+	[IFLA_IPOIB_MODE]	= { .type = NLA_U16 },
+	[IFLA_IPOIB_UMCAST]	= { .type = NLA_U16 },
+};
+
+static int
+infiniband_info_data_parser (struct nlattr *info_data, gpointer parser_data)
+{
+	IpoibInfo *info = parser_data;
+	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
+	int err;
+
+	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data,
+	                        (struct nla_policy *) infiniband_info_policy);
+	if (err < 0)
+		return err;
+	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
+		return -EINVAL;
+
+	info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]);
+
+	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
+	case IPOIB_MODE_DATAGRAM:
+		info->mode = "datagram";
+		break;
+	case IPOIB_MODE_CONNECTED:
+		info->mode = "connected";
+		break;
+	default:
+		return -NLE_PARSE_ERR;
 	}
 
-	return success;
+	return 0;
+}
+
+static gboolean
+infiniband_get_info (NMPlatform *platform, int ifindex, int *parent, int *p_key, const char **mode)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const NMPObject *obj;
+	IpoibInfo info = { -1, NULL };
+
+	obj = cache_lookup_link (platform, ifindex);
+	if (!obj)
+		return FALSE;
+
+	if (parent)
+		*parent = obj->link.parent;
+
+	if (_nl_link_parse_info_data (priv->nlh,
+	                              ifindex,
+	                              infiniband_info_data_parser,
+	                              &info) != 0) {
+		const char *iface = obj->link.name;
+		char *path, *contents = NULL;
+
+		/* Fall back to reading sysfs */
+		path = g_strdup_printf ("/sys/class/net/%s/mode", ASSERT_VALID_PATH_COMPONENT (iface));
+		contents = nm_platform_sysctl_get (platform, path);
+		g_free (path);
+		if (!contents)
+			return FALSE;
+
+		if (strstr (contents, "datagram"))
+			info.mode = "datagram";
+		else if (strstr (contents, "connected"))
+			info.mode = "connected";
+		g_free (contents);
+
+		path = g_strdup_printf ("/sys/class/net/%s/pkey", ASSERT_VALID_PATH_COMPONENT (iface));
+		contents = nm_platform_sysctl_get (platform, path);
+		g_free (path);
+		if (!contents)
+			return FALSE;
+
+		info.p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
+		g_free (contents);
+
+		if (info.p_key < 0)
+			return FALSE;
+	}
+
+	if (p_key)
+		*p_key = info.p_key;
+	if (mode)
+		*mode = info.mode;
+	return TRUE;
 }
 
 static gboolean
 veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props)
 {
 	const char *ifname;
-	gs_free struct ethtool_stats *stats = NULL;
-	int peer_ifindex_stat;
+	int peer_ifindex;
 
-	ifname = nm_platform_link_get_name (ifindex);
+	ifname = nm_platform_link_get_name (platform, ifindex);
 	if (!ifname)
 		return FALSE;
 
-	peer_ifindex_stat = ethtool_get_stringset_index (ifname, ETH_SS_STATS, "peer_ifindex");
-	if (peer_ifindex_stat == -1) {
-		debug ("%s: peer_ifindex ethtool stat does not exist?", ifname);
+	peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (ifname);
+	if (peer_ifindex <= 0)
 		return FALSE;
-	}
 
-	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
-	stats->cmd = ETHTOOL_GSTATS;
-	stats->n_stats = peer_ifindex_stat + 1;
-	if (!ethtool_get (ifname, stats))
-		return FALSE;
-
-	props->peer = stats->data[peer_ifindex_stat];
+	props->peer = peer_ifindex;
 	return TRUE;
 }
 
 static gboolean
-tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
+tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props)
 {
-	const char *ifname;
 	char *path, *val;
 	gboolean success = TRUE;
 
@@ -2984,16 +3434,15 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 	props->owner = -1;
 	props->group = -1;
 
-	ifname = nm_platform_link_get_name (ifindex);
 	if (!ifname || !nm_utils_iface_valid_name (ifname))
 		return FALSE;
 	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
 
 	path = g_strdup_printf ("/sys/class/net/%s/owner", ifname);
-	val = nm_platform_sysctl_get (path);
+	val = nm_platform_sysctl_get (platform, path);
 	g_free (path);
 	if (val) {
-		props->owner = nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
+		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
 		if (errno)
 			success = FALSE;
 		g_free (val);
@@ -3001,10 +3450,10 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 		success = FALSE;
 
 	path = g_strdup_printf ("/sys/class/net/%s/group", ifname);
-	val = nm_platform_sysctl_get (path);
+	val = nm_platform_sysctl_get (platform, path);
 	g_free (path);
 	if (val) {
-		props->group = nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
+		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
 		if (errno)
 			success = FALSE;
 		g_free (val);
@@ -3012,12 +3461,12 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 		success = FALSE;
 
 	path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname);
-	val = nm_platform_sysctl_get (path);
+	val = nm_platform_sysctl_get (platform, path);
 	g_free (path);
 	if (val) {
 		gint64 flags;
 
-		flags = nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
+		flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
 		if (!errno) {
 #ifndef IFF_MULTI_QUEUE
 			const int IFF_MULTI_QUEUE = 0x0100;
@@ -3035,6 +3484,12 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *
 	return success;
 }
 
+static gboolean
+tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
+{
+	return tun_get_properties_ifname (platform, nm_platform_link_get_name (platform, ifindex), props);
+}
+
 static const struct nla_policy macvlan_info_policy[IFLA_MACVLAN_MAX + 1] = {
 	[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
 #ifdef MACVLAN_FLAG_NOPROMISC
@@ -3084,20 +3539,20 @@ static gboolean
 macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
 	int err;
+	const NMPObject *obj;
 
-	rtnllink = link_get (platform, ifindex);
-	if (!rtnllink)
+	obj = cache_lookup_link (platform, ifindex);
+	if (!obj)
 		return FALSE;
 
-	props->parent_ifindex = rtnl_link_get_link (rtnllink);
+	props->parent_ifindex = obj->link.parent;
 
-	err = nm_rtnl_link_parse_info_data (priv->nlh, ifindex,
-	                                    macvlan_info_data_parser, props);
+	err = _nl_link_parse_info_data (priv->nlh, ifindex,
+	                                macvlan_info_data_parser, props);
 	if (err != 0) {
 		warning ("(%s) could not read properties: %s",
-		         rtnl_link_get_name (rtnllink), nl_geterror (err));
+		         obj->link.name, nl_geterror (err));
 	}
 	return (err == 0);
 }
@@ -3224,11 +3679,11 @@ vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperti
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int err;
 
-	err = nm_rtnl_link_parse_info_data (priv->nlh, ifindex,
-	                                    vxlan_info_data_parser, props);
+	err = _nl_link_parse_info_data (priv->nlh, ifindex,
+	                                vxlan_info_data_parser, props);
 	if (err != 0) {
-		warning ("(%s) could not read properties: %s",
-		         link_get_name (platform, ifindex), nl_geterror (err));
+		warning ("(%s) could not read vxlan properties: %s",
+		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
 	}
 	return (err == 0);
 }
@@ -3278,11 +3733,11 @@ gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int err;
 
-	err = nm_rtnl_link_parse_info_data (priv->nlh, ifindex,
-	                                    gre_info_data_parser, props);
+	err = _nl_link_parse_info_data (priv->nlh, ifindex,
+	                                gre_info_data_parser, props);
 	if (err != 0) {
-		warning ("(%s) could not read properties: %s",
-		         link_get_name (platform, ifindex), nl_geterror (err));
+		warning ("(%s) could not read gre properties: %s",
+		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
 	}
 	return (err == 0);
 }
@@ -3295,26 +3750,25 @@ wifi_get_wifi_data (NMPlatform *platform, int ifindex)
 
 	wifi_data = g_hash_table_lookup (priv->wifi_data, GINT_TO_POINTER (ifindex));
 	if (!wifi_data) {
-		NMLinkType type;
-		const char *ifname;
-
-		type = link_get_type (platform, ifindex);
-		ifname = link_get_name (platform, ifindex);
-
-		if (type == NM_LINK_TYPE_WIFI)
-			wifi_data = wifi_utils_init (ifname, ifindex, TRUE);
-		else if (type == NM_LINK_TYPE_OLPC_MESH) {
-			/* The kernel driver now uses nl80211, but we force use of WEXT because
-			 * the cfg80211 interactions are not quite ready to support access to
-			 * mesh control through nl80211 just yet.
-			 */
+		const NMPlatformLink *pllink;
+
+		pllink = nm_platform_link_get (platform, ifindex);
+		if (pllink) {
+			if (pllink->type == NM_LINK_TYPE_WIFI)
+				wifi_data = wifi_utils_init (pllink->name, ifindex, TRUE);
+			else if (pllink->type == NM_LINK_TYPE_OLPC_MESH) {
+				/* The kernel driver now uses nl80211, but we force use of WEXT because
+				 * the cfg80211 interactions are not quite ready to support access to
+				 * mesh control through nl80211 just yet.
+				 */
 #if HAVE_WEXT
-			wifi_data = wifi_wext_init (ifname, ifindex, FALSE);
+				wifi_data = wifi_wext_init (pllink->name, ifindex, FALSE);
 #endif
-		}
+			}
 
-		if (wifi_data)
-			g_hash_table_insert (priv->wifi_data, GINT_TO_POINTER (ifindex), wifi_data);
+			if (wifi_data)
+				g_hash_table_insert (priv->wifi_data, GINT_TO_POINTER (ifindex), wifi_data);
+		}
 	}
 
 	return wifi_data;
@@ -3460,18 +3914,11 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 static gboolean
 link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 {
-	NMLinkType type = link_get_type (platform, ifindex);
-
-	if (type == NM_LINK_TYPE_ETHERNET) {
-		struct ethtool_wolinfo wol;
+	NMLinkType type = nm_platform_link_get_type (platform, ifindex);
 
-		memset (&wol, 0, sizeof (wol));
-		wol.cmd = ETHTOOL_GWOL;
-		if (!ethtool_get (link_get_name (platform, ifindex), &wol))
-			return FALSE;
-
-		return wol.wolopts != 0;
-	} else if (type == NM_LINK_TYPE_WIFI) {
+	if (type == NM_LINK_TYPE_ETHERNET)
+		return nmp_utils_ethtool_get_wake_on_lan (nm_platform_link_get_name (platform, ifindex));
+	else if (type == NM_LINK_TYPE_WIFI) {
 		WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
 
 		if (!wifi_data)
@@ -3482,55 +3929,45 @@ link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 		return FALSE;
 }
 
-/******************************************************************/
-
 static gboolean
-_address_match (struct rtnl_addr *addr, int family, int ifindex)
+link_get_driver_info (NMPlatform *platform,
+                      int ifindex,
+                      char **out_driver_name,
+                      char **out_driver_version,
+                      char **out_fw_version)
 {
-	g_return_val_if_fail (addr, FALSE);
-
-	return rtnl_addr_get_family (addr) == family &&
-	       (ifindex == 0 || rtnl_addr_get_ifindex (addr) == ifindex);
+	return nmp_utils_ethtool_get_driver_info (nm_platform_link_get_name (platform, ifindex),
+	                                          out_driver_name,
+	                                          out_driver_version,
+	                                          out_fw_version);
 }
 
+/******************************************************************/
+
 static GArray *
-ip4_address_get_all (NMPlatform *platform, int ifindex)
+ipx_address_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *addresses;
-	NMPlatformIP4Address address;
-	struct nl_object *object;
 
-	addresses = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Address));
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
 
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		if (_address_match ((struct rtnl_addr *) object, AF_INET, ifindex)) {
-			if (init_ip4_address (&address, (struct rtnl_addr *) object))
-				g_array_append_val (addresses, address);
-		}
-	}
+	return nmp_cache_lookup_multi_to_array (priv->cache,
+	                                        obj_type,
+	                                        nmp_cache_id_init_addrroute_visible_by_ifindex (NMP_CACHE_ID_STATIC,
+	                                                                                        obj_type,
+	                                                                                        ifindex));
+}
 
-	return addresses;
+static GArray *
+ip4_address_get_all (NMPlatform *platform, int ifindex)
+{
+	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS);
 }
 
 static GArray *
 ip6_address_get_all (NMPlatform *platform, int ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *addresses;
-	NMPlatformIP6Address address;
-	struct nl_object *object;
-
-	addresses = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Address));
-
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		if (_address_match ((struct rtnl_addr *) object, AF_INET6, ifindex)) {
-			if (init_ip6_address (&address, (struct rtnl_addr *) object))
-				g_array_append_val (addresses, address);
-		}
-	}
-
-	return addresses;
+	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
 }
 
 #define IPV4LL_NETWORK (htonl (0xA9FE0000L))
@@ -3543,7 +3980,8 @@ ip4_is_link_local (const struct in_addr *src)
 }
 
 static struct nl_object *
-build_rtnl_addr (int family,
+build_rtnl_addr (NMPlatform *platform,
+                 int family,
                  int ifindex,
                  gconstpointer addr,
                  gconstpointer peer_addr,
@@ -3553,10 +3991,10 @@ build_rtnl_addr (int family,
                  guint flags,
                  const char *label)
 {
-	auto_nl_object struct rtnl_addr *rtnladdr = _nm_rtnl_addr_alloc (ifindex);
+	auto_nl_object struct rtnl_addr *rtnladdr = _nl_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);
+	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (family, addr, addrlen);
 	int nle;
 
 	/* IP address */
@@ -3568,7 +4006,7 @@ build_rtnl_addr (int family,
 
 	/* Tighten scope (IPv4 only) */
 	if (family == AF_INET && ip4_is_link_local (addr))
-		rtnl_addr_set_scope (rtnladdr, rtnl_str2scope ("link"));
+		rtnl_addr_set_scope (rtnladdr, RT_SCOPE_LINK);
 
 	/* IPv4 Broadcast address */
 	if (family == AF_INET) {
@@ -3576,14 +4014,14 @@ build_rtnl_addr (int family,
 		auto_nl_addr struct nl_addr *bcaddr = NULL;
 
 		bcast = *((in_addr_t *) addr) | ~nm_utils_ip4_prefix_to_netmask (plen);
-		bcaddr = _nm_nl_addr_build (family, &bcast, addrlen);
+		bcaddr = _nl_addr_build (family, &bcast, addrlen);
 		g_assert (bcaddr);
 		rtnl_addr_set_broadcast (rtnladdr, bcaddr);
 	}
 
 	/* Peer/point-to-point address */
 	if (peer_addr) {
-		auto_nl_addr struct nl_addr *nlpeer = _nm_nl_addr_build (family, peer_addr, addrlen);
+		auto_nl_addr struct nl_addr *nlpeer = _nl_addr_build (family, peer_addr, addrlen);
 
 		nle = rtnl_addr_set_peer (rtnladdr, nlpeer);
 		if (nle && nle != -NLE_AF_NOSUPPORT) {
@@ -3593,19 +4031,15 @@ build_rtnl_addr (int family,
 		}
 	}
 
-	rtnl_addr_set_prefixlen (rtnladdr, plen);
-	if (lifetime) {
-		/* note that here we set the relative timestamps (ticking from *now*).
-		 * Contrary to the rtnl_addr objects from our cache, which have absolute
-		 * timestamps (see _rtnl_addr_hack_lifetimes_rel_to_abs()).
-		 *
-		 * This is correct, because we only use build_rtnl_addr() for
-		 * add_object(), delete_object() and cache search (ip_address_exists). */
+	_nl_rtnl_addr_set_prefixlen (rtnladdr, plen);
+	if (   (lifetime  != 0 && lifetime  != NM_PLATFORM_LIFETIME_PERMANENT)
+	    || (preferred != 0 && preferred != NM_PLATFORM_LIFETIME_PERMANENT)) {
+		/* note that here we set the relative timestamps (ticking from *now*). */
 		rtnl_addr_set_valid_lifetime (rtnladdr, lifetime);
 		rtnl_addr_set_preferred_lifetime (rtnladdr, preferred);
 	}
 	if (flags) {
-		if ((flags & ~0xFF) && !check_support_kernel_extended_ifa_flags (nm_platform_get ())) {
+		if ((flags & ~0xFF) && !_support_kernel_extended_ifa_flags_get ()) {
 			/* Older kernels don't accept unknown netlink attributes.
 			 *
 			 * With commit libnl commit 5206c050504f8676a24854519b9c351470fb7cc6, libnl will only set
@@ -3625,6 +4059,48 @@ build_rtnl_addr (int family,
 	return (struct nl_object *) rtnladdr_copy;
 }
 
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+	const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj;
+	guint32 lifetime, preferred;
+
+	nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred,
+	                        0, 0, &lifetime, &preferred);
+
+	return build_rtnl_addr (platform,
+	                        AF_INET,
+	                        obj->ifindex,
+	                        &obj->address,
+	                        obj->peer_address ? &obj->peer_address : NULL,
+	                        obj->plen,
+	                        lifetime,
+	                        preferred,
+	                        0,
+	                        obj->label[0] ? obj->label : NULL);
+}
+
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+	const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj;
+	guint32 lifetime, preferred;
+
+	nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred,
+	                        0, 0, &lifetime, &preferred);
+
+	return build_rtnl_addr (platform,
+	                        AF_INET6,
+	                        obj->ifindex,
+	                        &obj->address,
+	                        !IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL,
+	                        obj->plen,
+	                        lifetime,
+	                        preferred,
+	                        0,
+	                        NULL);
+}
+
 static gboolean
 ip4_address_add (NMPlatform *platform,
                  int ifindex,
@@ -3635,10 +4111,16 @@ ip4_address_add (NMPlatform *platform,
                  guint32 preferred,
                  const char *label)
 {
-	return add_object (platform, build_rtnl_addr (AF_INET, ifindex, &addr,
-	                                              peer_addr ? &peer_addr : NULL,
-	                                              plen, lifetime, preferred, 0,
-	                                              label));
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = NULL;
+
+	nlo = build_rtnl_addr (platform, AF_INET, ifindex, &addr,
+	                       peer_addr ? &peer_addr : NULL,
+	                       plen, lifetime, preferred, 0,
+	                       label);
+	return do_add_addrroute (platform,
+	                         nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen),
+	                         nlo);
 }
 
 static gboolean
@@ -3651,169 +4133,114 @@ ip6_address_add (NMPlatform *platform,
                  guint32 preferred,
                  guint flags)
 {
-	return add_object (platform, build_rtnl_addr (AF_INET6, ifindex, &addr,
-	                                              IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
-	                                              plen, lifetime, preferred, flags,
-	                                              NULL));
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = NULL;
+
+	nlo = build_rtnl_addr (platform, AF_INET6, ifindex, &addr,
+	                       IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
+	                       plen, lifetime, preferred, flags,
+	                       NULL);
+	return do_add_addrroute (platform,
+	                         nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen),
+	                         nlo);
 }
 
 static gboolean
 ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
-	return delete_object (platform, build_rtnl_addr (AF_INET, ifindex, &addr, peer_address ? &peer_address : NULL, plen, 0, 0, 0, NULL), TRUE);
+	NMPObject obj_needle;
+
+	nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen);
+	obj_needle.ip4_address.peer_address = peer_address;
+	return do_delete_object (platform, &obj_needle, NULL);
 }
 
 static gboolean
 ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	return delete_object (platform, build_rtnl_addr (AF_INET6, ifindex, &addr, NULL, plen, 0, 0, 0, NULL), TRUE);
+	NMPObject obj_needle;
+
+	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
+	return do_delete_object (platform, &obj_needle, NULL);
 }
 
-static gboolean
-ip_address_exists (NMPlatform *platform, int family, int ifindex, gconstpointer addr, int plen)
+static const NMPlatformIP4Address *
+ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 {
-	auto_nl_object struct nl_object *object = build_rtnl_addr (family, ifindex, addr, NULL, plen, 0, 0, 0, NULL);
-	auto_nl_object struct nl_object *cached_object = nl_cache_search (choose_cache (platform, object), object);
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-	return !!cached_object;
+	nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip4_address;
+	return NULL;
 }
 
-static gboolean
-ip4_address_exists (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+static const NMPlatformIP6Address *
+ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	return ip_address_exists (platform, AF_INET, ifindex, &addr, plen);
-}
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-static gboolean
-ip6_address_exists (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
-{
-	return ip_address_exists (platform, AF_INET6, ifindex, &addr, plen);
+	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip6_address;
+	return NULL;
 }
 
-static gboolean
-ip4_check_reinstall_device_route (NMPlatform *platform, int ifindex, const NMPlatformIP4Address *address, guint32 device_route_metric)
+/******************************************************************/
+
+static GArray *
+ipx_route_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type, NMPlatformGetRouteFlags flags)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPlatformIP4Address addr_candidate;
-	NMPlatformIP4Route route_candidate;
-	struct nl_object *object;
-	guint32 device_network;
-
-	for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) {
-		if (_address_match ((struct rtnl_addr *) object, AF_INET, 0)) {
-			if (init_ip4_address (&addr_candidate, (struct rtnl_addr *) object))
-				if (   addr_candidate.plen == address->plen
-				    && addr_candidate.address == address->address) {
-					/* If we already have the same address installed on any interface,
-					 * we back off.
-					 * Perform this check first, as we expect to have significantly less
-					 * addresses to search. */
-					return FALSE;
-				}
-		}
-	}
+	NMPCacheId cache_id;
+	const NMPlatformIPRoute *const* routes;
+	GArray *array;
+	const NMPClass *klass;
+	gboolean with_rtprot_kernel;
+	guint i, len;
 
-	device_network = nm_utils_ip4_address_clear_host_address (address->address, address->plen);
-
-	for (object = nl_cache_get_first (priv->route_cache); object; object = nl_cache_get_next (object)) {
-		if (_route_match ((struct rtnl_route *) object, AF_INET, 0, TRUE)) {
-			if (init_ip4_route (&route_candidate, (struct rtnl_route *) object)) {
-				if (   route_candidate.network == device_network
-				    && route_candidate.plen == address->plen
-				    && (   route_candidate.metric == 0
-				        || route_candidate.metric == device_route_metric)) {
-					/* There is already any route with metric 0 or the metric we want to install
-					 * for the same subnet. */
-					return FALSE;
-				}
-			}
-		}
-	}
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	return TRUE;
-}
+	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
+		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
 
-/******************************************************************/
+	klass = nmp_class_from_type (obj_type);
 
-static gboolean
-_route_match (struct rtnl_route *rtnlroute, int family, int ifindex, gboolean include_proto_kernel)
-{
-	struct rtnl_nexthop *nexthop;
+	nmp_cache_id_init_routes_visible (&cache_id,
+	                                  obj_type,
+	                                  NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT),
+	                                  NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT),
+	                                  ifindex);
 
-	g_return_val_if_fail (rtnlroute, FALSE);
+	routes = (const NMPlatformIPRoute *const*) nmp_cache_lookup_multi (priv->cache, &cache_id, &len);
 
-	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 ||
-	    rtnl_route_get_flags (rtnlroute) & RTM_F_CLONED)
-		return FALSE;
+	array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
 
-	if (ifindex == 0)
-		return TRUE;
+	with_rtprot_kernel = NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL);
+	for (i = 0; i < len; i++) {
+		nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (routes[i])) == klass);
 
-	nexthop = rtnl_route_nexthop_n (rtnlroute, 0);
-	return rtnl_route_nh_get_ifindex (nexthop) == ifindex;
+		if (   with_rtprot_kernel
+		    || routes[i]->source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
+			g_array_append_vals (array, routes[i], 1);
+	}
+	return array;
 }
 
 static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mode)
+ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *routes;
-	NMPlatformIP4Route route;
-	struct nl_object *object;
-
-	g_return_val_if_fail (NM_IN_SET (mode, NM_PLATFORM_GET_ROUTE_MODE_ALL, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT), NULL);
-
-	routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-
-	for (object = nl_cache_get_first (priv->route_cache); object; object = nl_cache_get_next (object)) {
-		if (_route_match ((struct rtnl_route *) object, AF_INET, ifindex, FALSE)) {
-			if (_rtnl_route_is_default ((struct rtnl_route *) object)) {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT)
-					continue;
-			} else {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT)
-					continue;
-			}
-			if (init_ip4_route (&route, (struct rtnl_route *) object))
-				g_array_append_val (routes, route);
-		}
-	}
-
-	return routes;
+	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ROUTE, flags);
 }
 
 static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteMode mode)
+ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GArray *routes;
-	NMPlatformIP6Route route;
-	struct nl_object *object;
-
-	g_return_val_if_fail (NM_IN_SET (mode, NM_PLATFORM_GET_ROUTE_MODE_ALL, NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT), NULL);
-
-	routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
-
-	for (object = nl_cache_get_first (priv->route_cache); object; object = nl_cache_get_next (object)) {
-		if (_route_match ((struct rtnl_route *) object, AF_INET6, ifindex, FALSE)) {
-			if (_rtnl_route_is_default ((struct rtnl_route *) object)) {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_NO_DEFAULT)
-					continue;
-			} else {
-				if (mode == NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT)
-					continue;
-			}
-			if (init_ip6_route (&route, (struct rtnl_route *) object))
-				g_array_append_val (routes, route);
-		}
-	}
-
-	return routes;
+	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags);
 }
 
 static void
@@ -3846,26 +4273,26 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
 	int addrlen = (family == AF_INET) ? sizeof (in_addr_t) : sizeof (struct in6_addr);
 	/* Workaround a libnl bug by using zero destination address length for default routes */
 	auto_nl_addr struct nl_addr *dst = NULL;
-	auto_nl_addr struct nl_addr *gw = gateway ? _nm_nl_addr_build (family, gateway, addrlen) : NULL;
-	auto_nl_addr struct nl_addr *pref_src_nl = pref_src ? _nm_nl_addr_build (family, pref_src, addrlen) : NULL;
+	auto_nl_addr struct nl_addr *gw = gateway ? _nl_addr_build (family, gateway, addrlen) : NULL;
+	auto_nl_addr struct nl_addr *pref_src_nl = pref_src ? _nl_addr_build (family, pref_src, addrlen) : NULL;
 
 	/* There seem to be problems adding a route with non-zero host identifier.
 	 * Adding IPv6 routes is simply ignored, without error message.
 	 * In the IPv4 case, we got an error. Thus, we have to make sure, that
 	 * the address is sane. */
 	clear_host_address (family, network, plen, network_clean);
-	dst = _nm_nl_addr_build (family, network_clean, plen ? addrlen : 0);
+	dst = _nl_addr_build (family, network_clean, plen ? addrlen : 0);
 	nl_addr_set_prefixlen (dst, plen);
 
-	rtnlroute = _nm_rtnl_route_alloc ();
+	rtnlroute = _nl_rtnl_route_alloc ();
 	rtnl_route_set_table (rtnlroute, RT_TABLE_MAIN);
 	rtnl_route_set_tos (rtnlroute, 0);
 	rtnl_route_set_dst (rtnlroute, dst);
 	rtnl_route_set_priority (rtnlroute, metric);
 	rtnl_route_set_family (rtnlroute, family);
-	rtnl_route_set_protocol (rtnlroute, source_to_rtprot (source));
+	rtnl_route_set_protocol (rtnlroute, _nm_ip_config_source_to_rtprot (source));
 
-	nexthop = _nm_rtnl_route_nh_alloc ();
+	nexthop = _nl_rtnl_route_nh_alloc ();
 	rtnl_route_nh_set_ifindex (nexthop, ifindex);
 	if (gw && !nl_addr_iszero (gw))
 		rtnl_route_nh_set_gateway (nexthop, gw);
@@ -3879,111 +4306,113 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
 	return (struct nl_object *) rtnlroute;
 }
 
-static gboolean
-ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
-               in_addr_t network, int plen, in_addr_t gateway,
-               guint32 pref_src, guint32 metric, guint32 mss)
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
 {
-	return add_object (platform, build_rtnl_route (AF_INET, ifindex, source, &network, plen, &gateway, pref_src ? &pref_src : NULL, metric, mss));
+	const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj;
+
+	return build_rtnl_route (AF_INET,
+	                         obj->ifindex,
+	                         obj->source,
+	                         &obj->network,
+	                         obj->plen,
+	                         &obj->gateway,
+	                         obj->pref_src ? &obj->pref_src : NULL,
+	                         obj->metric,
+	                         obj->mss);
 }
 
-static gboolean
-ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
-               struct in6_addr network, int plen, struct in6_addr gateway,
-               guint32 metric, guint32 mss)
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
 {
-	metric = nm_utils_ip6_route_metric_normalize (metric);
+	const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj;
 
-	return add_object (platform, build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss));
+	return build_rtnl_route (AF_INET6,
+	                         obj->ifindex,
+	                         obj->source,
+	                         &obj->network,
+	                         obj->plen,
+	                         &obj->gateway,
+	                         NULL,
+	                         obj->metric,
+	                         obj->mss);
 }
 
-static struct rtnl_route *
-route_search_cache (struct nl_cache *cache, int family, int ifindex, const void *network, int plen, guint32 metric)
+static gboolean
+ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
+               in_addr_t network, int plen, in_addr_t gateway,
+               guint32 pref_src, guint32 metric, guint32 mss)
 {
-	guint32 network_clean[4], dst_clean[4];
-	struct nl_object *object;
-
-	clear_host_address (family, network, plen, network_clean);
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = NULL;
 
-	for (object = nl_cache_get_first (cache); object; object = nl_cache_get_next (object)) {
-		struct nl_addr *dst;
-		struct rtnl_route *rtnlroute = (struct rtnl_route *) object;
-
-		if (!_route_match (rtnlroute, family, ifindex, FALSE))
-			continue;
-
-		if (metric != rtnl_route_get_priority (rtnlroute))
-			continue;
-
-		dst = rtnl_route_get_dst (rtnlroute);
-		if (   !dst
-		    || nl_addr_get_family (dst) != family
-		    || nl_addr_get_prefixlen (dst) != plen)
-			continue;
-
-		/* 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;
-
-		rtnl_route_get (rtnlroute);
-		return rtnlroute;
-	}
-	return NULL;
+	nlo = build_rtnl_route (AF_INET, ifindex, source, &network, plen, &gateway, pref_src ? &pref_src : NULL, metric, mss);
+	return do_add_addrroute (platform,
+	                         nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric),
+	                         nlo);
 }
 
 static gboolean
-refresh_route (NMPlatform *platform, int family, int ifindex, const void *network, int plen, guint32 metric)
+ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
+               struct in6_addr network, int plen, struct in6_addr gateway,
+               guint32 metric, guint32 mss)
 {
-	struct nl_cache *cache;
-	auto_nl_object struct rtnl_route *cached_object = NULL;
+	NMPObject obj_needle;
+	auto_nl_object struct nl_object *nlo = NULL;
 
-	cache = choose_cache_by_type (platform, family == AF_INET ? OBJECT_TYPE_IP4_ROUTE : OBJECT_TYPE_IP6_ROUTE);
-	cached_object = route_search_cache (cache, family, ifindex, network, plen, metric);
+	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	if (cached_object)
-		return refresh_object (platform, (struct nl_object *) cached_object, TRUE, NM_PLATFORM_REASON_INTERNAL);
-	return TRUE;
+	nlo = build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss);
+	return do_add_addrroute (platform,
+	                         nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric),
+	                         nlo);
 }
 
 static gboolean
 ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	in_addr_t gateway = 0;
-	struct rtnl_route *cached_object;
-	struct nl_object *route = build_rtnl_route (AF_INET, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN, &network, plen, &gateway, NULL, metric, 0);
+	auto_nl_object struct nl_object *nlo = build_rtnl_route (AF_INET, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN, &network, plen, &gateway, NULL, metric, 0);
 	uint8_t scope = RT_SCOPE_NOWHERE;
-	struct nl_cache *cache;
+	const NMPObject *obj;
+	NMPObject obj_needle;
 
-	g_return_val_if_fail (route, FALSE);
+	g_return_val_if_fail (nlo, FALSE);
 
-	cache = choose_cache_by_type (platform, OBJECT_TYPE_IP4_ROUTE);
+	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
 
 	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;
-		}
+		 * Instead, make sure that we have the most recent state and process all
+		 * delayed actions (including re-reading data from netlink). */
+		delayed_action_handle_all (platform, 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);
-	if (!cached_object)
-		cached_object = route_search_cache (cache, AF_INET, ifindex, &network, plen, metric);
+	obj = nmp_cache_lookup_obj (priv->cache, &obj_needle);
+
+	if (metric == 0 && !obj) {
+		/* hmm... we are about to delete an IP4 route with metric 0. We must only
+		 * send the delete request if such a route really exists. Above we refreshed
+		 * the platform cache, still no such route exists.
+		 *
+		 * Be extra careful and reload the routes. We must be sure that such a
+		 * route doesn't exists, because when we add an IPv4 address, we immediately
+		 * afterwards try to delete the kernel-added device route with metric 0.
+		 * It might be, that we didn't yet get the notification about that route.
+		 *
+		 * FIXME: once our ip4_address_add() is sure that upon return we have
+		 * the latest state from in the platform cache, we might save this
+		 * additional expensive cache-resync. */
+		do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE, TRUE);
+
+		obj = nmp_cache_lookup_obj (priv->cache, &obj_needle);
+		if (!obj)
+			return TRUE;
+	}
 
 	if (!_nl_has_capability (1 /* NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE */)) {
 		/* When searching for a matching IPv4 route to delete, the kernel
@@ -4000,8 +4429,8 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 		 * So, this workaround is only needed unless we have NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE.
 		 **/
 
-		if (cached_object)
-			scope = rtnl_route_get_scope (cached_object);
+		if (obj)
+			scope = nm_platform_route_scope_inv (obj->ip4_route.scope_inv);
 
 		if (scope == RT_SCOPE_NOWHERE) {
 			/* If we would set the scope to RT_SCOPE_NOWHERE, libnl would guess the scope.
@@ -4010,7 +4439,7 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 			scope = RT_SCOPE_UNIVERSE;
 		}
 	}
-	rtnl_route_set_scope ((struct rtnl_route *) route, scope);
+	rtnl_route_set_scope ((struct rtnl_route *) nlo, scope);
 
 	/* 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. */
@@ -4022,179 +4451,51 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 	 * pref_src: NULL
 	 */
 
-	rtnl_route_put (cached_object);
-	return delete_object (platform, route, FALSE) && refresh_route (platform, AF_INET, ifindex, &network, plen, metric);
+	return do_delete_object (platform, &obj_needle, nlo);
 }
 
 static gboolean
 ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
 	struct in6_addr gateway = IN6ADDR_ANY_INIT;
+	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_needle;
 
 	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);
-}
-
-static gboolean
-ip_route_exists (NMPlatform *platform, int family, int ifindex, gpointer network, int plen, guint32 metric)
-{
-	auto_nl_object struct nl_object *object = build_rtnl_route (family, ifindex,
-	                                                            NM_IP_CONFIG_SOURCE_UNKNOWN,
-	                                                            network, plen, NULL, NULL, metric, 0);
-	struct nl_cache *cache = choose_cache (platform, object);
-	auto_nl_object struct nl_object *cached_object = nl_cache_search (cache, object);
-
-	if (!cached_object)
-		cached_object = (struct nl_object *) route_search_cache (cache, family, ifindex, network, plen, metric);
-	return !!cached_object;
-}
-
-static gboolean
-ip4_route_exists (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
-{
-	return ip_route_exists (platform, AF_INET, ifindex, &network, plen, metric);
-}
-
-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);
-}
-
-/******************************************************************/
+	nlo = build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0);
 
-/* Initialize the link cache while ensuring all links are of AF_UNSPEC,
- * family (even though the kernel might set AF_BRIDGE for bridges).
- * See also: _nl_link_family_unset() */
-static void
-init_link_cache (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_object *object = NULL;
+	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
 
-	rtnl_link_alloc_cache (priv->nlh, AF_UNSPEC, &priv->link_cache);
-
-	do {
-		for (object = nl_cache_get_first (priv->link_cache); object; object = nl_cache_get_next (object)) {
-			if (rtnl_link_get_family ((struct rtnl_link *)object) != AF_UNSPEC)
-				break;
-		}
-
-		if (object) {
-			/* A non-AF_UNSPEC object encoutnered */
-			struct nl_object *existing;
-
-			nl_object_get (object);
-			nl_cache_remove (object);
-			rtnl_link_set_family ((struct rtnl_link *)object, AF_UNSPEC);
-			existing = nl_cache_search (priv->link_cache, object);
-			if (existing)
-				nl_object_put (existing);
-			else
-				nl_cache_add (priv->link_cache, object);
-			nl_object_put (object);
-		}
-	} while (object);
+	return do_delete_object (platform, &obj_needle, nlo);
 }
 
-/* Calls announce_object with appropriate arguments for all objects
- * which are not coherent between old and new caches and deallocates
- * the old cache. */
-static void
-cache_announce_changes (NMPlatform *platform, struct nl_cache *new, struct nl_cache *old)
+static const NMPlatformIP4Route *
+ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
-	struct nl_object *object;
-
-	if (!old)
-		return;
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-	for (object = nl_cache_get_first (new); object; object = nl_cache_get_next (object)) {
-		struct nl_object *cached_object = nm_nl_cache_search (old, object);
-
-		if (cached_object) {
-			ObjectType type = object_type_from_nl_object (object);
-			if (nm_nl_object_diff (type, object, cached_object))
-				announce_object (platform, object, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_EXTERNAL);
-			nl_object_put (cached_object);
-		} else
-			announce_object (platform, object, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL);
-	}
-	for (object = nl_cache_get_first (old); object; object = nl_cache_get_next (object)) {
-		struct nl_object *cached_object = nm_nl_cache_search (new, object);
-		if (cached_object)
-			nl_object_put (cached_object);
-		else
-			announce_object (platform, object, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL);
-	}
-
-	nl_cache_free (old);
+	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip4_route;
+	return NULL;
 }
 
-/* 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)
+static const NMPlatformIP6Route *
+ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	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));
+	NMPObject obj_needle;
+	const NMPObject *obj;
 
-		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
- * them and destroys the old caches. */
-static void
-cache_repopulate_all (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_cache *old_link_cache = priv->link_cache;
-	struct nl_cache *old_address_cache = priv->address_cache;
-	struct nl_cache *old_route_cache = priv->route_cache;
-	struct nl_object *object;
-
-	debug ("platform: %spopulate platform cache", old_link_cache ? "re" : "");
-
-	/* Allocate new netlink caches */
-	init_link_cache (platform);
-	rtnl_addr_alloc_cache (priv->nlh, &priv->address_cache);
-	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);
-	}
+	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	/* Make sure all changes we've missed are announced. */
-	cache_announce_changes (platform, priv->link_cache, old_link_cache);
-	cache_announce_changes (platform, priv->address_cache, old_address_cache);
-	cache_announce_changes (platform, priv->route_cache, old_route_cache);
+	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
+	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	if (nmp_object_is_visible (obj))
+		return &obj->ip6_route;
+	return NULL;
 }
 
 /******************************************************************/
@@ -4208,10 +4509,9 @@ verify_source (struct nl_msg *msg, gpointer user_data)
 {
 	struct ucred *creds = nlmsg_get_creds (msg);
 
-	if (!creds || creds->pid || creds->uid || creds->gid) {
+	if (!creds || creds->pid) {
 		if (creds)
-			warning ("netlink: received non-kernel message (pid %d uid %d gid %d)",
-					creds->pid, creds->uid, creds->gid);
+			warning ("netlink: received non-kernel message (pid %d)", creds->pid);
 		else
 			warning ("netlink: received message without credentials");
 		return NL_STOP;
@@ -4225,35 +4525,42 @@ event_handler (GIOChannel *channel,
                GIOCondition io_condition,
                gpointer user_data)
 {
-	NMPlatform *platform = NM_PLATFORM (user_data);
+	delayed_action_handle_all (NM_PLATFORM (user_data), TRUE);
+	return TRUE;
+}
+
+static gboolean
+event_handler_read_netlink_one (NMPlatform *platform)
+{
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int nle;
 
 	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;
+
 	if (nle < 0)
 		switch (nle) {
+		case -NLE_AGAIN:
+			return FALSE;
 		case -NLE_DUMP_INTR:
-			/* this most likely happens due to our request (RTM_GETADDR, AF_INET6, NLM_F_DUMP)
-			 * to detect support for support_kernel_extended_ifa_flags. This is not critical
-			 * and can happen easily. */
 			debug ("Uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 			break;
 		case -NLE_NOMEM:
 			warning ("Too many netlink events. Need to resynchronize platform cache");
 			/* Drain the event queue, we've lost events and are out of sync anyway and we'd
 			 * 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);
+			_nl_sock_flush_data (priv->nlh_event);
+			priv->nlh_seq_expect = 0;
+			delayed_action_schedule (platform,
+			                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
+			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+			                         NULL);
 			break;
 		default:
 			error ("Failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
@@ -4262,6 +4569,71 @@ event_handler (GIOChannel *channel,
 	return TRUE;
 }
 
+static gboolean
+event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	int r;
+	struct pollfd pfd;
+	gboolean any = FALSE;
+	gint64 timestamp = 0, now;
+	const int TIMEOUT = 250;
+	int timeout = 0;
+	guint32 wait_for_seq = 0;
+
+	while (TRUE) {
+		while (event_handler_read_netlink_one (platform))
+			any = TRUE;
+
+		if (!wait_for_acks || priv->nlh_seq_expect == 0) {
+			if (wait_for_seq)
+				_LOGT ("read-netlink-all: ACK for sequence number %u received", priv->nlh_seq_expect);
+			return any;
+		}
+
+		now = nm_utils_get_monotonic_timestamp_ms ();
+		if (wait_for_seq != priv->nlh_seq_expect) {
+			/* We are waiting for a new sequence number (or we will wait for the first time).
+			 * Reset/start counting the overall wait time. */
+			_LOGT ("read-netlink-all: wait for ACK for sequence number %u...", priv->nlh_seq_expect);
+			wait_for_seq = priv->nlh_seq_expect;
+			timestamp = now;
+			timeout = TIMEOUT;
+		} else {
+			if ((now - timestamp) >= TIMEOUT) {
+				/* timeout. Don't wait for this sequence number anymore. */
+				break;
+			}
+
+			/* readjust the wait-time. */
+			timeout = TIMEOUT - (now - timestamp);
+		}
+
+		memset (&pfd, 0, sizeof (pfd));
+		pfd.fd = nl_socket_get_fd (priv->nlh_event);
+		pfd.events = POLLIN;
+		r = poll (&pfd, 1, timeout);
+
+		if (r == 0) {
+			/* timeout. */
+			break;
+		}
+		if (r < 0) {
+			int errsv = errno;
+
+			if (errsv != EINTR) {
+				_LOGE ("read-netlink-all: poll failed with %s", strerror (errsv));
+				return any;
+			}
+			/* Continue to read again, even if there might be nothing to read after EINTR. */
+		}
+	}
+
+	_LOGW ("read-netlink-all: timeout waiting for ACK to sequence number %u...", wait_for_seq);
+	priv->nlh_seq_expect = 0;
+	return any;
+}
+
 static struct nl_sock *
 setup_socket (gboolean event, gpointer user_data)
 {
@@ -4278,7 +4650,8 @@ setup_socket (gboolean event, gpointer user_data)
 	/* Dispatch event messages (event socket only) */
 	if (event) {
 		nl_socket_modify_cb (sock, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data);
-		nl_socket_disable_seq_check (sock);
+		nl_socket_modify_cb (sock, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, event_seq_check, user_data);
+		nl_socket_modify_err_cb (sock, NL_CB_CUSTOM, event_err, user_data);
 	}
 
 	nle = nl_connect (sock, NETLINK_ROUTE);
@@ -4298,14 +4671,23 @@ setup_socket (gboolean event, gpointer user_data)
 /******************************************************************/
 
 static void
+cache_update_link_udev (NMPlatform *platform, int ifindex, GUdevDevice *udev_device)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	auto_nmp_obj NMPObject *obj_cache = NULL;
+	gboolean was_visible;
+	NMPCacheOpsType cache_op;
+
+	cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udev_device, &obj_cache, &was_visible, cache_pre_hook, platform);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+}
+
+static void
 udev_device_added (NMPlatform *platform,
                    GUdevDevice *udev_device)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
 	const char *ifname;
 	int ifindex;
-	gboolean was_announceable = FALSE;
 
 	ifname = g_udev_device_get_name (udev_device);
 	if (!ifname) {
@@ -4329,59 +4711,37 @@ udev_device_added (NMPlatform *platform,
 		return;
 	}
 
-	rtnllink = rtnl_link_get (priv->link_cache, ifindex);
-	if (rtnllink)
-		was_announceable = link_is_announceable (platform, rtnllink);
-
-	g_hash_table_insert (priv->udev_devices, GINT_TO_POINTER (ifindex),
-	                     g_object_ref (udev_device));
+	cache_update_link_udev (platform, ifindex, udev_device);
+}
 
-	/* Announce devices only if they also have been discovered via Netlink. */
-	if (rtnllink && link_is_announceable (platform, rtnllink))
-		announce_object (platform, (struct nl_object *) rtnllink, was_announceable ? NM_PLATFORM_SIGNAL_CHANGED : NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL);
+static gboolean
+_udev_device_removed_match_link (const NMPObject *obj, gpointer udev_device)
+{
+	return obj->_link.udev.device == udev_device;
 }
 
 static void
 udev_device_removed (NMPlatform *platform,
                      GUdevDevice *udev_device)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct rtnl_link *rtnllink = NULL;
 	int ifindex = 0;
-	gboolean was_announceable = FALSE;
 
 	if (g_udev_device_get_property (udev_device, "IFINDEX"))
 		ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
 	else {
-		GHashTableIter iter;
-		gpointer key, value;
+		const NMPObject *obj;
 
-		/* This should not happen, but just to be sure.
-		 * If we can't get IFINDEX, go through the devices and
-		 * compare the pointers.
-		 */
-		g_hash_table_iter_init (&iter, priv->udev_devices);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			if ((GUdevDevice *)value == udev_device) {
-				ifindex = GPOINTER_TO_INT (key);
-				break;
-			}
-		}
+		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+		                                  0, NULL, FALSE, NM_LINK_TYPE_NONE, _udev_device_removed_match_link, udev_device);
+		if (obj)
+			ifindex = obj->link.ifindex;
 	}
 
 	debug ("udev-remove: IFINDEX=%d", ifindex);
 	if (ifindex <= 0)
 		return;
 
-	rtnllink = rtnl_link_get (priv->link_cache, ifindex);
-	if (rtnllink)
-		was_announceable = link_is_announceable (platform, rtnllink);
-
-	g_hash_table_remove (priv->udev_devices, GINT_TO_POINTER (ifindex));
-
-	/* Announce device removal if it is no longer announceable. */
-	if (was_announceable && !link_is_announceable (platform, rtnllink))
-		announce_object (platform, (struct nl_object *) rtnllink, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL);
+	cache_update_link_udev (platform, ifindex, NULL);
 }
 
 static void
@@ -4416,28 +4776,40 @@ handle_udev_event (GUdevClient *client,
 /******************************************************************/
 
 static void
-nm_linux_platform_init (NMLinuxPlatform *platform)
+nm_linux_platform_init (NMLinuxPlatform *self)
 {
+	NMLinuxPlatformPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate);
+
+	self->priv = priv;
+
+	priv->delayed_deletion = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+	                                                (GEqualFunc) nmp_object_id_equal,
+	                                                (GDestroyNotify) nmp_object_unref,
+	                                                (GDestroyNotify) nmp_object_unref);
+	priv->cache = nmp_cache_new ();
+	priv->delayed_action.list_master_connected = g_ptr_array_new ();
+	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
+	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
 }
 
-static gboolean
-setup (NMPlatform *platform)
+static void
+constructed (GObject *_object)
 {
+	NMPlatform *platform = NM_PLATFORM (_object);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const char *udev_subsys[] = { "net", NULL };
-	GUdevEnumerator *enumerator;
-	GList *devices, *iter;
 	int channel_flags;
 	gboolean status;
 	int nle;
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	struct nl_object *object;
-#endif
+	GUdevEnumerator *enumerator;
+	GList *devices, *iter;
+
+	_LOGD ("create");
 
 	/* Initialize netlink socket for requests */
 	priv->nlh = setup_socket (FALSE, platform);
 	g_assert (priv->nlh);
-	debug ("Netlink socket for requests established: %d", nl_socket_get_local_port (priv->nlh));
+	debug ("Netlink socket for requests established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
 	/* Initialize netlink socket for events */
 	priv->nlh_event = setup_socket (TRUE, platform);
@@ -4454,7 +4826,7 @@ setup (NMPlatform *platform)
 	                                 RTNLGRP_IPV4_ROUTE,  RTNLGRP_IPV6_ROUTE,
 	                                 0);
 	g_assert (!nle);
-	debug ("Netlink socket for events established: %d", nl_socket_get_local_port (priv->nlh_event));
+	debug ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh_event), nl_socket_get_fd (priv->nlh_event));
 
 	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh_event));
 	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
@@ -4465,37 +4837,31 @@ setup (NMPlatform *platform)
 		channel_flags | G_IO_FLAG_NONBLOCK, NULL);
 	g_assert (status);
 	priv->event_id = g_io_add_watch (priv->event_channel,
-		(EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
-		event_handler, platform);
-
-	cache_repopulate_all (platform);
-
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	/* Initial check for user IPv6LL support once the link cache is allocated
-	 * and filled.  If there are no links in the cache yet then we'll check
-	 * when a new link shows up in announce_object().
-	 */
-	object = nl_cache_get_first (priv->link_cache);
-	if (object) {
-		uint8_t mode;
-
-		if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) object, &mode) == 0)
-			priv->support_user_ipv6ll = 1;
-		else
-			priv->support_user_ipv6ll = -1;
-	}
-#else
-	priv->support_user_ipv6ll = -1;
-#endif
+	                                (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
+	                                 event_handler, platform);
 
 	/* Set up udev monitoring */
 	priv->udev_client = g_udev_client_new (udev_subsys);
 	g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (handle_udev_event), platform);
-	priv->udev_devices = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+
+	/* complete construction of the GObject instance before populating the cache. */
+	G_OBJECT_CLASS (nm_linux_platform_parent_class)->constructed (_object);
+
+	_LOGD ("populate platform cache");
+	delayed_action_schedule (platform,
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+	                         NULL);
+
+	delayed_action_handle_all (platform, FALSE);
 
 	/* And read initial device list */
 	enumerator = g_udev_enumerator_new (priv->udev_client);
 	g_udev_enumerator_add_match_subsystem (enumerator, "net");
+
 	g_udev_enumerator_add_match_is_initialized (enumerator);
 
 	devices = g_udev_enumerator_execute (enumerator);
@@ -4505,16 +4871,26 @@ setup (NMPlatform *platform)
 	}
 	g_list_free (devices);
 	g_object_unref (enumerator);
+}
 
-	/* request all IPv6 addresses (hopeing that there is at least one), to check for
-	 * the IFA_FLAGS attribute. */
-	nle = nl_rtgen_request (priv->nlh_event, RTM_GETADDR, AF_INET6, NLM_F_DUMP);
-	if (nle < 0)
-		nm_log_warn (LOGD_PLATFORM, "Netlink error: requesting RTM_GETADDR failed with %s", nl_geterror (nle));
+static void
+dispose (GObject *object)
+{
+	NMPlatform *platform = NM_PLATFORM (object);
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
+	_LOGD ("dispose");
 
-	return TRUE;
+	priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
+	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
+	g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
+
+	nm_clear_g_source (&priv->delayed_action.idle_id);
+
+	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
+	g_clear_pointer (&priv->delayed_deletion, g_hash_table_unref);
+
+	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
 }
 
 static void
@@ -4522,17 +4898,18 @@ nm_linux_platform_finalize (GObject *object)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (object);
 
+	nmp_cache_free (priv->cache);
+
+	g_ptr_array_unref (priv->delayed_action.list_master_connected);
+	g_ptr_array_unref (priv->delayed_action.list_refresh_link);
+
 	/* Free netlink resources */
 	g_source_remove (priv->event_id);
 	g_io_channel_unref (priv->event_channel);
 	nl_socket_free (priv->nlh);
 	nl_socket_free (priv->nlh_event);
-	nl_cache_free (priv->link_cache);
-	nl_cache_free (priv->address_cache);
-	nl_cache_free (priv->route_cache);
 
 	g_object_unref (priv->udev_client);
-	g_hash_table_unref (priv->udev_devices);
 	g_hash_table_unref (priv->wifi_data);
 
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->finalize (object);
@@ -4549,21 +4926,21 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	g_type_class_add_private (klass, sizeof (NMLinuxPlatformPrivate));
 
 	/* virtual methods */
+	object_class->constructed = constructed;
+	object_class->dispose = dispose;
 	object_class->finalize = nm_linux_platform_finalize;
 
-	platform_class->setup = setup;
-
 	platform_class->sysctl_set = sysctl_set;
 	platform_class->sysctl_get = sysctl_get;
 
 	platform_class->link_get = _nm_platform_link_get;
+	platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname;
+	platform_class->link_get_by_address = _nm_platform_link_get_by_address;
 	platform_class->link_get_all = link_get_all;
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
-	platform_class->link_get_ifindex = link_get_ifindex;
-	platform_class->link_get_name = link_get_name;
-	platform_class->link_get_type = link_get_type;
 	platform_class->link_get_type_name = link_get_type_name;
+	platform_class->link_get_unmanaged = link_get_unmanaged;
 
 	platform_class->link_refresh = link_refresh;
 
@@ -4571,28 +4948,26 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_down = link_set_down;
 	platform_class->link_set_arp = link_set_arp;
 	platform_class->link_set_noarp = link_set_noarp;
-	platform_class->link_is_up = link_is_up;
-	platform_class->link_is_connected = link_is_connected;
-	platform_class->link_uses_arp = link_uses_arp;
 
-	platform_class->link_get_user_ipv6ll_enabled = link_get_user_ipv6ll_enabled;
+	platform_class->link_get_udi = link_get_udi;
+	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_get_address = link_get_address;
 	platform_class->link_set_address = link_set_address;
-	platform_class->link_get_mtu = link_get_mtu;
+	platform_class->link_get_permanent_address = link_get_permanent_address;
 	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_get_driver_info = link_get_driver_info;
 
 	platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
 	platform_class->link_supports_vlans = link_supports_vlans;
 
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
-	platform_class->link_get_master = link_get_master;
 	platform_class->master_set_option = master_set_option;
 	platform_class->master_get_option = master_get_option;
 	platform_class->slave_set_option = slave_set_option;
@@ -4604,6 +4979,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->vlan_set_egress_map = vlan_set_egress_map;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
+	platform_class->infiniband_get_info = infiniband_get_info;
 
 	platform_class->veth_get_properties = veth_get_properties;
 	platform_class->tun_get_properties = tun_get_properties;
@@ -4626,26 +5002,27 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
+	platform_class->ip4_address_get = ip4_address_get;
+	platform_class->ip6_address_get = ip6_address_get;
 	platform_class->ip4_address_get_all = ip4_address_get_all;
 	platform_class->ip6_address_get_all = ip6_address_get_all;
 	platform_class->ip4_address_add = ip4_address_add;
 	platform_class->ip6_address_add = ip6_address_add;
 	platform_class->ip4_address_delete = ip4_address_delete;
 	platform_class->ip6_address_delete = ip6_address_delete;
-	platform_class->ip4_address_exists = ip4_address_exists;
-	platform_class->ip6_address_exists = ip6_address_exists;
-
-	platform_class->ip4_check_reinstall_device_route = ip4_check_reinstall_device_route;
 
+	platform_class->ip4_route_get = ip4_route_get;
+	platform_class->ip6_route_get = ip6_route_get;
 	platform_class->ip4_route_get_all = ip4_route_get_all;
 	platform_class->ip6_route_get_all = ip6_route_get_all;
 	platform_class->ip4_route_add = ip4_route_add;
 	platform_class->ip6_route_add = ip6_route_add;
 	platform_class->ip4_route_delete = ip4_route_delete;
 	platform_class->ip6_route_delete = ip6_route_delete;
-	platform_class->ip4_route_exists = ip4_route_exists;
-	platform_class->ip6_route_exists = ip6_route_exists;
 
 	platform_class->check_support_kernel_extended_ifa_flags = check_support_kernel_extended_ifa_flags;
 	platform_class->check_support_user_ipv6ll = check_support_user_ipv6ll;
+
+	platform_class->process_events = process_events;
 }
+