about 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.c2379
1 files changed, 1158 insertions, 1221 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 6b84c185..c4c93ed3 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -48,6 +48,7 @@
 #include "nmp-object.h"
 #include "nmp-netns.h"
 #include "nm-platform-utils.h"
+#include "nm-platform-private.h"
 #include "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
 #include "nm-utils/unaligned.h"
@@ -103,6 +104,14 @@
 #define IFLA_IPTUN_MAX                  (__IFLA_IPTUN_MAX - 1)
 #endif
 
+
+static const gboolean RTA_PREF_SUPPORTED_AT_COMPILETIME = (RTA_MAX >= 20 /* RTA_PREF */);
+
+G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
+#define RTA_PREF                        20
+#undef  RTA_MAX
+#define RTA_MAX                        (MAX ((__RTA_MAX - 1), RTA_PREF))
+
 #ifndef MACVLAN_FLAG_NOPROMISC
 #define MACVLAN_FLAG_NOPROMISC          1
 #endif
@@ -197,6 +206,21 @@ typedef enum {
 	INFINIBAND_ACTION_DELETE_CHILD,
 } InfinibandAction;
 
+typedef enum {
+	CHANGE_LINK_TYPE_UNSPEC,
+	CHANGE_LINK_TYPE_SET_MTU,
+	CHANGE_LINK_TYPE_SET_ADDRESS,
+} ChangeLinkType;
+
+typedef struct {
+	union {
+		struct {
+			gconstpointer address;
+			gsize length;
+		} set_address;
+	};
+} ChangeLinkData;
+
 enum {
 	DELAYED_ACTION_IDX_REFRESH_ALL_LINKS,
 	DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES,
@@ -255,13 +279,25 @@ static void delayed_action_schedule (NMPlatform *platform, DelayedActionType act
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
 static void do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name);
 static void do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type);
-static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
-static void cache_prune_candidates_prune (NMPlatform *platform);
+static void cache_on_change (NMPlatform *platform,
+                             NMPCacheOpsType cache_op,
+                             const NMPObject *obj_old,
+                             const NMPObject *obj_new);
+static void cache_prune_all (NMPlatform *platform);
 static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
-static void ASSERT_NETNS_CURRENT (NMPlatform *platform);
 
 /*****************************************************************************/
 
+static NMPlatformError
+wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result)
+{
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
+		return NM_PLATFORM_ERROR_SUCCESS;
+	if (seq_result < 0)
+		return (NMPlatformError) seq_result;
+	return NM_PLATFORM_ERROR_NETLINK;
+}
+
 static const char *
 wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size)
 {
@@ -287,35 +323,117 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, g
 	return buf0;
 }
 
-/******************************************************************
+/*****************************************************************************
  * Support IFLA_INET6_ADDR_GEN_MODE
- ******************************************************************/
+ *****************************************************************************/
 
 static int _support_user_ipv6ll = 0;
 #define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0))
 
+static void
+_support_user_ipv6ll_detect (struct nlattr **tb)
+{
+	gboolean supported;
+
+	nm_assert (_support_user_ipv6ll_still_undecided ());
+
+	/* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */
+	supported = !!tb[IFLA_INET6_ADDR_GEN_MODE];
+	_support_user_ipv6ll = supported ? 1 : -1;
+	_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s",
+	        supported ? "detected" : "not detected");
+}
+
 static gboolean
 _support_user_ipv6ll_get (void)
 {
 	if (_support_user_ipv6ll_still_undecided ()) {
-		_support_user_ipv6ll = -1;
-		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s", "failed to detect; assume no support");
-		return FALSE;
+		_support_user_ipv6ll = 1;
+		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s", "failed to detect; assume support");
 	}
-	return _support_user_ipv6ll > 0;
+	return _support_user_ipv6ll >= 0;
+}
+
+/*****************************************************************************
+ * extended IFA_FLAGS support
+ *****************************************************************************/
 
+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
+_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
+{
+	struct nlmsghdr *msg_hdr;
+	gboolean support;
+
+	nm_assert (_support_kernel_extended_ifa_flags_still_undecided ());
+	nm_assert (msg);
+
+	msg_hdr = nlmsg_hdr (msg);
+
+	nm_assert (msg_hdr && msg_hdr->nlmsg_type == RTM_NEWADDR);
+
+	/* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6,
+	 * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */
+	if (NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family, AF_INET, AF_INET6))
+		return;
+
+	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
+	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
+	 * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14,
+	 * dated 30 March, 2014.
+	 *
+	 * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy
+	 * way to detect kernel support. */
+	support = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), IFA_FLAGS);
+	_support_kernel_extended_ifa_flags = support ? 1 : -1;
+	_LOG2D ("kernel-support: extended-ifa-flags: %s", support ? "detected" : "not detected");
 }
 
+static gboolean
+_support_kernel_extended_ifa_flags_get (void)
+{
+	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
+		_LOG2D ("kernel-support: extended-ifa-flags: %s", "unable to detect kernel support for handling IPv6 temporary addresses. Assume support");
+		_support_kernel_extended_ifa_flags = 1;
+	}
+	return _support_kernel_extended_ifa_flags >= 0;
+}
+
+/*****************************************************************************
+ * Support RTA_PREF
+ *****************************************************************************/
+
+static int _support_rta_pref = 0;
+#define _support_rta_pref_still_undecided() (G_UNLIKELY (_support_rta_pref == 0))
+
 static void
-_support_user_ipv6ll_detect (struct nlattr **tb)
+_support_rta_pref_detect (struct nlattr **tb)
 {
-	if (_support_user_ipv6ll_still_undecided ()) {
-		gboolean supported = !!tb[IFLA_INET6_ADDR_GEN_MODE];
+	gboolean supported;
+
+	nm_assert (_support_rta_pref_still_undecided ());
 
-		_support_user_ipv6ll = supported ? 1 : -1;
-		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s",
-		        supported ? "detected" : "not detected");
+	/* RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */
+	supported = !!tb[RTA_PREF];
+	_support_rta_pref = supported ? 1 : -1;
+	_LOG2D ("kernel-support: RTA_PREF: ability to set router preference for IPv6 routes: %s",
+	        supported ? "detected" : "not detected");
+}
+
+static gboolean
+_support_rta_pref_get (void)
+{
+	if (_support_rta_pref_still_undecided ()) {
+		/* if we couldn't detect support, we fallback on compile-time check, whether
+		 * RTA_PREF is present in the kernel headers. */
+		_support_rta_pref = RTA_PREF_SUPPORTED_AT_COMPILETIME ? 1 : -1;
+		_LOG2D ("kernel-support: RTA_PREF: ability to set router preference for IPv6 routes: %s",
+		        RTA_PREF_SUPPORTED_AT_COMPILETIME ? "assume support" : "assume no support");
 	}
+	return _support_rta_pref >= 0;
 }
 
 /******************************************************************
@@ -377,6 +495,7 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_WWAN_NET,      "wwan",        NULL,          "wwan" },
 	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
 
+	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
 	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
 	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
 	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
@@ -388,13 +507,13 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
 	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
 	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
+	{ NM_LINK_TYPE_PPP,           "ppp",         NULL,          "ppp" },
 	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         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" },
@@ -628,7 +747,7 @@ _linktype_get_type (NMPlatform *platform,
 {
 	guint i;
 
-	ASSERT_NETNS_CURRENT (platform);
+	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (platform);
 	nm_assert (ifname);
 
 	if (completed_from_cache) {
@@ -693,6 +812,8 @@ _linktype_get_type (NMPlatform *platform,
 		return NM_LINK_TYPE_SIT;
 	else if (arptype == ARPHRD_TUNNEL6)
 		return NM_LINK_TYPE_IP6TNL;
+	else if (arptype == ARPHRD_PPP)
+		return NM_LINK_TYPE_PPP;
 
 	{
 		NMPUtilsEthtoolDriverInfo driver_info;
@@ -762,6 +883,10 @@ _linktype_get_type (NMPlatform *platform,
 			 * aside from the DEVTYPE. */
 			if (!g_strcmp0 (devtype, "gadget"))
 				return NM_LINK_TYPE_ETHERNET;
+
+			/* Distributed Switch Architecture switch chips */
+			if (!g_strcmp0 (devtype, "dsa"))
+				return NM_LINK_TYPE_ETHERNET;
 		}
 	}
 
@@ -772,32 +897,149 @@ _linktype_get_type (NMPlatform *platform,
  * libnl unility functions and wrappers
  ******************************************************************/
 
-#define nm_auto_nlmsg __attribute__((cleanup(_nm_auto_nl_msg_cleanup)))
+#define NLMSG_TAIL(nmsg) \
+    ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/* copied from iproute2's addattr_l(). */
+static gboolean
+_nl_addattr_l (struct nlmsghdr *n,
+               int maxlen,
+               int type,
+               const void *data,
+               int alen)
+{
+	int len = RTA_LENGTH (alen);
+	struct rtattr *rta;
+
+	if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+		return FALSE;
+
+	rta = NLMSG_TAIL (n);
+	rta->rta_type = type;
+	rta->rta_len = len;
+	memcpy (RTA_DATA (rta), data, alen);
+	n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+	return TRUE;
+}
+
 static void
 _nm_auto_nl_msg_cleanup (void *ptr)
 {
 	nlmsg_free (*((struct nl_msg **) ptr));
 }
+#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
 
 static const char *
-_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
+_nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
 {
-	const char *str_type = NULL;
+	const char *b;
+	const char *s;
+	guint flags, flags_before;
+	const char *prefix;
 
-	switch (type) {
-	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;
+	nm_utils_to_string_buffer_init (&buf, &len);
+	b = buf;
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:  s = "NEWLINK";  break;
+	case RTM_DELLINK:  s = "DELLINK";  break;
+	case RTM_NEWADDR:  s = "NEWADDR";  break;
+	case RTM_DELADDR:  s = "DELADDR";  break;
+	case RTM_NEWROUTE: s = "NEWROUTE"; break;
+	case RTM_DELROUTE: s = "DELROUTE"; break;
+	default:           s = NULL;       break;
 	}
-	if (str_type)
-		g_strlcpy (buf, str_type, len);
+
+	if (s)
+		nm_utils_strbuf_append (&buf, &len, "RTM_%s", s);
 	else
-		g_snprintf (buf, len, "(%d)", type);
-	return buf;
+		nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
+
+	flags = hdr->nlmsg_flags;
+
+	if (!flags) {
+		nm_utils_strbuf_append_str (&buf, &len, ", flags 0");
+		goto flags_done;
+	}
+
+#define _F(f, n) \
+	G_STMT_START { \
+		if (NM_FLAGS_ALL (flags, f)) { \
+			flags &= ~(f); \
+			nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \
+			if (!flags) \
+				goto flags_done; \
+			prefix = ","; \
+		} \
+	} G_STMT_END
+
+	prefix = ", flags ";
+	flags_before = flags;
+	_F (NLM_F_REQUEST, "request");
+	_F (NLM_F_MULTI, "multi");
+	_F (NLM_F_ACK, "ack");
+	_F (NLM_F_ECHO, "echo");
+	_F (NLM_F_DUMP_INTR, "dump_intr");
+	_F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
+
+	if (flags_before != flags)
+		prefix = ";";
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_NEWADDR:
+	case RTM_NEWROUTE:
+		_F (NLM_F_REPLACE, "replace");
+		_F (NLM_F_EXCL, "excl");
+		_F (NLM_F_CREATE, "create");
+		_F (NLM_F_APPEND, "append");
+		break;
+	case RTM_GETLINK:
+	case RTM_GETADDR:
+	case RTM_GETROUTE:
+		_F (NLM_F_DUMP, "dump");
+		_F (NLM_F_ROOT, "root");
+		_F (NLM_F_MATCH, "match");
+		_F (NLM_F_ATOMIC, "atomic");
+		break;
+	}
+
+#undef _F
+
+	if (flags_before != flags)
+		prefix = ";";
+	nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags);
+
+flags_done:
+
+	nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
+
+	return b;
+}
+
+static int
+_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+               const struct nla_policy *policy)
+{
+	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
+}
+#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
+
+static int
+_nl_nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+                 int maxtype, const struct nla_policy *policy)
+{
+	return nlmsg_parse (nlh, hdrlen, tb, maxtype, (struct nla_policy *) policy);
 }
+#define nlmsg_parse(...) _nl_nlmsg_parse(__VA_ARGS__)
+
+static int
+_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+                      const struct nla_policy *policy)
+{
+	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
+}
+#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
 
 /******************************************************************
  * NMPObject/netlink functions
@@ -826,7 +1068,7 @@ _parse_af_inet6 (NMPlatform *platform,
                  guint8 *out_addr_gen_mode_inv,
                  gboolean *out_addr_gen_mode_valid)
 {
-	static struct nla_policy policy[IFLA_INET6_MAX+1] = {
+	static const struct nla_policy policy[IFLA_INET6_MAX+1] = {
 		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
 		[IFLA_INET6_CACHEINFO]          = { .minlen = nm_offsetofend (struct ifla_cacheinfo, retrans_time) },
 		[IFLA_INET6_CONF]               = { .minlen = 4 },
@@ -862,7 +1104,8 @@ _parse_af_inet6 (NMPlatform *platform,
 	/* Hack to detect support addrgenmode of the kernel. We only parse
 	 * netlink messages that we receive from kernel, hence this check
 	 * is valid. */
-	_support_user_ipv6ll_detect (tb);
+	if (_support_user_ipv6ll_still_undecided ())
+		_support_user_ipv6ll_detect (tb);
 
 	if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
 		i6_addr_gen_mode_inv = _nm_platform_uint8_inv (nla_get_u8 (tb[IFLA_INET6_ADDR_GEN_MODE]));
@@ -892,7 +1135,7 @@ errout:
 static NMPObject *
 _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
 		[IFLA_GRE_LINK]     = { .type = NLA_U32 },
 		[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
 		[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
@@ -952,7 +1195,7 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
 		[IFLA_IPOIB_PKEY]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_MODE]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_UMCAST] = { .type = NLA_U16 },
@@ -998,7 +1241,7 @@ _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
 		[IFLA_IPTUN_LINK]        = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]       = { .type = NLA_UNSPEC,
 		                             .minlen = sizeof (struct in6_addr)},
@@ -1051,7 +1294,7 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1089,7 +1332,7 @@ _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
 		[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
 		[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
 	};
@@ -1132,7 +1375,7 @@ _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[__IFLA_MACSEC_MAX] = {
+	static const struct nla_policy policy[__IFLA_MACSEC_MAX] = {
 		[IFLA_MACSEC_SCI]            = { .type = NLA_U64 },
 		[IFLA_MACSEC_ICV_LEN]        = { .type = NLA_U8 },
 		[IFLA_MACSEC_CIPHER_SUITE]   = { .type = NLA_U64 },
@@ -1182,7 +1425,7 @@ _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1284,7 +1527,7 @@ _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
 static NMPObject *
 _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_VLAN_MAX+1] = {
+	static const struct nla_policy policy[IFLA_VLAN_MAX+1] = {
 		[IFLA_VLAN_ID]          = { .type = NLA_U16 },
 		[IFLA_VLAN_FLAGS]       = { .minlen = nm_offsetofend (struct ifla_vlan_flags, flags) },
 		[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
@@ -1370,7 +1613,7 @@ struct nm_ifla_vxlan_port_range {
 static NMPObject *
 _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
 		[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
@@ -1460,7 +1703,7 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
 {
-	static struct nla_policy policy[IFLA_MAX+1] = {
+	static const struct nla_policy policy[IFLA_MAX+1] = {
 		[IFLA_IFNAME]           = { .type = NLA_STRING,
 		                            .maxlen = IFNAMSIZ },
 		[IFLA_MTU]              = { .type = NLA_U32 },
@@ -1488,7 +1731,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		[IFLA_NET_NS_PID]       = { .type = NLA_U32 },
 		[IFLA_NET_NS_FD]        = { .type = NLA_U32 },
 	};
-	static struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
+	static const struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
 		[IFLA_INFO_KIND]        = { .type = NLA_STRING },
 		[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
 		[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
@@ -1514,6 +1757,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		return NULL;
 	ifi = nlmsg_data(nlh);
 
+	if (ifi->ifi_family != AF_UNSPEC)
+		return NULL;
+
 	obj = nmp_object_new_link (ifi->ifi_index);
 
 	if (id_only)
@@ -1680,7 +1926,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 				 * Also, sometimes the info-data is missing for updates. In this case
 				 * we want to keep the previously received lnk_data. */
 				nmp_object_unref (lnk_data);
-				lnk_data = nmp_object_ref (link_cached->_link.netlink.lnk);
+				lnk_data = (NMPObject *) nmp_object_ref (link_cached->_link.netlink.lnk);
 			}
 			if (address_complete_from_cache)
 				obj->link.addr = link_cached->link.addr;
@@ -1711,7 +1957,7 @@ errout:
 static NMPObject *
 _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static struct nla_policy policy[IFA_MAX+1] = {
+	static const struct nla_policy policy[IFA_MAX+1] = {
 		[IFA_LABEL]     = { .type = NLA_STRING,
 		                     .maxlen = IFNAMSIZ },
 		[IFA_CACHEINFO] = { .minlen = nm_offsetofend (struct ifa_cacheinfo, tstamp) },
@@ -1733,7 +1979,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 		goto errout;
 	is_v4 = ifa->ifa_family == AF_INET;
 
-	err = nlmsg_parse(nlh, sizeof(*ifa), tb, IFA_MAX, policy);
+	err = nlmsg_parse (nlh, sizeof(*ifa), tb, IFA_MAX, policy);
 	if (err < 0)
 		goto errout;
 
@@ -1826,10 +2072,12 @@ errout:
 static NMPObject *
 _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static struct nla_policy policy[RTA_MAX+1] = {
+	static const struct nla_policy policy[RTA_MAX+1] = {
+		[RTA_TABLE]     = { .type = NLA_U32 },
 		[RTA_IIF]       = { .type = NLA_U32 },
 		[RTA_OIF]       = { .type = NLA_U32 },
 		[RTA_PRIORITY]  = { .type = NLA_U32 },
+		[RTA_PREF]      = { .type = NLA_U8 },
 		[RTA_FLOW]      = { .type = NLA_U32 },
 		[RTA_CACHEINFO] = { .minlen = nm_offsetofend (struct rta_cacheinfo, rta_tsage) },
 		[RTA_METRICS]   = { .type = NLA_NESTED },
@@ -1849,7 +2097,6 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	} nh;
 	guint32 mss;
 	guint32 window = 0, cwnd = 0, initcwnd = 0, initrwnd = 0, mtu = 0, lock = 0;
-	guint32 table;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*rtm)))
 		return NULL;
@@ -1869,12 +2116,6 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	if (err < 0)
 		goto errout;
 
-	table = tb[RTA_TABLE]
-	        ? nla_get_u32 (tb[RTA_TABLE])
-	        : (guint32) rtm->rtm_table;
-	if (table != RT_TABLE_MAIN)
-		goto errout;
-
 	/*****************************************************************/
 
 	is_v4 = rtm->rtm_family == AF_INET;
@@ -1928,7 +2169,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	    || tb[RTA_GATEWAY]
 	    || tb[RTA_FLOW]) {
 		int ifindex = 0;
-		NMIPAddr gateway = NMIPAddrInit;
+		NMIPAddr gateway = { };
 
 		if (tb[RTA_OIF])
 			ifindex = nla_get_u32 (tb[RTA_OIF]);
@@ -1956,7 +2197,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	mss = 0;
 	if (tb[RTA_METRICS]) {
 		struct nlattr *mtb[RTAX_MAX + 1];
-		static struct nla_policy rtax_policy[RTAX_MAX + 1] = {
+		static const struct nla_policy rtax_policy[RTAX_MAX + 1] = {
 			[RTAX_LOCK]        = { .type = NLA_U32 },
 			[RTAX_ADVMSS]      = { .type = NLA_U32 },
 			[RTAX_WINDOW]      = { .type = NLA_U32 },
@@ -1990,6 +2231,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
 
+	obj->ip_route.table_coerced = nm_platform_route_table_coerce (  tb[RTA_TABLE]
+	                                                              ? nla_get_u32 (tb[RTA_TABLE])
+	                                                              : (guint32) rtm->rtm_table);
+
 	obj->ip_route.ifindex = nh.ifindex;
 
 	if (_check_addr_or_errout (tb, RTA_DST, addr_len))
@@ -2015,9 +2260,13 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			memcpy (&obj->ip6_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len);
 	}
 
-	if (!is_v4 && tb[RTA_SRC]) {
-		_check_addr_or_errout (tb, RTA_SRC, addr_len);
-		memcpy (&obj->ip6_route.src, nla_data (tb[RTA_SRC]), addr_len);
+	if (is_v4)
+		obj->ip4_route.tos = rtm->rtm_tos;
+	else {
+		if (tb[RTA_SRC]) {
+			_check_addr_or_errout (tb, RTA_SRC, addr_len);
+			memcpy (&obj->ip6_route.src, nla_data (tb[RTA_SRC]), addr_len);
+		}
 		obj->ip6_route.src_plen = rtm->rtm_src_len;
 	}
 
@@ -2027,12 +2276,20 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_route.initcwnd = initcwnd;
 	obj->ip_route.initrwnd = initrwnd;
 	obj->ip_route.mtu = mtu;
-	obj->ip_route.tos = rtm->rtm_tos;
-	obj->ip_route.lock_window = NM_FLAGS_HAS (lock, 1 << RTAX_WINDOW);
-	obj->ip_route.lock_cwnd = NM_FLAGS_HAS (lock, 1 << RTAX_CWND);
+	obj->ip_route.lock_window   = NM_FLAGS_HAS (lock, 1 << RTAX_WINDOW);
+	obj->ip_route.lock_cwnd     = NM_FLAGS_HAS (lock, 1 << RTAX_CWND);
 	obj->ip_route.lock_initcwnd = NM_FLAGS_HAS (lock, 1 << RTAX_INITCWND);
 	obj->ip_route.lock_initrwnd = NM_FLAGS_HAS (lock, 1 << RTAX_INITRWND);
-	obj->ip_route.lock_mtu  = NM_FLAGS_HAS (lock, 1 << RTAX_MTU);
+	obj->ip_route.lock_mtu      = NM_FLAGS_HAS (lock, 1 << RTAX_MTU);
+
+	if (!is_v4) {
+		/* Detect support for RTA_PREF by inspecting the netlink message. */
+		if (_support_rta_pref_still_undecided ())
+			_support_rta_pref_detect (tb);
+
+		if (tb[RTA_PREF])
+			obj->ip6_route.rt_pref = nla_get_u8 (tb[RTA_PREF]);
+	}
 
 	if (NM_FLAGS_HAS (rtm->rtm_flags, RTM_F_CLONED)) {
 		/* we must not straight way reject cloned routes, because we might have cached
@@ -2135,12 +2392,14 @@ nla_put_failure:
 
 static gboolean
 _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
-                               NMLinkType link_type)
+                               NMLinkType link_type,
+                               const char *veth_peer)
 {
 	struct nlattr *info;
 	const char *kind;
 
 	nm_assert (msg);
+	nm_assert (!!veth_peer == (link_type == NM_LINK_TYPE_VETH));
 
 	kind = nm_link_type_to_rtnl_type_string (link_type);
 	if (!kind)
@@ -2151,11 +2410,26 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 
 	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
 
+	if (veth_peer) {
+		struct ifinfomsg ifi = { };
+		struct nlattr *data, *info_peer;
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+		if (!(info_peer = nla_nest_start (msg, 1 /*VETH_INFO_PEER*/)))
+			goto nla_put_failure;
+		if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+			goto nla_put_failure;
+		NLA_PUT_STRING (msg, IFLA_IFNAME, veth_peer);
+		nla_nest_end (msg, info_peer);
+		nla_nest_end (msg, data);
+	}
+
 	nla_nest_end (msg, info);
 
 	return TRUE;
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -2363,7 +2637,7 @@ _nl_msg_new_address (int nlmsg_type,
 	    && *((in_addr_t *) address) != 0) {
 		in_addr_t broadcast;
 
-		broadcast = *((in_addr_t *) address) | ~nm_utils_ip4_prefix_to_netmask (plen);
+		broadcast = *((in_addr_t *) address) | ~_nm_utils_ip4_prefix_to_netmask (plen);
 		NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast);
 	}
 
@@ -2394,87 +2668,109 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
+static guint32
+ip_route_get_lock_flag (const NMPlatformIPRoute *route)
+{
+	return   (((guint32) route->lock_window)   << RTAX_WINDOW)
+	       | (((guint32) route->lock_cwnd)     << RTAX_CWND)
+	       | (((guint32) route->lock_initcwnd) << RTAX_INITCWND)
+	       | (((guint32) route->lock_initrwnd) << RTAX_INITRWND)
+	       | (((guint32) route->lock_mtu)      << RTAX_MTU);
+}
+
 /* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */
 static struct nl_msg *
 _nl_msg_new_route (int nlmsg_type,
-                   int nlmsg_flags,
-                   int family,
-                   int ifindex,
-                   NMIPConfigSource source,
-                   unsigned char scope,
-                   gconstpointer network,
-                   guint8 plen,
-                   gconstpointer gateway,
-                   guint32 metric,
-                   guint32 mss,
-                   gconstpointer pref_src,
-                   gconstpointer src,
-                   guint8 src_plen,
-                   guint8 tos,
-                   guint32 window,
-                   guint32 cwnd,
-                   guint32 initcwnd,
-                   guint32 initrwnd,
-                   guint32 mtu,
-                   guint32 lock)
+                   guint16 nlmsgflags,
+                   const NMPObject *obj)
 {
 	struct nl_msg *msg;
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	gboolean is_v4 = klass->addr_family == AF_INET;
+	const guint32 lock = ip_route_get_lock_flag (NMP_OBJECT_CAST_IP_ROUTE (obj));
+	const guint32 table = nm_platform_route_table_uncoerce (NMP_OBJECT_CAST_IP_ROUTE (obj)->table_coerced, TRUE);
 	struct rtmsg rtmsg = {
-		.rtm_family = family,
-		.rtm_tos = tos,
-		.rtm_table = RT_TABLE_MAIN, /* omit setting RTA_TABLE attribute */
-		.rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot (source),
-		.rtm_scope = scope,
+		.rtm_family = klass->addr_family,
+		.rtm_tos = is_v4
+		           ? obj->ip4_route.tos
+		           : 0,
+		.rtm_table = table <= 0xFF ? table : RT_TABLE_UNSPEC,
+		.rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot (obj->ip_route.rt_source),
+		.rtm_scope = is_v4
+		             ? nm_platform_route_scope_inv (obj->ip4_route.scope_inv)
+		             : RT_SCOPE_NOWHERE,
 		.rtm_type = RTN_UNICAST,
 		.rtm_flags = 0,
-		.rtm_dst_len = plen,
-		.rtm_src_len = src ? src_plen : 0,
+		.rtm_dst_len = obj->ip_route.plen,
+		.rtm_src_len = is_v4
+		               ? 0
+		               : NMP_OBJECT_CAST_IP6_ROUTE (obj)->src_plen,
 	};
 
 	gsize addr_len;
 
-	nm_assert (NM_IN_SET (family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
-	nm_assert (network);
 
-	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags);
 	if (!msg)
 		g_return_val_if_reached (NULL);
 
 	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
 
-	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
+	addr_len = is_v4
+	             ? sizeof (in_addr_t)
+	             : sizeof (struct in6_addr);
 
-	NLA_PUT (msg, RTA_DST, addr_len, network);
+	NLA_PUT (msg, RTA_DST, addr_len,
+	         is_v4
+	           ? (gconstpointer) &obj->ip4_route.network
+	           : (gconstpointer) &obj->ip6_route.network);
 
-	if (src)
-		NLA_PUT (msg, RTA_SRC, addr_len, src);
+	if (!is_v4) {
+		if (!IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (obj)->src))
+			NLA_PUT (msg, RTA_SRC, addr_len, &obj->ip6_route.src);
+	}
 
-	NLA_PUT_U32 (msg, RTA_PRIORITY, metric);
+	NLA_PUT_U32 (msg, RTA_PRIORITY, obj->ip_route.metric);
 
-	if (pref_src)
-		NLA_PUT (msg, RTA_PREFSRC, addr_len, pref_src);
+	if (table > 0xFF)
+		NLA_PUT_U32 (msg, RTA_TABLE, table);
+
+	if (is_v4) {
+		if (NMP_OBJECT_CAST_IP4_ROUTE (obj)->pref_src)
+			NLA_PUT (msg, RTA_PREFSRC, addr_len, &obj->ip4_route.pref_src);
+	} else {
+		if (!IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (obj)->pref_src))
+			NLA_PUT (msg, RTA_PREFSRC, addr_len, &obj->ip6_route.pref_src);
+	}
 
-	if (mss || window || cwnd || initcwnd || initrwnd || mtu || lock) {
+	if (   obj->ip_route.mss
+	    || obj->ip_route.window
+	    || obj->ip_route.cwnd
+	    || obj->ip_route.initcwnd
+	    || obj->ip_route.initrwnd
+	    || obj->ip_route.mtu
+	    || lock) {
 		struct nlattr *metrics;
 
 		metrics = nla_nest_start (msg, RTA_METRICS);
 		if (!metrics)
 			goto nla_put_failure;
 
-		if (mss)
-			NLA_PUT_U32 (msg, RTAX_ADVMSS, mss);
-		if (window)
-			NLA_PUT_U32 (msg, RTAX_WINDOW, window);
-		if (cwnd)
-			NLA_PUT_U32 (msg, RTAX_CWND, cwnd);
-		if (initcwnd)
-			NLA_PUT_U32 (msg, RTAX_INITCWND, initcwnd);
-		if (initrwnd)
-			NLA_PUT_U32 (msg, RTAX_INITRWND, initrwnd);
-		if (mtu)
-			NLA_PUT_U32 (msg, RTAX_MTU, mtu);
+		if (obj->ip_route.mss)
+			NLA_PUT_U32 (msg, RTAX_ADVMSS, obj->ip_route.mss);
+		if (obj->ip_route.window)
+			NLA_PUT_U32 (msg, RTAX_WINDOW, obj->ip_route.window);
+		if (obj->ip_route.cwnd)
+			NLA_PUT_U32 (msg, RTAX_CWND, obj->ip_route.cwnd);
+		if (obj->ip_route.initcwnd)
+			NLA_PUT_U32 (msg, RTAX_INITCWND, obj->ip_route.initcwnd);
+		if (obj->ip_route.initrwnd)
+			NLA_PUT_U32 (msg, RTAX_INITRWND, obj->ip_route.initrwnd);
+		if (obj->ip_route.mtu)
+			NLA_PUT_U32 (msg, RTAX_MTU, obj->ip_route.mtu);
 		if (lock)
 			NLA_PUT_U32 (msg, RTAX_LOCK, lock);
 
@@ -2482,10 +2778,17 @@ _nl_msg_new_route (int nlmsg_type,
 	}
 
 	/* We currently don't have need for multi-hop routes... */
-	if (   gateway
-	    && memcmp (gateway, &nm_ip_addr_zero, addr_len) != 0)
-		NLA_PUT (msg, RTA_GATEWAY, addr_len, gateway);
-	NLA_PUT_U32 (msg, RTA_OIF, ifindex);
+	if (is_v4) {
+		NLA_PUT (msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway);
+	} else {
+		if (!IN6_IS_ADDR_UNSPECIFIED (&obj->ip6_route.gateway))
+			NLA_PUT (msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway);
+	}
+	NLA_PUT_U32 (msg, RTA_OIF, obj->ip_route.ifindex);
+
+	if (   !is_v4
+	    && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM)
+		NLA_PUT_U8 (msg, RTA_PREF, obj->ip6_route.rt_pref);
 
 	return msg;
 
@@ -2494,56 +2797,27 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
-/*****************************************************************************/
-
-static int _support_kernel_extended_ifa_flags = -1;
-
-#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == -1))
-
-static void
-_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
-{
-	struct nlmsghdr *msg_hdr;
-
-	if (!_support_kernel_extended_ifa_flags_still_undecided ())
-		return;
-
-	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)
-		return;
-
-	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
-	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
-	 * and IFA_F_NOPREFIXROUTE (they were added together).
-	 **/
-	_support_kernel_extended_ifa_flags = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), IFA_FLAGS);
-	_LOG2D ("kernel-support: extended-ifa-flags: %s", _support_kernel_extended_ifa_flags ? "detected" : "not detected");
-}
-
-static gboolean
-_support_kernel_extended_ifa_flags_get (void)
-{
-	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
-		_LOG2D ("kernel-support: extended-ifa-flags: %s", "unable to detect kernel support for handling IPv6 temporary addresses. Assume support");
-		_support_kernel_extended_ifa_flags = 1;
-	}
-	return _support_kernel_extended_ifa_flags;
-}
-
 /******************************************************************
  * NMPlatform types and functions
  ******************************************************************/
 
+typedef enum {
+	DELAYED_ACTION_RESPONSE_TYPE_VOID                       = 0,
+	DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS    = 1,
+	DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET                  = 2,
+} DelayedActionWaitForNlResponseType;
+
 typedef struct {
 	guint32 seq_number;
 	WaitForNlResponseResult seq_result;
+	DelayedActionWaitForNlResponseType response_type;
 	gint64 timeout_abs_ns;
 	WaitForNlResponseResult *out_seq_result;
-	gint *out_refresh_all_in_progess;
+	union {
+		gint *out_refresh_all_in_progess;
+		NMPObject **out_route_get;
+		gpointer out_data;
+	} response;
 } DelayedActionWaitForNlResponseData;
 
 typedef struct {
@@ -2553,11 +2827,12 @@ typedef struct {
 	guint32 nlh_seq_last_handled;
 #endif
 	guint32 nlh_seq_last_seen;
-	NMPCache *cache;
 	GIOChannel *event_channel;
 	guint event_id;
 
-	gboolean sysctl_get_warned;
+	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+
+	bool sysctl_get_warned;
 	GHashTable *sysctl_get_prev_values;
 
 	NMUdevClient *udev_client;
@@ -2578,8 +2853,6 @@ typedef struct {
 		gint is_handling;
 	} delayed_action;
 
-	GHashTable *prune_candidates;
-
 	GHashTable *wifi_data;
 } NMLinuxPlatformPrivate;
 
@@ -2599,8 +2872,15 @@ G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
 NMPlatform *
 nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support)
 {
+	gboolean use_udev = FALSE;
+
+	if (   nmp_netns_is_initial ()
+	    && access ("/sys", W_OK) == 0)
+		use_udev = TRUE;
+
 	return g_object_new (NM_TYPE_LINUX_PLATFORM,
 	                     NM_PLATFORM_LOG_WITH_PTR, log_with_ptr,
+	                     NM_PLATFORM_USE_UDEV, use_udev,
 	                     NM_PLATFORM_NETNS_SUPPORT, netns_support,
 	                     NULL);
 }
@@ -2611,13 +2891,6 @@ nm_linux_platform_setup (void)
 	nm_platform_setup (nm_linux_platform_new (FALSE, FALSE));
 }
 
-static void
-ASSERT_NETNS_CURRENT (NMPlatform *platform)
-{
-	nm_assert (NM_IS_LINUX_PLATFORM (platform));
-	nm_assert (NM_IN_SET (nm_platform_netns_get (platform), NULL, nmp_netns_get_current ()));
-}
-
 /*****************************************************************************/
 
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
@@ -2676,7 +2949,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int fd, tries;
 	gssize nwrote;
-	gsize len;
+	gssize len;
 	char *actual;
 	gs_free char *actual_free = NULL;
 	int errsv;
@@ -2731,6 +3004,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	 * about to write.
 	 */
 	len = strlen (value) + 1;
+	nm_assert (len > 0);
 	if (len > 512)
 		actual = actual_free = g_malloc (len + 1);
 	else
@@ -2752,16 +3026,27 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			break;
 		}
 	}
-	if (nwrote == -1 && errsv != EEXIST) {
-		_LOGE ("sysctl: failed to set '%s' to '%s': (%d) %s",
-		       path, value, errsv, strerror (errsv));
+	if (nwrote == -1) {
+		NMLogLevel level = LOGL_ERR;
+
+		if (errsv == EEXIST) {
+			level = LOGL_DEBUG;
+		} else if (   errsv == EINVAL
+		           && nm_utils_sysctl_ip_conf_is_path (AF_INET6, path, NULL, "mtu")) {
+			/* setting the MTU can fail under regular conditions. Suppress
+			 * logging a warning. */
+			level = LOGL_DEBUG;
+		}
+
+		_NMLOG (level, "sysctl: failed to set '%s' to '%s': (%d) %s",
+		        path, value, errsv, strerror (errsv));
 	} else if (nwrote < len - 1) {
 		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
 		       path, value);
 	}
 
 	if (nwrote < len - 1) {
-		if (close (fd) != 0) {
+		if (nm_close (fd) != 0) {
 			if (errsv != 0)
 				errno = errsv;
 		} else if (errsv != 0)
@@ -2770,7 +3055,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			errno = EIO;
 		return FALSE;
 	}
-	if (close (fd) != 0) {
+	if (nm_close (fd) != 0) {
 		/* errno is already properly set. */
 		return FALSE;
 	}
@@ -2804,7 +3089,7 @@ _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *pathid, const char *
 	if (!priv->sysctl_get_prev_values) {
 		_nm_logging_clear_platform_logging_cache = _nm_logging_clear_platform_logging_cache_impl;
 		sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform);
-		priv->sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		priv->sysctl_get_prev_values = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 	} else
 		prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, pathid);
 
@@ -2872,20 +3157,30 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 
 /*****************************************************************************/
 
-static gboolean
-check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+static NMPlatformKernelSupportFlags
+check_kernel_support (NMPlatform *platform,
+                      NMPlatformKernelSupportFlags request_flags)
 {
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	NMPlatformKernelSupportFlags response = 0;
 
-	return _support_kernel_extended_ifa_flags_get ();
-}
+	nm_assert (NM_IS_LINUX_PLATFORM (platform));
 
-static gboolean
-check_support_user_ipv6ll (NMPlatform *platform)
-{
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+		if (_support_kernel_extended_ifa_flags_get ())
+			response |= NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS;
+	}
 
-	return _support_user_ipv6ll_get ();
+	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
+		if (_support_user_ipv6ll_get ())
+			response |= NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL;
+	}
+
+	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF)) {
+		if (_support_rta_pref_get ())
+			response |= NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF;
+	}
+
+	return response;
 }
 
 static void
@@ -2896,86 +3191,6 @@ process_events (NMPlatform *platform)
 
 /*****************************************************************************/
 
-#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
-	({ \
-		NMPCacheId _cache_id; \
-		\
-		((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
-		                                              nmp_cache_id_init_object_type (&_cache_id, (obj_type), (visible_only)), \
-		                                              NULL)); \
-	})
-
-/*****************************************************************************/
-
-static void
-do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible)
-{
-	gboolean is_visible;
-	NMPObject obj_clone;
-	const NMPClass *klass;
-
-	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));
-
-	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));
-
-	ASSERT_NETNS_CURRENT (platform);
-
-	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",
-	       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));
-
-	/* 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 (platform,
-	               _nm_platform_signal_id_get (klass->signal_type_id),
-	               0,
-	               (int) klass->obj_type,
-	               obj_clone.object.ifindex,
-	               &obj_clone.object,
-	               (int) cache_op);
-}
-
-/*****************************************************************************/
-
 _NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_from_object_type, NMPObjectType, DelayedActionType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (DELAYED_ACTION_TYPE_NONE),
 	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_LINK,        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS),
@@ -3043,11 +3258,12 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data
 			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_ns ();
 			char b[255];
 
-			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT"%s%s)",
+			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT", response-type %d%s%s)",
 			                        data->seq_number,
 			                        timeout < 0 ? "-" : "",
 			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND,
 			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
+			                        (int) data->response_type,
 			                        data->seq_result ? ", " : "",
 			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : "");
 		} else
@@ -3109,9 +3325,22 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE;
 	if (data->out_seq_result)
 		*data->out_seq_result = seq_result;
-	if (data->out_refresh_all_in_progess) {
-		nm_assert (*data->out_refresh_all_in_progess > 0);
-		*data->out_refresh_all_in_progess -= 1;
+	switch (data->response_type) {
+	case DELAYED_ACTION_RESPONSE_TYPE_VOID:
+		break;
+	case DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS:
+		if (data->response.out_refresh_all_in_progess) {
+			nm_assert (*data->response.out_refresh_all_in_progess > 0);
+			*data->response.out_refresh_all_in_progess -= 1;
+			data->response.out_refresh_all_in_progess = NULL;
+		}
+		break;
+	case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET:
+		if (data->response.out_route_get) {
+			nm_assert (!*data->response.out_route_get);
+			data->response.out_route_get = NULL;
+		}
+		break;
 	}
 
 	g_array_remove_index_fast (priv->delayed_action.list_wait_for_nl_response, idx);
@@ -3146,13 +3375,15 @@ delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
 static void
 delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	nm_auto_nmpobj NMPObject *obj_cache = NULL;
-	gboolean was_visible;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
 	NMPCacheOpsType cache_op;
 
-	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);
+	cache_op = nmp_cache_update_link_master_connected (nm_platform_get_cache (platform), master_ifindex, &obj_old, &obj_new);
+	if (cache_op == NMP_CACHE_OPS_UNCHANGED)
+		return;
+	cache_on_change (platform, cache_op, obj_old, obj_new);
+	nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 }
 
 static void
@@ -3273,7 +3504,7 @@ delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink)
 		any = TRUE;
 	priv->delayed_action.is_handling--;
 
-	cache_prune_candidates_prune (platform);
+	cache_prune_all (platform);
 
 	return any;
 }
@@ -3319,13 +3550,15 @@ static void
 delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
                                               guint32 seq_number,
                                               WaitForNlResponseResult *out_seq_result,
-                                              gint *out_refresh_all_in_progess)
+                                              DelayedActionWaitForNlResponseType response_type,
+                                              gpointer response_out_data)
 {
 	DelayedActionWaitForNlResponseData data = {
 		.seq_number = seq_number,
 		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
 		.out_seq_result = out_seq_result,
-		.out_refresh_all_in_progess = out_refresh_all_in_progess,
+		.response_type = response_type,
+		.response.out_data = response_out_data,
 	};
 
 	delayed_action_schedule (platform,
@@ -3336,146 +3569,111 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 /*****************************************************************************/
 
 static void
-cache_prune_candidates_record_all (NMPlatform *platform, NMPObjectType obj_type)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-
-	priv->prune_candidates = nmp_cache_lookup_all_to_hash (priv->cache,
-	                                                       nmp_cache_id_init_object_type (&cache_id, 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);
-
-	if (!priv->prune_candidates)
-		priv->prune_candidates = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
-
-	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
-cache_prune_candidates_drop (NMPlatform *platform, const NMPObject *obj)
+cache_prune_one_type (NMPlatform *platform, NMPObjectType obj_type)
 {
-	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);
+	NMDedupMultiIter iter;
+	const NMPObject *obj;
+	NMPCacheOpsType cache_op;
+	NMPLookup lookup;
+	NMPCache *cache = nm_platform_get_cache (platform);
+
+	nmp_lookup_init_obj_type (&lookup,
+	                          obj_type);
+	nm_dedup_multi_iter_init (&iter,
+	                          nmp_cache_lookup (cache,
+	                                            &lookup));
+	while (nm_dedup_multi_iter_next (&iter)) {
+		if (iter.current->dirty) {
+			nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+			obj = iter.current->obj;
+			_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+			cache_op = nmp_cache_remove (cache, obj, TRUE, TRUE, &obj_old);
+			nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+			cache_on_change (platform, cache_op, obj_old, NULL);
+			nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, NULL);
+		}
 	}
 }
 
 static void
-cache_prune_candidates_prune (NMPlatform *platform)
+cache_prune_all (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GHashTable *prune_candidates;
-	GHashTableIter iter;
-	const NMPObject *obj;
-	gboolean was_visible;
-	NMPCacheOpsType cache_op;
-
-	if (!priv->prune_candidates)
-		return;
-
-	prune_candidates = priv->prune_candidates;
-	priv->prune_candidates = NULL;
+	DelayedActionType iflags, action_type;
 
-	g_hash_table_iter_init (&iter, prune_candidates);
-	while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) {
-		nm_auto_nmpobj NMPObject *obj_cache = NULL;
+	action_type = DELAYED_ACTION_TYPE_REFRESH_ALL;
+	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+		bool *p = &priv->pruning[delayed_action_refresh_all_to_idx (iflags)];
 
-		_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);
+		if (*p) {
+			*p = FALSE;
+			cache_prune_one_type (platform, delayed_action_refresh_to_object_type (iflags));
+		}
 	}
-
-	g_hash_table_unref (prune_candidates);
 }
 
 static void
-cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+cache_on_change (NMPlatform *platform,
+                 NMPCacheOpsType cache_op,
+                 const NMPObject *obj_old,
+                 const NMPObject *obj_new)
 {
-	NMPlatform *platform = user_data;
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPClass *klass;
 	char str_buf[sizeof (_nm_utils_to_string_buffer)];
 	char str_buf2[sizeof (_nm_utils_to_string_buffer)];
+	NMPCache *cache = nm_platform_get_cache (platform);
 
-	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));
-	nm_assert (!old || !new || NMP_OBJECT_GET_CLASS (old) == NMP_OBJECT_GET_CLASS (new));
+	ASSERT_nmp_cache_ops (cache, cache_op, obj_old, obj_new);
+	nm_assert (cache_op != NMP_CACHE_OPS_UNCHANGED);
 
-	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)));
+	klass = obj_old ? NMP_OBJECT_GET_CLASS (obj_old) : NMP_OBJECT_GET_CLASS (obj_new);
 
 	_LOGt ("update-cache-%s: %s: %s%s%s",
 	       klass->obj_type_name,
-	       (ops_type == NMP_CACHE_OPS_UPDATED
+	       (cache_op == NMP_CACHE_OPS_UPDATED
 	           ? "UPDATE"
-	           : (ops_type == NMP_CACHE_OPS_REMOVED
+	           : (cache_op == 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))
+	                 : (cache_op == NMP_CACHE_OPS_ADDED) ? "ADD" : "???")),
+	       (cache_op != NMP_CACHE_OPS_ADDED
+	           ? nmp_object_to_string (obj_old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))
+	           : nmp_object_to_string (obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))),
+	       (cache_op == NMP_CACHE_OPS_UPDATED) ? " -> " : "",
+	       (cache_op == NMP_CACHE_OPS_UPDATED
+	           ? nmp_object_to_string (obj_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));
+			if (   obj_old
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (cache, obj_old->link.master, obj_new, obj_old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (obj_old->link.master));
+			if (   obj_new
+			    && (!obj_old || obj_old->link.master != obj_new->link.master)
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (cache, obj_new->link.master, obj_new, obj_old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (obj_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));
+			if (   obj_new /* <-- nonsensical, make coverity happy */
+			    && nmp_cache_link_connected_needs_toggle (cache, obj_new, obj_new, obj_old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (obj_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 (   cache_op == NMP_CACHE_OPS_REMOVED
+			    && obj_old /* <-- nonsensical, make coverity happy */)
+				ifindex = obj_old->link.ifindex;
+			else if (   cache_op == NMP_CACHE_OPS_UPDATED
+			         && obj_old && obj_new /* <-- nonsensical, make coverity happy */
+			         && !obj_new->_link.netlink.is_in_netlink
+			         && obj_new->_link.netlink.is_in_netlink != obj_old->_link.netlink.is_in_netlink)
+				ifindex = obj_new->link.ifindex;
 
 			if (ifindex > 0) {
 				delayed_action_schedule (platform,
@@ -3494,40 +3692,40 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 			 * Currently, kernel misses to sent us a notification in this case
 			 * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */
 
-			if (   ops_type == NMP_CACHE_OPS_REMOVED
-			    && old /* <-- nonsensical, make coverity happy */
-			    && old->_link.netlink.is_in_netlink)
-				ifindex = old->link.ifindex;
-			else if (   ops_type == NMP_CACHE_OPS_UPDATED
-			         && old && new /* <-- nonsensical, make coverity happy */
-			         && old->_link.netlink.is_in_netlink
-			         && !new->_link.netlink.is_in_netlink)
-				ifindex = new->link.ifindex;
+			if (   cache_op == NMP_CACHE_OPS_REMOVED
+			    && obj_old /* <-- nonsensical, make coverity happy */
+			    && obj_old->_link.netlink.is_in_netlink)
+				ifindex = obj_old->link.ifindex;
+			else if (   cache_op == NMP_CACHE_OPS_UPDATED
+			         && obj_old && obj_new /* <-- nonsensical, make coverity happy */
+			         && obj_old->_link.netlink.is_in_netlink
+			         && !obj_new->_link.netlink.is_in_netlink)
+				ifindex = obj_new->link.ifindex;
 
 			if (ifindex > 0) {
-				const NMPlatformLink *const *links;
-
-				links = cache_lookup_all_objects (NMPlatformLink, platform, NMP_OBJECT_TYPE_LINK, FALSE);
-				if (links) {
-					for (; *links; links++) {
-						const NMPlatformLink *l = (*links);
-
-						if (l->parent == ifindex)
-							delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (l->ifindex));
-					}
+				NMPLookup lookup;
+				NMDedupMultiIter iter;
+				const NMPlatformLink *l;
+
+				nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+				nmp_cache_iter_for_each_link (&iter,
+				                              nmp_cache_lookup (cache, &lookup),
+				                              &l) {
+					if (l->parent == ifindex)
+						delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (l->ifindex));
 				}
 			}
 		}
 		{
 			/* 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
-			    && new->_link.netlink.is_in_netlink
-			    && (   (   NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_UP)
-			            && !NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_UP))
-			        || (   NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_LOWER_UP)
-			            && !NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_LOWER_UP)))) {
+			if (   cache_op == NMP_CACHE_OPS_UPDATED
+			    && obj_old && obj_new /* <-- nonsensical, make coverity happy */
+			    && obj_old->_link.netlink.is_in_netlink
+			    && obj_new->_link.netlink.is_in_netlink
+			    && (   (   NM_FLAGS_HAS (obj_old->link.n_ifi_flags, IFF_UP)
+			            && !NM_FLAGS_HAS (obj_new->link.n_ifi_flags, IFF_UP))
+			        || (   NM_FLAGS_HAS (obj_old->link.n_ifi_flags, IFF_LOWER_UP)
+			            && !NM_FLAGS_HAS (obj_new->link.n_ifi_flags, IFF_LOWER_UP)))) {
 				/* FIXME: I suspect that IFF_LOWER_UP must not be considered, and I
 				 * think kernel does send RTM_DELROUTE events for IPv6 routes, so
 				 * we might not need to refresh IPv6 routes. */
@@ -3537,17 +3735,17 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				                         NULL);
 			}
 		}
-		if (   NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)
-		    && (new && new->_link.netlink.is_in_netlink)
-		    && (!old || !old->_link.netlink.is_in_netlink))
+		if (   NM_IN_SET (cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)
+		    && (obj_new && obj_new->_link.netlink.is_in_netlink)
+		    && (!obj_old || !obj_old->_link.netlink.is_in_netlink))
 		{
-			if (!new->_link.netlink.lnk) {
+			if (!obj_new->_link.netlink.lnk) {
 				/* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that
 				 * kernel didn't send this notification, thus when we first learn about a link
 				 * that lacks an lnk_data we re-request it again.
 				 *
 				 * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */
-				switch (new->link.type) {
+				switch (obj_new->link.type) {
 				case NM_LINK_TYPE_GRE:
 				case NM_LINK_TYPE_IP6TNL:
 				case NM_LINK_TYPE_INFINIBAND:
@@ -3558,23 +3756,23 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				case NM_LINK_TYPE_VXLAN:
 					delayed_action_schedule (platform,
 					                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-					                         GINT_TO_POINTER (new->link.ifindex));
+					                         GINT_TO_POINTER (obj_new->link.ifindex));
 					break;
 				default:
 					break;
 				}
 			}
-			if (   new->link.type == NM_LINK_TYPE_VETH
-			    && new->link.parent == 0) {
+			if (   obj_new->link.type == NM_LINK_TYPE_VETH
+			    && obj_new->link.parent == 0) {
 				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
 				 * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827).
 				 * Request it again. */
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-				                         GINT_TO_POINTER (new->link.ifindex));
+				                         GINT_TO_POINTER (obj_new->link.ifindex));
 			}
-			if (   new->link.type == NM_LINK_TYPE_ETHERNET
-			    && new->link.addr.len == 0) {
+			if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
+			    && obj_new->link.addr.len == 0) {
 				/* Due to a kernel bug, we sometimes receive spurious NEWLINK
 				 * messages after a wifi interface has disappeared. Since the
 				 * link is not present anymore we can't determine its type and
@@ -3584,7 +3782,7 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				 */
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-				                         GINT_TO_POINTER (new->link.ifindex));
+				                         GINT_TO_POINTER (obj_new->link.ifindex));
 			}
 		}
 		{
@@ -3592,14 +3790,14 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 			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.n_ifi_flags, IFF_LOWER_UP) : 2)
-			                    != (old && old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_LOWER_UP) : 2);
+			changed_master =    (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0 ? obj_new->link.master : 0)
+			                 != (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0 ? obj_old->link.master : 0);
+			changed_connected =    (obj_new && obj_new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (obj_new->link.n_ifi_flags, IFF_LOWER_UP) : 2)
+			                    != (obj_old && obj_old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (obj_old->link.n_ifi_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;
+				ifindex1 = (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0) ? obj_old->link.master : 0;
+				ifindex2 = (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0) ? obj_new->link.master : 0;
 
 				if (ifindex1 > 0)
 					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex1));
@@ -3607,29 +3805,13 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex2));
 			}
 		}
-		{
-			if (   (       (ops_type == NMP_CACHE_OPS_REMOVED)
-			        || (   (ops_type == NMP_CACHE_OPS_UPDATED)
-			            && new
-			            && !new->_link.netlink.is_in_netlink))
-			    && old
-			    && old->_link.netlink.is_in_netlink
-			    && old->link.master) {
-				/* sometimes we receive a wrong RTM_DELLINK message when unslaving
-				 * a device. Refetch the link again to check whether the device
-				 * is really gone.
-				 *
-				 * https://bugzilla.redhat.com/show_bug.cgi?id=1285719#c2 */
-				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (old->link.ifindex));
-			}
-		}
 		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) {
+			if (cache_op == NMP_CACHE_OPS_REMOVED) {
 				delayed_action_schedule (platform,
 				                         (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS)
 				                             ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
@@ -3643,69 +3825,113 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 	}
 }
 
-static void
-cache_post (NMPlatform *platform,
-            struct nlmsghdr *msghdr,
-            NMPCacheOpsType cache_op,
-            NMPObject *obj,
-            NMPObject *obj_cache)
+/*****************************************************************************/
+
+static guint32
+_nlh_seq_next_get (NMLinuxPlatformPrivate *priv)
+{
+	/* generate a new sequence number, but skip zero. */
+	return priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
+}
+
+/**
+ * _nl_send_nlmsghdr:
+ * @platform:
+ * @nlhdr:
+ * @out_seq_result:
+ * @response_type:
+ * @response_out_data:
+ *
+ * Returns: 0 on success or a negative errno. Beware, it's an errno, not nlerror.
+ */
+static int
+_nl_send_nlmsghdr (NMPlatform *platform,
+                   struct nlmsghdr *nlhdr,
+                   WaitForNlResponseResult *out_seq_result,
+                   DelayedActionWaitForNlResponseType response_type,
+                   gpointer response_out_data)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	guint32 seq;
+	int nle;
 
-	nm_assert (NMP_OBJECT_IS_VALID (obj));
-	nm_assert (!obj_cache || nmp_object_id_equal (obj, obj_cache));
-
-	if (msghdr->nlmsg_type == RTM_NEWROUTE) {
-		DelayedActionType action_type;
-
-		action_type = NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP4_ROUTE
-		                  ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-		                  : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES;
-		if (   !delayed_action_refresh_all_in_progress (platform, action_type)
-		    && nmp_cache_find_other_route_for_same_destination (priv->cache, obj)) {
-			/* via `iproute route change` the user can update an existing route which effectively
-			 * means that a new object (with a different ID) comes into existance, replacing the
-			 * old on. In other words, as the ID of the object changes, we really see a new
-			 * object with the old one deleted.
-			 * However, kernel decides not to send a RTM_DELROUTE event for that.
-			 *
-			 * To hack around that, check if the update leaves us with multiple routes for the
-			 * same network/plen,metric part. In that case, we cannot do better then requesting
-			 * all routes anew, which sucks.
-			 *
-			 * One mitigation to avoid a dump is only to request a new dump, if we are not in
-			 * the middle of an ongoing dump (delayed_action_refresh_all_in_progress). */
-			delayed_action_schedule (platform, action_type, NULL);
+	nm_assert (nlhdr);
+
+	seq = _nlh_seq_next_get (priv);
+	nlhdr->nlmsg_seq = seq;
+
+	{
+		struct sockaddr_nl nladdr = {
+			.nl_family = AF_NETLINK,
+		};
+		struct iovec iov = {
+			.iov_base = nlhdr,
+			.iov_len = nlhdr->nlmsg_len
+		};
+		struct msghdr msg = {
+			.msg_name = &nladdr,
+			.msg_namelen = sizeof(nladdr),
+			.msg_iov = &iov,
+			.msg_iovlen = 1,
+		};
+		int try_count;
+
+		if (!nlhdr->nlmsg_pid)
+			nlhdr->nlmsg_pid = nl_socket_get_local_port (priv->nlh);
+		nlhdr->nlmsg_flags |= (NLM_F_REQUEST | NLM_F_ACK);
+
+		try_count = 0;
+again:
+		nle = sendmsg (nl_socket_get_fd (priv->nlh), &msg, 0);
+		if (nle < 0) {
+			nle = errno;
+			if (nle == EINTR && try_count++ < 100)
+				goto again;
+			_LOGD ("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", g_strerror (nle), nle);
+			return -nle;
 		}
 	}
-}
 
-/*****************************************************************************/
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
+	                                              response_type, response_out_data);
+	return 0;
+}
 
+/**
+ * _nl_send_nlmsg:
+ * @platform:
+ * @nlmsg:
+ * @out_seq_result:
+ * @response_type:
+ * @response_out_data:
+ *
+ * Returns: 0 on success, or a negative libnl3 error code (beware, it's not an errno).
+ */
 static int
-_nl_send_auto_with_seq (NMPlatform *platform,
-                        struct nl_msg *nlmsg,
-                        WaitForNlResponseResult *out_seq_result,
-                        gint *out_refresh_all_in_progess)
+_nl_send_nlmsg (NMPlatform *platform,
+                struct nl_msg *nlmsg,
+                WaitForNlResponseResult *out_seq_result,
+                DelayedActionWaitForNlResponseType response_type,
+                gpointer response_out_data)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	struct nlmsghdr *nlhdr;
 	guint32 seq;
 	int nle;
 
-	/* complete the message with a sequence number (ensuring it's not zero). */
-	seq = priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
-
-	nlmsg_hdr (nlmsg)->nlmsg_seq = seq;
+	nlhdr = nlmsg_hdr (nlmsg);
+	seq = _nlh_seq_next_get (priv);
+	nlhdr->nlmsg_seq = seq;
 
 	nle = nl_send_auto (priv->nlh, nlmsg);
+	if (nle < 0) {
+		_LOGD ("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nl_geterror (nle), nle);
+		return nle;
+	}
 
-	if (nle >= 0) {
-		nle = 0;
-		delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_refresh_all_in_progess);
-	} else
-		_LOGD ("netlink: send: failed sending message: %s (%d)", nl_geterror (nle), nle);
-
-	return nle;
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
+	                                              response_type, response_out_data);
+	return 0;
 }
 
 static void
@@ -3713,17 +3939,23 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	int nle;
 
 	if (name && !name[0])
 		name = NULL;
 
 	g_return_if_fail (ifindex > 0 || name);
 
-	_LOGD ("do-request-link: %d %s", ifindex, name ? name : "");
+	_LOGD ("do-request-link: %d %s", ifindex, name ?: "");
 
 	if (ifindex > 0) {
-		cache_prune_candidates_record_one (platform,
-		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
+		const NMDedupMultiEntry *entry;
+
+		entry = nmp_cache_lookup_entry_link (nm_platform_get_cache (platform), ifindex);
+		if (entry) {
+			priv->pruning[DELAYED_ACTION_IDX_REFRESH_ALL_LINKS] = TRUE;
+			nm_dedup_multi_entry_set_dirty (entry, TRUE);
+		}
 	}
 
 	event_handler_read_netlink (platform, FALSE);
@@ -3734,8 +3966,15 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 	                          name,
 	                          0,
 	                          0);
-	if (nlmsg)
-		_nl_send_auto_with_seq (platform, nlmsg, NULL, NULL);
+	if (nlmsg) {
+		nle = _nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+		if (nle < 0) {
+			_LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
+			       ifindex, name ?: "",
+			       nl_geterror (nle), -nle);
+			return;
+		}
+	}
 }
 
 static void
@@ -3755,7 +3994,9 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
-		cache_prune_candidates_record_all (platform, delayed_action_refresh_to_object_type (iflags));
+		priv->pruning[delayed_action_refresh_all_to_idx (iflags)] = TRUE;
+		nmp_cache_dirty_set_all (nm_platform_get_cache (platform),
+		                         delayed_action_refresh_to_object_type (iflags));
 	}
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
@@ -3795,7 +4036,7 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (nle < 0)
 			continue;
 
-		if (_nl_send_auto_with_seq (platform, nlmsg, NULL, out_refresh_all_in_progess) < 0) {
+		if (_nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
 			nm_assert (*out_refresh_all_in_progess > 0);
 			*out_refresh_all_in_progess -= 1;
 		}
@@ -3825,13 +4066,12 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number)
 		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
 			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
 
-			if (data->seq_number == priv->nlh_seq_last_seen) {
-				if (data->out_refresh_all_in_progess) {
-					nm_assert (*data->out_refresh_all_in_progess > 0);
-					*data->out_refresh_all_in_progess -= 1;
-					data->out_refresh_all_in_progess = NULL;
-					break;
-				}
+			if (   data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
+			    && data->response.out_refresh_all_in_progess
+			    && data->seq_number == priv->nlh_seq_last_seen) {
+				*data->response.out_refresh_all_in_progess -= 1;
+				data->response.out_refresh_all_in_progess = NULL;
+				break;
 			}
 		}
 	}
@@ -3879,18 +4119,19 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu
 static void
 event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_events)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	NMLinuxPlatformPrivate *priv;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	nm_auto_nmpobj NMPObject *obj_cache = NULL;
 	NMPCacheOpsType cache_op;
 	struct nlmsghdr *msghdr;
-	char buf_nlmsg_type[16];
+	char buf_nlmsghdr[400];
 	gboolean id_only = FALSE;
-	gboolean was_visible;
+	NMPCache *cache = nm_platform_get_cache (platform);
+	gboolean is_dump;
 
 	msghdr = nlmsg_hdr (msg);
 
-	if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR)
+	if (   _support_kernel_extended_ifa_flags_still_undecided ()
+	    && msghdr->nlmsg_type == RTM_NEWADDR)
 		_support_kernel_extended_ifa_flags_detect (msg);
 
 	if (!handle_events)
@@ -3902,156 +4143,144 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 		id_only = TRUE;
 	}
 
-	obj = nmp_object_new_from_nl (platform, priv->cache, msg, id_only);
+	obj = nmp_object_new_from_nl (platform, cache, msg, id_only);
 	if (!obj) {
-		_LOGT ("event-notification: %s, seq %u: ignore",
-		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
-		       msghdr->nlmsg_seq);
+		_LOGT ("event-notification: %s: ignore",
+		       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 		return;
 	}
 
-	_LOGT ("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,
-	           id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-
 	switch (msghdr->nlmsg_type) {
-
-	case RTM_NEWLINK:
 	case RTM_NEWADDR:
+	case RTM_NEWLINK:
 	case RTM_NEWROUTE:
-	case RTM_GETLINK:
-		cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
-
-		cache_post (platform, msghdr, cache_op, obj, obj_cache);
-
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
-		break;
-
-	case RTM_DELLINK:
-	case RTM_DELADDR:
-	case RTM_DELROUTE:
-		cache_op = nmp_cache_remove_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		is_dump = delayed_action_refresh_all_in_progress (platform,
+		                                                  delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)));
 		break;
-
 	default:
-		break;
+		is_dump = FALSE;
 	}
 
-	cache_prune_candidates_drop (platform, obj_cache);
-}
-
-/*****************************************************************************/
-
-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;
-}
-
-const NMPlatformObject *const*
-nm_linux_platform_lookup (NMPlatform *platform, const NMPCacheId *cache_id, guint *out_len)
-{
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), NULL);
-	g_return_val_if_fail (cache_id, NULL);
+	_LOGT ("event-notification: %s%s: %s",
+	       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
+	       is_dump ? ", in-dump" : "",
+	       nmp_object_to_string (obj,
+	                             id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
+	                             NULL, 0));
 
-	return nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                               cache_id, out_len);
-}
+	{
+		nm_auto_nmpobj const NMPObject *obj_old = NULL;
+		nm_auto_nmpobj const NMPObject *obj_new = NULL;
+
+		switch (msghdr->nlmsg_type) {
+
+		case RTM_NEWLINK:
+		case RTM_NEWADDR:
+		case RTM_GETLINK:
+			cache_op = nmp_cache_update_netlink (cache, obj, is_dump, &obj_old, &obj_new);
+			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+				cache_on_change (platform, cache_op, obj_old, obj_new);
+				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+			}
+			break;
 
-static GArray *
-link_get_all (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
+		case RTM_NEWROUTE: {
+			nm_auto_nmpobj const NMPObject *obj_replace = NULL;
+			gboolean resync_required = FALSE;
+			gboolean only_dirty = FALSE;
+
+			if (obj->ip_route.rt_cloned) {
+				/* a cloned route might be a response for RTM_GETROUTE. Check, whether it is. */
+				nm_assert (!nmp_object_is_alive (obj));
+				priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+				if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+					guint i;
+
+					nm_assert (priv->delayed_action.list_wait_for_nl_response->len > 0);
+					for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
+						DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+						if (   data->response_type == DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET
+						    && data->response.out_route_get) {
+							nm_assert (!*data->response.out_route_get);
+							if (data->seq_number == nlmsg_hdr (msg)->nlmsg_seq) {
+								*data->response.out_route_get = nmp_object_clone (obj, FALSE);
+								data->response.out_route_get = NULL;
+								break;
+							}
+						}
+					}
+				}
+			}
 
-	return nmp_cache_lookup_multi_to_array (priv->cache,
-	                                        NMP_OBJECT_TYPE_LINK,
-	                                        nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, TRUE));
-}
+			cache_op = nmp_cache_update_netlink_route (cache,
+			                                           obj,
+			                                           is_dump,
+			                                           msghdr->nlmsg_flags,
+			                                           &obj_old,
+			                                           &obj_new,
+			                                           &obj_replace,
+			                                           &resync_required);
+			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+				if (obj_replace) {
+					const NMDedupMultiEntry *entry_replace;
+
+					/* we found an object that is to be replaced by the RTM_NEWROUTE message.
+					 * While we invoke the signal, the platform cache might change and invalidate
+					 * the findings. Mitigate that (for the most part), by marking the entry as
+					 * dirty and only delete @obj_replace if it is still dirty afterwards.
+					 *
+					 * Yes, there is a tiny tiny chance for still getting it wrong. But in practice,
+					 * the signal handlers do not cause to call the platform again, so the cache
+					 * is not really changing. -- if they would, it would anyway be dangerous to overflow
+					 * the stack and it's not ensured that the processing of netlink messages is
+					 * reentrant (maybe it is).
+					 */
+					entry_replace = nmp_cache_lookup_entry (cache, obj_replace);
+					nm_assert (entry_replace && entry_replace->obj == obj_replace);
+					nm_dedup_multi_entry_set_dirty (entry_replace, TRUE);
+					only_dirty = TRUE;
+				}
+				cache_on_change (platform, cache_op, obj_old, obj_new);
+				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+			}
 
-static const NMPlatformLink *
-_nm_platform_link_get (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj;
+			if (obj_replace) {
+				/* the RTM_NEWROUTE message indicates that another route was replaced.
+				 * Remove it now. */
+				cache_op = nmp_cache_remove (cache, obj_replace, TRUE, only_dirty, NULL);
+				if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+					nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+					cache_on_change (platform, cache_op, obj_replace, NULL);
+					nm_platform_cache_update_emit_signal (platform, cache_op, obj_replace, NULL);
+				}
+			}
 
-	obj = cache_lookup_link (platform, ifindex);
-	return obj ? &obj->link : NULL;
-}
+			if (resync_required) {
+				/* we'd like to avoid such resyncs as they are expensive and we should only rely on the
+				 * netlink events. This needs investigation. */
+				_LOGT ("schedule resync of routes after RTM_NEWROUTE");
+				delayed_action_schedule (platform,
+				                         delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)),
+				                         NULL);
+			}
+			break;
+		}
 
-static const NMPlatformLink *
-_nm_platform_link_get_by_ifname (NMPlatform *platform,
-                                 const char *ifname)
-{
-	const NMPObject *obj = NULL;
+		case RTM_DELLINK:
+		case RTM_DELADDR:
+		case RTM_DELROUTE:
+			cache_op = nmp_cache_remove_netlink (cache, obj, &obj_old, &obj_new);
+			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+				cache_on_change (platform, cache_op, obj_old, obj_new);
+				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+			}
+			break;
 
-	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);
+		default:
+			break;
+		}
 	}
-	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 const NMPObject *
-link_get_lnk (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
-{
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
-
-	if (!obj)
-		return NULL;
-
-	NM_SET_OUT (out_link, &obj->link);
-
-	if (!obj->_link.netlink.lnk)
-		return NULL;
-	if (   link_type != NM_LINK_TYPE_NONE
-	    && (   link_type != obj->link.type
-	        || link_type != NMP_OBJECT_GET_CLASS (obj->_link.netlink.lnk)->lnk_link_type))
-		return NULL;
-
-	return obj->_link.netlink.lnk;
 }
 
 /*****************************************************************************/
@@ -4063,34 +4292,21 @@ do_add_link_with_lookup (NMPlatform *platform,
                          struct nl_msg *nlmsg,
                          const NMPlatformLink **out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPObject *obj = NULL;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
+	NMPCache *cache = nm_platform_get_cache (platform);
 
 	event_handler_read_netlink (platform, FALSE);
 
-	if (nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
-		/* hm, a link with such a name already exists. Try reloading first. */
-		do_request_link (platform, 0, name);
-
-		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
-		if (obj) {
-			_LOGE ("do-add-link[%s/%s]: link already exists: %s",
-			       name,
-			       nm_link_type_to_string (link_type),
-			       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-			return FALSE;
-		}
-	}
-
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
 		       name,
 		       nm_link_type_to_string (link_type),
 		       nl_geterror (nle), -nle);
+		NM_SET_OUT (out_link, NULL);
 		return FALSE;
 	}
 
@@ -4100,35 +4316,29 @@ do_add_link_with_lookup (NMPlatform *platform,
 
 	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
 	            ? LOGL_DEBUG
-	            : LOGL_ERR,
+	            : LOGL_WARN,
 	        "do-add-link[%s/%s]: %s",
 	        name,
 	        nm_link_type_to_string (link_type),
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
-	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL);
-
-	if (!obj) {
-		/* either kernel signaled failure, or it signaled success and the link object
-		 * is not (yet) in the cache. Try to reload it... */
-		do_request_link (platform, 0, name);
-		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL);
+	if (out_link) {
+		obj = nmp_cache_lookup_link_full (cache, 0, name, FALSE, link_type, NULL, NULL);
+		*out_link = NMP_OBJECT_CAST_LINK (obj);
 	}
 
-	if (out_link)
-		*out_link = obj ? &obj->link : NULL;
-	return !!obj;
+	return seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 }
 
-static gboolean
-do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
+static NMPlatformError
+do_add_addrroute (NMPlatform *platform,
+                  const NMPObject *obj_id,
+                  struct nl_msg *nlmsg,
+                  gboolean suppress_netlink_failure)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
-	const NMPObject *obj;
 
 	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
 	                      NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS,
@@ -4136,72 +4346,69 @@ do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-%s[%s]: failure sending netlink request \"%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);
-		return FALSE;
+		return NM_PLATFORM_ERROR_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
 
 	nm_assert (seq_result);
 
-	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	_NMLOG ((   seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	         || (   suppress_netlink_failure
+	             && seq_result < 0))
 	            ? LOGL_DEBUG
-	            : LOGL_ERR,
+	            : LOGL_WARN,
 	        "do-add-%s[%s]: %s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
-	/* In rare cases, the object is not yet ready as we received the ACK from
-	 * kernel. Need to refetch.
-	 *
-	 * We want to safe the expensive refetch, thus we look first into the cache
-	 * whether the object exists.
-	 *
-	 * FIXME: if the object already existed previously, we might not notice a
-	 * missing update. It's not clear how to fix that reliably without refechting
-	 * all the time. */
-	obj = nmp_cache_lookup_obj (priv->cache, obj_id);
-	if (!obj) {
-		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
-		obj = nmp_cache_lookup_obj (priv->cache, obj_id);
+	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
+		/* In rare cases, the object is not yet ready as we received the ACK from
+		 * kernel. Need to refetch.
+		 *
+		 * We want to safe the expensive refetch, thus we look first into the cache
+		 * whether the object exists.
+		 *
+		 * rh#1484434 */
+		if (!nmp_cache_lookup_obj (nm_platform_get_cache (platform), obj_id))
+			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
 	}
 
-	/* Adding is only successful, if kernel reported success *and* we have the
-	 * expected object in cache afterwards. */
-	return obj && seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+	return wait_for_nl_response_to_plerr (seq_result);
 }
 
 static gboolean
 do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
-	gboolean success = TRUE;
+	gboolean success;
 	const char *log_detail = "";
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-delete-%s[%s]: failure sending netlink request \"%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);
-		goto out;
+		return FALSE;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
 
 	nm_assert (seq_result);
 
+	success = TRUE;
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
 		/* ok */
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT))
@@ -4216,42 +4423,60 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	else
 		success = FALSE;
 
-	_NMLOG (success ? LOGL_DEBUG : LOGL_ERR,
+	_NMLOG (success ? LOGL_DEBUG : LOGL_WARN,
 	        "do-delete-%s[%s]: %s%s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
 
-out:
-	if (!nmp_cache_lookup_obj (priv->cache, obj_id))
-		return TRUE;
+	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
+		/* In rare cases, the object is still there after we receive the ACK from
+		 * kernel. Need to refetch.
+		 *
+		 * We want to safe the expensive refetch, thus we look first into the cache
+		 * whether the object exists.
+		 *
+		 * rh#1484434 */
+		if (nmp_cache_lookup_obj (nm_platform_get_cache (platform), obj_id))
+			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
+	}
 
-	/* such an object still exists in the cache. To be sure, refetch it (and
-	 * hope it's gone) */
-	do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
-	return !nmp_cache_lookup_obj (priv->cache, obj_id);
+	return success;
 }
 
-static WaitForNlResponseResult
-do_change_link_request (NMPlatform *platform,
-                        int ifindex,
-                        struct nl_msg *nlmsg)
+static NMPlatformError
+do_change_link (NMPlatform *platform,
+                ChangeLinkType change_link_type,
+                int ifindex,
+                struct nl_msg *nlmsg,
+                const ChangeLinkData *data)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
-	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	char s_buf[256];
+	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
+	NMLogLevel log_level = LOGL_DEBUG;
+	const char *log_result = "failure";
+	const char *log_detail = "";
+	gs_free char *log_detail_free = NULL;
+	const NMPObject *obj_cache;
 
-	if (!nm_platform_netns_push (platform, &netns))
-		return WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	if (!nm_platform_netns_push (platform, &netns)) {
+		log_level = LOGL_ERR;
+		log_detail = ", failure to change network namespace";
+		goto out;
+	}
 
 retry:
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
-		_LOGE ("do-change-link[%d]: failure sending netlink request \"%s\" (%d)",
-		       ifindex,
-		       nl_geterror (nle), -nle);
-		return WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+		log_level = LOGL_ERR;
+		log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)",
+		                                   nl_geterror (nle), -nle);
+		log_detail = log_detail_free;
+		goto out;
 	}
 
 	/* always refetch the link after changing it. There seems to be issues
@@ -4267,18 +4492,6 @@ retry:
 		nlmsg_hdr (nlmsg)->nlmsg_type = RTM_SETLINK;
 		goto retry;
 	}
-	return seq_result;
-}
-
-static NMPlatformError
-do_change_link_result (NMPlatform *platform,
-                       int ifindex,
-                       WaitForNlResponseResult seq_result)
-{
-	char s_buf[256];
-	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
-	NMLogLevel log_level = LOGL_DEBUG;
-	const char *log_result = "failure", *log_detail = "";
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
 		log_result = "success";
@@ -4287,38 +4500,43 @@ do_change_link_result (NMPlatform *platform,
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
 		log_detail = ", firmware not found";
 		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+	} else if (   NM_IN_SET (-((int) seq_result), ERANGE)
+	           && change_link_type == CHANGE_LINK_TYPE_SET_MTU) {
+		log_detail = ", setting MTU to requested size is not possible";
+		result = NM_PLATFORM_ERROR_CANT_SET_MTU;
+	} else if (   NM_IN_SET (-((int) seq_result), ENFILE)
+	           && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS
+	           && (obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex))
+	           && obj_cache->link.addr.len == data->set_address.length
+	           && memcmp (obj_cache->link.addr.data, data->set_address.address, data->set_address.length) == 0) {
+		/* workaround ENFILE which may be wrongly returned (bgo #770456).
+		 * If the MAC address is as expected, assume success? */
+		log_result = "success";
+		log_detail = " (assume success changing address)";
+		result = NM_PLATFORM_ERROR_SUCCESS;
 	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
 		log_level = LOGL_DEBUG;
 		result = NM_PLATFORM_ERROR_NOT_FOUND;
 	} else {
-		log_level = LOGL_ERR;
+		log_level = LOGL_WARN;
 		result = NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
+
+out:
 	_NMLOG (log_level,
 	        "do-change-link[%d]: %s changing link: %s%s",
 	        ifindex,
 	        log_result,
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
-
 	return result;
 }
 
-static NMPlatformError
-do_change_link (NMPlatform *platform,
-                int ifindex,
-                struct nl_msg *nlmsg)
-{
-	WaitForNlResponseResult seq_result;
-
-	seq_result = do_change_link_request (platform, ifindex, nlmsg);
-	return do_change_link_result (platform, ifindex, seq_result);
-}
-
 static gboolean
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
+          const char *veth_peer,
           const void *address,
           size_t address_len,
           const NMPlatformLink **out_link)
@@ -4337,9 +4555,6 @@ link_add (NMPlatform *platform,
 			(void) nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
-	_LOGD ("link: add link '%s' of type '%s' (%d)",
-	       name, nm_link_type_to_string (type), (int) type);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -4352,7 +4567,7 @@ link_add (NMPlatform *platform,
 	if (address && address_len)
 		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
-	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type))
+	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, veth_peer))
 		return FALSE;
 
 	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
@@ -4364,11 +4579,10 @@ static gboolean
 link_delete (NMPlatform *platform, int ifindex)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	NMPObject obj_id;
 	const NMPObject *obj;
 
-	obj = nmp_cache_lookup_link (priv->cache, ifindex);
+	obj = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
 	if (!obj || !obj->_link.netlink.is_in_netlink)
 		return FALSE;
 
@@ -4383,56 +4597,11 @@ link_delete (NMPlatform *platform, int ifindex)
 	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
-static const char *
-link_get_type_name (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
-
-	if (!obj)
-		return NULL;
-
-	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 obj->link.kind ?: "unknown";
-}
-
-static gboolean
-link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPObject *link;
-	struct udev_device *udevice = NULL;
-	const char *uproperty;
-
-	link = nmp_cache_lookup_link (priv->cache, ifindex);
-	if (!link)
-		return FALSE;
-
-	udevice = link->_link.udev.device;
-	if (!udevice)
-		return FALSE;
-
-	uproperty = udev_device_get_property_value (udevice, "NM_UNMANAGED");
-	if (!uproperty)
-		return FALSE;
-
-	*unmanaged = nm_udev_utils_property_as_boolean (uproperty);
-	return TRUE;
-}
-
 static gboolean
 link_refresh (NMPlatform *platform, int ifindex)
 {
 	do_request_link (platform, ifindex, NULL);
-	return !!cache_lookup_link (platform, ifindex);
+	return !!nm_platform_link_get_obj (platform, ifindex, TRUE);
 }
 
 static gboolean
@@ -4454,7 +4623,7 @@ link_set_netns (NMPlatform *platform,
 		return FALSE;
 
 	NLA_PUT (nlmsg, IFLA_NET_NS_FD, 4, &netns_fd);
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
@@ -4484,7 +4653,7 @@ link_change_flags (NMPlatform *platform,
 	                          flags_set);
 	if (!nlmsg)
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return do_change_link (platform, ifindex, nlmsg);
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
 
 static gboolean
@@ -4519,7 +4688,7 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 static const char *
 link_get_udi (NMPlatform *platform, int ifindex)
 {
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
+	const NMPObject *obj = nm_platform_link_get_obj (platform, ifindex, TRUE);
 
 	if (   !obj
 	    || !obj->_link.netlink.is_in_netlink
@@ -4528,35 +4697,21 @@ link_get_udi (NMPlatform *platform, int ifindex)
 	return udev_device_get_syspath (obj->_link.udev.device);
 }
 
-static struct udev_device *
-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 ? obj_cache->_link.udev.device : NULL;
-}
-
 static NMPlatformError
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64;
 
+	_LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
+	       ifindex,
+	       nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0));
+
 	if (!_support_user_ipv6ll_get ()) {
 		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
 		return NM_PLATFORM_ERROR_OPNOTSUPP;
 	}
 
-	_LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
-	       ifindex,
-	       nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0));
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -4567,7 +4722,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	    || !_nl_msg_new_link_set_afspec (nlmsg, mode, NULL))
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
-	return do_change_link (platform, ifindex, nlmsg);
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
 
 static gboolean
@@ -4582,7 +4737,7 @@ link_set_token (NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
 	if (!nlmsg || !_nl_msg_new_link_set_afspec (nlmsg, -1, &iid))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -4606,7 +4761,7 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	const NMPObject *obj;
 
-	obj = cache_lookup_link (platform, ifindex);
+	obj = nm_platform_link_get_obj (platform, ifindex, TRUE);
 
 	/* Only ARPHRD_ETHER links can possibly support VLANs. */
 	if (!obj || obj->link.arptype != ARPHRD_ETHER)
@@ -4647,8 +4802,12 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	gs_free char *mac = NULL;
-	WaitForNlResponseResult seq_result;
-	char s_buf[256];
+	const ChangeLinkData d = {
+		.set_address = {
+			.address = address,
+			.length = length,
+		},
+	};
 
 	if (!address || !length)
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
@@ -4668,32 +4827,32 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 
 	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
 
-	seq_result = do_change_link_request (platform, ifindex, nlmsg);
+	return do_change_link (platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d);
+nla_put_failure:
+	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+}
 
-	if (NM_IN_SET (-((int) seq_result), ENFILE)) {
-		const NMPObject *obj_cache;
+static NMPlatformError
+link_set_name (NMPlatform *platform, int ifindex, const char *name)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-		/* workaround ENFILE which may be wrongly returned (bgo #770456).
-		 * If the MAC address is as expected, assume success? */
+	_LOGD ("link: change %d: name: %s", ifindex, name);
 
-		obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
-		if (   obj_cache
-		    && obj_cache->link.addr.len == length
-		    && memcmp (obj_cache->link.addr.data, address, length) == 0) {
-			_NMLOG (LOGL_DEBUG,
-			        "do-change-link[%d]: %s changing link: %s%s",
-			        ifindex,
-			        "success",
-			        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
-			        " (assume success changing address)");
-			return NM_PLATFORM_ERROR_SUCCESS;
-		}
-	}
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
 
-	return do_change_link_result (platform, ifindex, seq_result);
+	NLA_PUT (nlmsg, IFLA_IFNAME, strlen (name) + 1, name);
 
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 nla_put_failure:
-	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -4710,7 +4869,7 @@ link_get_permanent_address (NMPlatform *platform,
 	return nmp_utils_ethtool_get_permanent_address (ifindex, buf, length);
 }
 
-static gboolean
+static NMPlatformError
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -4728,7 +4887,7 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 
 	NLA_PUT_U32 (nlmsg, IFLA_MTU, mtu);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_SET_MTU, ifindex, nlmsg, NULL);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5359,7 +5518,6 @@ link_vlan_change (NMPlatform *platform,
                   const NMVlanQosMapping *egress_map,
                   gsize n_egress_map)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPObject *obj_cache;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	const NMPObjectLnkVlan *lnk;
@@ -5371,7 +5529,7 @@ link_vlan_change (NMPlatform *platform,
 	char s_ingress[256];
 	char s_egress[256];
 
-	obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
+	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
 	if (   !obj_cache
 	    || !obj_cache->_link.netlink.is_in_netlink) {
 		_LOGD ("link: change %d: %s: link does not exist", ifindex, "vlan");
@@ -5437,7 +5595,7 @@ link_vlan_change (NMPlatform *platform,
 	                                            new_n_egress_map))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static int
@@ -5449,9 +5607,6 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 	struct ifreq ifr = { };
 	int fd;
 
-	_LOGD ("link: add %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
-	       tap ? "tap" : "tun", name, owner, group);
-
 	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
 	if (fd < 0)
 		return FALSE;
@@ -5467,30 +5622,30 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 		ifr.ifr_flags |= NM_IFF_MULTI_QUEUE;
 
 	if (ioctl (fd, TUNSETIFF, &ifr)) {
-		close (fd);
+		nm_close (fd);
 		return FALSE;
 	}
 
 	if (owner >= 0 && owner < G_MAXINT32) {
 		if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) {
-			close (fd);
+			nm_close (fd);
 			return FALSE;
 		}
 	}
 
 	if (group >= 0 && group < G_MAXINT32) {
 		if (ioctl (fd, TUNSETGROUP, (gid_t) group)) {
-			close (fd);
+			nm_close (fd);
 			return FALSE;
 		}
 	}
 
 	if (ioctl (fd, TUNSETPERSIST, 1)) {
-		close (fd);
+		nm_close (fd);
 		return FALSE;
 	}
 	do_request_link (platform, 0, name);
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
 	                                  0, name, FALSE,
 	                                  tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN,
 	                                  NULL, NULL);
@@ -5520,7 +5675,7 @@ link_enslave (NMPlatform *platform, int master, int slave)
 
 	NLA_PUT_U32 (nlmsg, IFLA_MASTER, master);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5540,7 +5695,6 @@ _infiniband_partition_action (NMPlatform *platform,
                               int p_key,
                               const NMPlatformLink **out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	nm_auto_close int dirfd = -1;
 	char ifname_parent[IFNAMSIZ];
 	const NMPObject *obj;
@@ -5576,7 +5730,7 @@ _infiniband_partition_action (NMPlatform *platform,
 	if (action == INFINIBAND_ACTION_DELETE_CHILD)
 		return TRUE;
 
-	obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE,
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform), 0, name, FALSE,
 	                                  NM_LINK_TYPE_INFINIBAND, NULL, NULL);
 	if (out_link)
 		*out_link = obj ? &obj->link : NULL;
@@ -5714,16 +5868,15 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru
 static gboolean
 link_can_assume (NMPlatform *platform, int ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-	const NMPlatformObject *const *objs;
-	guint i, len;
-	const NMPObject *link;
+	NMPLookup lookup;
+	const NMPObject *link, *o;
+	NMDedupMultiIter iter;
+	NMPCache *cache = nm_platform_get_cache (platform);
 
 	if (ifindex <= 0)
 		return FALSE;
 
-	link = cache_lookup_link (platform, ifindex);
+	link = nm_platform_link_get_obj (platform, ifindex, TRUE);
 	if (!link)
 		return FALSE;
 
@@ -5733,21 +5886,21 @@ link_can_assume (NMPlatform *platform, int ifindex)
 	if (link->link.master > 0)
 		return TRUE;
 
-	if (nmp_cache_lookup_multi (priv->cache,
-	                            nmp_cache_id_init_addrroute_visible_by_ifindex (&cache_id, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex),
-	                            NULL))
+	nmp_lookup_init_addrroute (&lookup,
+	                           NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                           ifindex);
+	if (nmp_cache_lookup (cache, &lookup))
 		return TRUE;
 
-	objs = nmp_cache_lookup_multi (priv->cache,
-	                               nmp_cache_id_init_addrroute_visible_by_ifindex (&cache_id, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex),
-	                               &len);
-	if (objs) {
-		for (i = 0; i < len; i++) {
-			const NMPlatformIP6Address *a = (NMPlatformIP6Address *) objs[i];
-
-			if (!IN6_IS_ADDR_LINKLOCAL (&a->address))
-				return TRUE;
-		}
+	nmp_lookup_init_addrroute (&lookup,
+	                           NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                           ifindex);
+	nmp_cache_iter_for_each (&iter,
+	                         nmp_cache_lookup (cache, &lookup),
+	                         &o) {
+		nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_TYPE_IP6_ADDRESS);
+		if (!IN6_IS_ADDR_LINKLOCAL (&o->ip6_address.address))
+			return TRUE;
 	}
 	return FALSE;
 }
@@ -5822,33 +5975,6 @@ link_get_driver_info (NMPlatform *platform,
 
 /*****************************************************************************/
 
-static GArray *
-ipx_address_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-
-	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
-
-	return nmp_cache_lookup_multi_to_array (priv->cache,
-	                                        obj_type,
-	                                        nmp_cache_id_init_addrroute_visible_by_ifindex (&cache_id,
-	                                                                                        obj_type,
-	                                                                                        ifindex));
-}
-
-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)
-{
-	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
-}
-
 static gboolean
 ip4_address_add (NMPlatform *platform,
                  int ifindex,
@@ -5877,7 +6003,7 @@ ip4_address_add (NMPlatform *platform,
 	                             label);
 
 	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
+	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -5906,8 +6032,8 @@ ip6_address_add (NMPlatform *platform,
 	                             preferred,
 	                             NULL);
 
-	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
+	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -5956,303 +6082,139 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, gui
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
 
-	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
 	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
-static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
-
-	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_address);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip4_address;
-	return NULL;
-}
-
-static const NMPlatformIP6Address *
-ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
-
-	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip6_address;
-	return NULL;
-}
-
 /*****************************************************************************/
 
-static GArray *
-ipx_route_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type, NMPlatformGetRouteFlags flags)
+static NMPlatformError
+ip_route_add (NMPlatform *platform,
+              NMPNlmFlags flags,
+              int addr_family,
+              const NMPlatformIPRoute *route)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-	const NMPlatformIPRoute *const* routes;
-	GArray *array;
-	const NMPClass *klass;
-	gboolean with_rtprot_kernel;
-	guint i, len;
-
-	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
-
-	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);
-
-	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);
-
-	routes = (const NMPlatformIPRoute *const*) nmp_cache_lookup_multi (priv->cache, &cache_id, &len);
-
-	array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
-
-	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);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj;
 
-		if (   with_rtprot_kernel
-		    || routes[i]->rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
-			g_array_append_vals (array, routes[i], 1);
+	switch (addr_family) {
+	case AF_INET:
+		nmp_object_stackinit (&obj, NMP_OBJECT_TYPE_IP4_ROUTE, (const NMPlatformObject *) route);
+		break;
+	case AF_INET6:
+		nmp_object_stackinit (&obj, NMP_OBJECT_TYPE_IP6_ROUTE, (const NMPlatformObject *) route);
+		break;
+	default:
+		nm_assert_not_reached ();
 	}
-	return array;
-}
 
-static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ROUTE, flags);
-}
+	nm_platform_ip_route_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (&obj));
 
-static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags);
-}
-
-static guint32
-ip_route_get_lock_flag (NMPlatformIPRoute *route)
-{
-	return   (((guint32) route->lock_window) << RTAX_WINDOW)
-	       | (((guint32) route->lock_cwnd) << RTAX_CWND)
-	       | (((guint32) route->lock_initcwnd) << RTAX_INITCWND)
-	       | (((guint32) route->lock_initrwnd) << RTAX_INITRWND)
-	       | (((guint32) route->lock_mtu) << RTAX_MTU);
-}
-
-static gboolean
-ip4_route_add (NMPlatform *platform, const NMPlatformIP4Route *route)
-{
-	NMPObject obj_id;
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	in_addr_t network;
-
-	network = nm_utils_ip4_address_clear_host_address (route->network, route->plen);
-
-	/* FIXME: take the scope from route into account */
-	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
-	                           NLM_F_CREATE | NLM_F_REPLACE,
-	                           AF_INET,
-	                           route->ifindex,
-	                           route->rt_source,
-	                           route->gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK,
-	                           &network,
-	                           route->plen,
-	                           &route->gateway,
-	                           route->metric,
-	                           route->mss,
-	                           route->pref_src ? &route->pref_src : NULL,
-	                           NULL,
-	                           0,
-	                           route->tos,
-	                           route->window,
-	                           route->cwnd,
-	                           route->initcwnd,
-	                           route->initrwnd,
-	                           route->mtu,
-	                           ip_route_get_lock_flag ((NMPlatformIPRoute *) route));
-
-	nmp_object_stackinit_id_ip4_route (&obj_id, route->ifindex, network, route->plen, route->metric);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
+	nlmsg = _nl_msg_new_route (RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj);
+	if (!nlmsg)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+	return do_add_addrroute (platform,
+	                         &obj,
+	                         nlmsg,
+	                         NM_FLAGS_HAS (flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE));
 }
 
 static gboolean
-ip6_route_add (NMPlatform *platform, const NMPlatformIP6Route *route)
+ip_route_delete (NMPlatform *platform,
+                 const NMPObject *obj)
 {
-	NMPObject obj_id;
+	nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct in6_addr network;
-
-	nm_utils_ip6_address_clear_host_address (&network, &route->network, route->plen);
-
-	/* FIXME: take the scope from route into account */
-	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
-	                           NLM_F_CREATE | NLM_F_REPLACE,
-	                           AF_INET6,
-	                           route->ifindex,
-	                           route->rt_source,
-	                           IN6_IS_ADDR_UNSPECIFIED (&route->gateway) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
-	                           &network,
-	                           route->plen,
-	                           &route->gateway,
-	                           route->metric,
-	                           route->mss,
-	                           !IN6_IS_ADDR_UNSPECIFIED (&route->pref_src) ? &route->pref_src : NULL,
-	                           !IN6_IS_ADDR_UNSPECIFIED (&route->src) ? &route->src : NULL,
-	                           route->src_plen,
-	                           route->tos,
-	                           route->window,
-	                           route->cwnd,
-	                           route->initcwnd,
-	                           route->initrwnd,
-	                           route->mtu,
-	                           ip_route_get_lock_flag ((NMPlatformIPRoute *) route));
-
-	nmp_object_stackinit_id_ip6_route (&obj_id, route->ifindex, &network, route->plen, route->metric);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
-}
 
-static gboolean
-ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	NMPObject obj_id;
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                 NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	network = nm_utils_ip4_address_clear_host_address (network, plen);
+	if (!NMP_OBJECT_IS_STACKINIT (obj))
+		obj_keep_alive = nmp_object_ref (obj);
 
-	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
+	nlmsg = _nl_msg_new_route (RTM_DELROUTE, 0, obj);
+	if (!nlmsg)
+		g_return_val_if_reached (FALSE);
+	return do_delete_object (platform, obj, nlmsg);
+}
 
-	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, 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);
-
-		if (!nmp_cache_lookup_obj (priv->cache, &obj_id)) {
-			/* 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);
+/*****************************************************************************/
 
-			if (!nmp_cache_lookup_obj (priv->cache, &obj_id))
-				return TRUE;
-		}
-	}
+static NMPlatformError
+ip_route_get (NMPlatform *platform,
+              int addr_family,
+              gconstpointer address,
+              int oif_ifindex,
+              NMPObject **out_route)
+{
+	const gboolean is_v4 = (addr_family == AF_INET);
+	const int addr_len = is_v4 ? 4 : 16;
+	int try_count = 0;
+	WaitForNlResponseResult seq_result;
+	int nle;
+	nm_auto_nlmsg NMPObject *route = NULL;
 
-	nlmsg = _nl_msg_new_route (RTM_DELROUTE,
-	                           0,
-	                           AF_INET,
-	                           ifindex,
-	                           NM_IP_CONFIG_SOURCE_UNKNOWN,
-	                           RT_SCOPE_NOWHERE,
-	                           &network,
-	                           plen,
-	                           NULL,
-	                           metric,
-	                           0,
-	                           NULL,
-	                           NULL,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0);
-	if (!nlmsg)
-		return FALSE;
+	nm_assert (NM_IS_LINUX_PLATFORM (platform));
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert (address);
+
+	do {
+		struct {
+			struct nlmsghdr n;
+			struct rtmsg r;
+			char buf[64];
+		} req = {
+			.n.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg)),
+			.n.nlmsg_flags = NLM_F_REQUEST,
+			.n.nlmsg_type = RTM_GETROUTE,
+			.r.rtm_family = addr_family,
+			.r.rtm_tos = 0,
+			.r.rtm_dst_len = is_v4 ? 32 : 128,
+			.r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */,
+		};
 
-	return do_delete_object (platform, &obj_id, nlmsg);
-}
+		g_clear_pointer (&route, nmp_object_unref);
 
-static gboolean
-ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	NMPObject obj_id;
+		if (!_nl_addattr_l (&req.n, sizeof (req), RTA_DST, address, addr_len))
+			nm_assert_not_reached ();
 
-	metric = nm_utils_ip6_route_metric_normalize (metric);
-
-	nm_utils_ip6_address_clear_host_address (&network, &network, plen);
-
-	nlmsg = _nl_msg_new_route (RTM_DELROUTE,
-	                           0,
-	                           AF_INET6,
-	                           ifindex,
-	                           NM_IP_CONFIG_SOURCE_UNKNOWN,
-	                           RT_SCOPE_NOWHERE,
-	                           &network,
-	                           plen,
-	                           NULL,
-	                           metric,
-	                           0,
-	                           NULL,
-	                           NULL,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0);
-	if (!nlmsg)
-		return FALSE;
+		if (oif_ifindex > 0) {
+			gint32 ii = oif_ifindex;
 
-	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
+			if (!_nl_addattr_l (&req.n, sizeof (req), RTA_OIF, &ii, sizeof (ii)))
+				nm_assert_not_reached ();
+		}
 
-	return do_delete_object (platform, &obj_id, nlmsg);
-}
+		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
+		if (nle < 0) {
+			_LOGE ("get-route: failure sending netlink request \"%s\" (%d)",
+			       g_strerror (-nle), -nle);
+			return NM_PLATFORM_ERROR_UNSPECIFIED;
+		}
 
-static const NMPlatformIP4Route *
-ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
+		delayed_action_handle_all (platform, FALSE);
 
-	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip4_route;
-	return NULL;
-}
+		/* Retry, if we failed due to a cache resync. That can happen when the netlink
+		 * socket fills up and we lost the response. */
+	} while (   seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+	         && ++try_count < 10);
 
-static const NMPlatformIP6Route *
-ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
+	if (seq_result < 0) {
+		/* negative seq_result is an errno from kernel. Map it to negative
+		 * NMPlatformError (which are also errno). */
+		return (NMPlatformError) seq_result;
+	}
 
-	metric = nm_utils_ip6_route_metric_normalize (metric);
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+		if (route) {
+			NM_SET_OUT (out_route, g_steal_pointer (&route));
+			return NM_PLATFORM_ERROR_SUCCESS;
+		}
+		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
+	}
 
-	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip6_route;
-	return NULL;
+	return NM_PLATFORM_ERROR_UNSPECIFIED;
 }
 
 /*****************************************************************************/
@@ -6351,6 +6313,7 @@ continue_reading:
 		gboolean abort_parsing = FALSE;
 		gboolean process_valid_msg = FALSE;
 		guint32 seq_number;
+		char buf_nlmsghdr[400];
 
 		msg = nlmsg_convert (hdr);
 		if (!msg) {
@@ -6370,8 +6333,8 @@ continue_reading:
 			goto stop;
 		}
 
-		_LOGt ("netlink: recvmsg: new message type %d, seq %u",
-		       hdr->nlmsg_type, hdr->nlmsg_seq);
+		_LOGt ("netlink: recvmsg: new message %s",
+		       _nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 
 		if (creds)
 			nlmsg_set_creds (msg, creds);
@@ -6512,7 +6475,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 
 			nle = event_handler_recvmsgs (platform, TRUE);
 
-			if (nle < 0)
+			if (nle < 0) {
 				switch (nle) {
 				case -NLE_AGAIN:
 					goto after_read;
@@ -6543,6 +6506,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 				default:
 					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 					break;
+				}
 			}
 			any = TRUE;
 		}
@@ -6615,19 +6579,19 @@ cache_update_link_udev (NMPlatform *platform,
                         int ifindex,
                         struct udev_device *udevice)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	nm_auto_nmpobj NMPObject *obj_cache = NULL;
-	gboolean was_visible;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
 	NMPCacheOpsType cache_op;
 
-	cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udevice, &obj_cache, &was_visible, cache_pre_hook, platform);
+	cache_op = nmp_cache_update_link_udev (nm_platform_get_cache (platform), ifindex, udevice, &obj_old, &obj_new);
 
 	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
 		nm_auto_pop_netns NMPNetns *netns = NULL;
 
+		cache_on_change (platform, cache_op, obj_old, obj_new);
 		if (!nm_platform_netns_push (platform, &netns))
 			return;
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 	}
 }
 
@@ -6683,7 +6647,7 @@ udev_device_removed (NMPlatform *platform,
 	if (ifindex <= 0) {
 		const NMPObject *obj;
 
-		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+		obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
 		                                  0, NULL, FALSE, NM_LINK_TYPE_NONE, _udev_device_removed_match_link, udevice);
 		if (obj)
 			ifindex = obj->link.ifindex;
@@ -6735,22 +6699,12 @@ static void
 nm_linux_platform_init (NMLinuxPlatform *self)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (self);
-	gboolean use_udev;
-
-	use_udev =    nmp_netns_is_initial ()
-	           && access ("/sys", W_OK) == 0;
 
 	priv->nlh_seq_next = 1;
-	priv->cache = nmp_cache_new (use_udev);
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
 	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
 	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
-
-	if (use_udev) {
-		priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
-		                                        handle_udev_event, self);
-	}
 }
 
 static void
@@ -6764,6 +6718,11 @@ constructed (GObject *_object)
 
 	nm_assert (!platform->_netns || platform->_netns == nmp_netns_get_current ());
 
+	if (nm_platform_get_use_udev (platform)) {
+		priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
+		                                        handle_udev_event, platform);
+	}
+
 	_LOGD ("create (%s netns, %s, %s udev)",
 	       !platform->_netns ? "ignore" : "use",
 	       !platform->_netns && nmp_netns_is_initial ()
@@ -6773,7 +6732,7 @@ constructed (GObject *_object)
 	                : nm_sprintf_bufa (100, "in netns[%p]%s",
 	                                   nmp_netns_get_current (),
 	                                   nmp_netns_get_current () == nmp_netns_get_initial () ? "/main" : "")),
-	       nmp_cache_use_udev_get (priv->cache) ? "use" : "no");
+	       nm_platform_get_use_udev (platform) ? "use" : "no");
 
 	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
@@ -6874,8 +6833,6 @@ dispose (GObject *object)
 	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
 	g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
 
-	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
-
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
 }
 
@@ -6884,8 +6841,6 @@ 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);
 	g_array_unref (priv->delayed_action.list_wait_for_nl_response);
@@ -6919,16 +6874,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	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_type_name = link_get_type_name;
-	platform_class->link_get_unmanaged = link_get_unmanaged;
-
-	platform_class->link_get_lnk = link_get_lnk;
 
 	platform_class->link_refresh = link_refresh;
 
@@ -6940,7 +6887,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_noarp = link_set_noarp;
 
 	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_set_token = link_set_token;
@@ -6948,6 +6894,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_address = link_set_address;
 	platform_class->link_get_permanent_address = link_get_permanent_address;
 	platform_class->link_set_mtu = link_set_mtu;
+	platform_class->link_set_name = link_set_name;
 	platform_class->link_set_sriov_num_vfs = link_set_sriov_num_vfs;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
@@ -6995,26 +6942,16 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_ipip_add = link_ipip_add;
 	platform_class->link_sit_add = link_sit_add;
 
-	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_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->check_support_kernel_extended_ifa_flags = check_support_kernel_extended_ifa_flags;
-	platform_class->check_support_user_ipv6ll = check_support_user_ipv6ll;
+	platform_class->ip_route_add = ip_route_add;
+	platform_class->ip_route_delete = ip_route_delete;
+	platform_class->ip_route_get = ip_route_get;
+
+	platform_class->check_kernel_support = check_kernel_support;
 
 	platform_class->process_events = process_events;
 }